diff -r b7f1952cef8d -r d643bfb862d8 webserver.php --- a/webserver.php Mon Sep 01 17:03:44 2008 -0400 +++ b/webserver.php Tue Sep 23 23:24:13 2008 -0400 @@ -13,6 +13,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. */ +require('multithreading.php'); + /** * Version of the server * @const string @@ -60,6 +62,13 @@ var $bind_address = '127.0.0.1'; /** + * Port we're listening on + * @var int + */ + + var $port = 8080; + + /** * Socket abstraction object * @var object */ @@ -130,6 +139,13 @@ var $allow_fork = true; /** + * Multi-threading manager. + * @var object + */ + + var $threader = false; + + /** * Keep-alive support uses this to track what the client requested. * Only used if $allow_fork is set to true. * @var bool @@ -180,6 +196,14 @@ var $parent_pid = 0; /** + * List of IPC request handlers + * @var array + * @access private + */ + + var $ipc_handlers = array(); + + /** * Sockets for parent and child to communicate * @var resource * @var resource @@ -189,6 +213,13 @@ var $child_sock = null; /** + * Switched on when a graceful reboot event is sent. + * @var bool + */ + + var $reboot_sent = false; + + /** * Constructor. * @param string IPv4 address to bind to * @param int Port number @@ -201,12 +232,6 @@ @set_time_limit(0); @ini_set('memory_limit', '128M'); - // do we have socket functions? - if ( !function_exists('socket_create') ) - { - burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.'); - } - // make sure we're not running as root // note that if allow_root is true, you must specify a UID/GID (or user/group) to switch to once the socket is bound $allow_root = ( $port < 1024 ) ? true : false; @@ -275,8 +300,10 @@ } $this->bind_address = $address; + $this->port = $port; $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n"; $this->parent_pid = getmypid(); + $this->threader = new Threader(); // create a UUID $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() )); @@ -294,7 +321,7 @@ function __destruct() { - if ( !defined('HTTPD_WS_CHILD') && $this->socket_initted ) + if ( !$this->threader->is_child() && $this->socket_initted ) { if ( function_exists('status') ) status('WebServer: destroying socket'); @@ -305,18 +332,138 @@ { if ( function_exists('status') ) status('WebServer: asking all children to exit'); - $this->send_ipc_event("die _"); + $this->threader->kill_all_children(); } + } + } + + /** + * Reboot the server. Useful for applying new settings. + * @param string Optional, new IP address to bind to + * @param int Optional, new port to bind to + * @param bool Optional, whether to allow forking or not + */ + + function reboot($addr = null, $port = null, $allow_fork = null) + { + if ( function_exists('status') ) + status('Reboot request has been received'); + + $addr = ( !is_string($addr) ) ? $this->bind_address : $addr; + $port = ( !is_int($port) ) ? $this->port : $port; + $fork = ( !is_bool($allow_fork) ) ? $this->allow_fork : $allow_fork; + + // + // REBOOTING IS A COMPLICATED THING. + // We need to ask all children to close any existing connections so + // that all relevant socket resources can be freed. Then we need to + // call the constructor again to respawn the server, and finally + // re-enter the server loop. + // + // However, reboot() is often called from a PHP-based handler. This + // means that some config page probably still needs to be sent. What + // we can do here is send an IPC event that fires the actual reboot, + // then return to allow the current page to finish up. We also need + // to signal the current process to shut down any existing keep- + // alive connections. This can be accomplished by setting in_keepalive + // to false. + // + + // Kill the entire child after this response is sent + $this->in_keepalive = false; + + // If we're the parent process, we need to know that a reboot event + // was fired, and thus the server's main socket needs to be destroyed + // and recreated. This is just done with another boolean switch. + $this->reboot_sent = true; + + // this is really to track if there are any children + $oldfork = $this->allow_fork; + + // Set our new server flags + $this->bind_address = $addr; + $this->port = $port; + $this->allow_fork = $fork; + + // If we're a child, we have to tell the parent what the hell is + // going on, and then get out of here as quickly as possible + // (and other children should do the same). If this is a child, + // fire an IPC reboot event. Else, fire a "die all" event + if ( $this->threader->is_child() ) + { + if ( function_exists('status') ) + status('Signaling parent with parameter changes (fork = ' . intval($fork) . ') + reboot request'); + // this is the child + $this->threader->ipc_send(array( + 'action' => 'ws_reboot', + 'addr' => $addr, + 'port' => $port, + 'fork' => $fork + )); + } + else if ( !$this->threader->is_child() && $oldfork ) + { + if ( function_exists('status') ) + status('Waiting on all children'); - // that last operation should have been asynchronous, so shut everything down now - @socket_shutdown($this->parent_sock); - @socket_close($this->parent_sock); + // this is the parent, and there are children present + $this->threader->kill_all_children(); + + // all children are dead, we are ok to respawn + $this->respawn(); + } + else + { + // this is a childless parent; delay any action until the current + // request has been sent (do nothing now) } - else if ( defined('HTTPD_WS_CHILD') ) + } + + /** + * Respawns the server. All children should be dead, and any client + * connections must be closed. + */ + + function respawn() + { + $this->reboot_sent = false; + + if ( function_exists('status') ) + status('Respawn event sent'); + $this->server->destroy(); + unset($this->server); + + // try to spawn up to 10 times + for ( $i = 0; $i < 10; $i++ ) { - @socket_shutdown($this->child_sock); - @socket_close($this->child_sock); + try + { + $this->__construct($this->bind_address, $this->port); + } + catch ( Exception $e ) + { + if ( $i == 9 ) + { + if ( function_exists('burnout') ) + { + burnout("Couldn't respawn because one of the child processes did not die, and thus the port was not freed."); + } + exit(1); + } + if ( function_exists('status') ) + { + status("Respawn failed, retrying in 2 seconds"); + } + usleep(2000000); + continue; + } + break; } + + if ( function_exists('status') ) + status('Respawn is complete, entering server loop with bind_address = ' . $this->bind_address . ' allow_fork = ' . strval(intval($this->allow_fork))); + + // all handlers should already be set up, so just break out and we should automatically continue the server loop } /** @@ -325,44 +472,29 @@ function serve() { - // If we're allowed to use multithreading, set up to handle SIGUSR2 which waits on the child - if ( function_exists('pcntl_signal') && $this->allow_fork ) - { - // required for signal handling to work - declare(ticks=1); - - // trap SIGTERM - pcntl_signal(SIGUSR2, array(&$this, '_ipc_event')); - - if ( !($sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP)) ) - { - throw new Exception("Could not set up private IPC socket. Reason: " . socket_strerror(socket_last_error())); - } - - $this->parent_sock =& $sockets[0]; - $this->child_sock =& $sockets[1]; - } - while ( true ) { + ## + ## STAGE 0: CLEANUP FROM PREVIOUS RUN + ## + // if this is a child process, we're finished - close up shop - if ( defined('HTTPD_WS_CHILD') && !$this->in_keepalive ) + if ( $this->threader->is_child() && !$this->in_keepalive ) { if ( function_exists('status') ) status('Exiting child process'); $remote->destroy(); - // let the parent know that we're out of here - $this->send_ipc_event("exit " . getmypid()); - - // bye exit(0); } + ## + ## STAGE 1: LISTENER AND INIT + ## + // wait for connection... - // trick from http://us.php.net/manual/en/function.socket-accept.php - if ( !defined('HTTPD_WS_CHILD') ) + if ( !$this->threader->is_child() ) { $remote = $this->server->accept(); } @@ -376,42 +508,27 @@ // fork off if possible if ( function_exists('pcntl_fork') && $this->allow_fork && !$this->in_keepalive ) { - $pid = pcntl_fork(); - if ( $pid == -1 ) + if ( $this->threader->fork() == FORK_CHILD ) { - // do nothing; continue responding to request in single-threaded mode + // this is the child + define('HTTPD_WS_CHILD', 1); } - else if ( $pid ) + else { // we are the parent, continue listening $remote->soft_shutdown(); $this->child_list[] = $pid; continue; } - else - { - // this is the child - define('HTTPD_WS_CHILD', 1); - - // setup to handle signals - if ( function_exists('pcntl_signal') ) - { - // required for signal handling to work - declare(ticks=1); - - // trap SIGTERM - pcntl_signal(SIGUSR2, array(&$this, '_ipc_event')); - } - } } $this->in_keepalive = false; $this->headers_sent = false; $this->in_scriptlet = false; - // - // READ THE REQUEST - // + ## + ## STAGE 2: READ REQUEST + ## // this is a complicated situation because we need to keep enough ticks going to properly handle // signals, meaning we can't use stream_set_timeout() and instead need to rely on our own timing @@ -427,7 +544,7 @@ if ( $start_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) || $remote->is_eof() ) { // request expired -- end the process here - if ( !defined('HTTPD_WS_CHILD') ) + if ( !$this->threader->is_child() ) $remote->destroy(); continue 2; @@ -445,10 +562,14 @@ $last_line = $line; } + ## + ## STAGE 3: PARSE REQUEST AND HEADERS + ## + // parse request $client_headers = trim($client_headers); - if ( isset($last_finish_time) && empty($client_headers) && defined('HTTPD_WS_CHILD') && $last_finish_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) ) + if ( isset($last_finish_time) && empty($client_headers) && $this->threader->is_child() && $last_finish_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) ) { status('[debug] keep-alive connection timed out (checkpoint 2)'); continue; // will jump back to the start of the loop and kill the child process @@ -466,7 +587,7 @@ $method =& $match[1]; $uri =& $match[2]; - // set client headers + // set client header SERVER variables foreach ( $_SERVER as $key => $_ ) { if ( preg_match('/^HTTP_/', $key) ) @@ -482,7 +603,7 @@ } // enable keep-alive if requested - if ( isset($_SERVER['HTTP_CONNECTION']) && defined('HTTPD_WS_CHILD') ) + if ( isset($_SERVER['HTTP_CONNECTION']) && $this->threader->is_child() ) { $this->in_keepalive = ( strtolower($_SERVER['HTTP_CONNECTION']) === 'keep-alive' ); } @@ -515,133 +636,7 @@ $_FILES = array(); if ( $method == 'POST' ) { - // read POST data - if ( isset($_SERVER['HTTP_CONTENT_TYPE']) && preg_match('#^multipart/form-data; ?boundary=([A-z0-9_-]+)$#i', $_SERVER['HTTP_CONTENT_TYPE'], $match) ) - { - // this is a multipart request - $boundary =& $match[1]; - $mode = 'data'; - $last_line = ''; - $i = 0; - while ( $data = $remote->read_normal(8388608) ) - { - $data_trim = trim($data, "\r\n"); - if ( $mode != 'data' ) - { - $data = str_replace("\r", '', $data); - } - if ( ( $data_trim === "--$boundary" || $data_trim === "--$boundary--" ) && $i > 0 ) - { - // trim off the first LF and the last CRLF - $currval_data = substr($currval_data, 1, strlen($currval_data)-3); - - // this is the end of a part of the message; parse it into either $_POST or $_FILES - if ( is_string($have_a_file) ) - { - - // write data to a temporary file - $errcode = UPLOAD_ERR_OK; - $tempfile = tempnam('phpupload', ( function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp' )); - if ( $fh = @fopen($tempfile, 'w') ) - { - if ( empty($have_a_file) ) - { - $errcode = UPLOAD_ERR_NO_FILE; - } - else - { - fwrite($fh, $currval_data); - } - fclose($fh); - } - else - { - $errcode = UPLOAD_ERR_CANT_WRITE; - } - $_FILES[$currval_name] = array( - 'name' => $have_a_file, - 'type' => $currval_type, - 'size' => filesize($tempfile), - 'tmp_name' => $tempfile, - 'error' => $errcode - ); - } - else - { - $_POST[$currval_name] = $currval_data; - } - } - - if ( $data_trim === "--$boundary" ) - { - // switch from "data" mode to "headers" mode - $currval_name = ''; - $currval_data = ''; - $currval_type = ''; - $have_a_file = false; - $mode = 'headers'; - } - else if ( $data_trim === "--$boundary--" ) - { - // end of request - break; - } - else if ( ( empty($data_trim) && empty($last_line) ) && $mode == 'headers' ) - { - // start of data - $mode = 'data'; - } - else if ( $mode == 'headers' ) - { - // read header - // we're only looking for Content-Disposition and Content-Type - if ( preg_match('#^Content-Disposition: form-data; name="([^"\a\t\r\n]+)"(?:; filename="([^"\a\t\r\n]+)")?#i', $data_trim, $match) ) - { - // content-disposition header, set name and mode. - $currval_name = $match[1]; - if ( isset($match[2]) ) - { - $have_a_file = $match[2]; - } - else - { - $have_a_file = false; - } - } - else if ( preg_match('#^Content-Type: ([a-z0-9-]+/[a-z0-9/-]+)$#i', $data_trim, $match) ) - { - $currval_type = $match[1]; - } - } - else if ( $mode == 'data' ) - { - $currval_data .= $data; - } - $last_line = $data_trim; - $i++; - } - } - else - { - if ( isset($_SERVER['HTTP_CONTENT_LENGTH']) ) - { - $postdata = $remote->read_binary(intval($_SERVER['HTTP_CONTENT_LENGTH'])); - } - else - { - $postdata = $remote->read_normal(8388608); - } - if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $postdata, $matches) ) - { - if ( isset($matches[1]) ) - { - foreach ( $matches[0] as $i => $_ ) - { - $_POST[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true; - } - } - } - } + $this->parse_post_data($remote); } // parse URI @@ -659,6 +654,7 @@ // get remote IP and port $remote->get_peer_info($_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT']); + // process $_GET $_GET = array(); if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $params, $matches) ) { @@ -671,10 +667,15 @@ } } + // Parse GET, POST, and FILES into multi-depth arrays $_GET = $this->parse_multi_depth_array($_GET); $_POST = $this->parse_multi_depth_array($_POST); $_FILES = $this->parse_multi_depth_array($_FILES); + ## + ## STAGE 4: HANDLER RESOLUTION + ## + // init handler $handler = false; @@ -731,8 +732,16 @@ } } + ## + ## STAGE 5: HANDLER CALL + ## + $this->send_standard_response($remote, $handler, $uri, $params); + ## + ## STAGE 6: CLEANUP + ## + // now that we're done sending the response, delete any temporary uploaded files if ( !empty($_FILES) ) { @@ -745,22 +754,194 @@ } } - if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') ) + if ( !$this->in_keepalive && $this->threader->is_child() ) { // connection: close // continue on to the shutdown handler continue; } - else if ( defined('HTTPD_WS_CHILD') ) + else if ( $this->threader->is_child() ) { - // if ( defined('HTTPD_WS_CHILD') ) + // if ( $this->threader->is_child() ) // status('Continuing connection'); // $remote->write("\r\n\r\n"); $last_finish_time = microtime(true); } else { + // standalone process $remote->destroy(); + + // if a reboot was fired and we're running in single-process mode, now is the time to respawn + if ( !$this->threader->is_child() && $this->reboot_sent ) + { + $this->respawn(); + } + } + } + } + + /** + * Parse POST data and format $_POST and $_FILES. + * @param resource Remote socket + */ + + function parse_post_data($remote) + { + $postdata = ''; + + // read POST data + if ( isset($_SERVER['HTTP_CONTENT_TYPE']) && preg_match('#^multipart/form-data; ?boundary=([A-z0-9_-]+)$#i', $_SERVER['HTTP_CONTENT_TYPE'], $match) ) + { + // this is a multipart request + $boundary =& $match[1]; + $mode = 'data'; + $last_line = ''; + $i = 0; + while ( $data = $remote->read_normal(8388608) ) + { + $data_trim = trim($data, "\r\n"); + if ( $mode != 'data' ) + { + $data = str_replace("\r", '', $data); + } + if ( ( $data_trim === "--$boundary" || $data_trim === "--$boundary--" ) && $i > 0 ) + { + // trim off the first LF and the last CRLF + if ( HTTPD_SOCKET_LAYER == 'Raw' ) + $currval_data = substr($currval_data, 1, strlen($currval_data)-3); + else + $currval_data = substr($currval_data, 0, strlen($currval_data)-2); + + // this is the end of a part of the message; parse it into either $_POST or $_FILES + if ( is_string($have_a_file) ) + { + // write data to a temporary file + $errcode = UPLOAD_ERR_OK; + $tempfile = tempnam('phpupload', ( function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp' )); + if ( $fh = @fopen($tempfile, 'w') ) + { + if ( empty($have_a_file) ) + { + $errcode = UPLOAD_ERR_NO_FILE; + } + else + { + fwrite($fh, $currval_data); + } + fclose($fh); + } + else + { + $errcode = UPLOAD_ERR_CANT_WRITE; + } + $_FILES[$currval_name] = array( + 'name' => $have_a_file, + 'type' => $currval_type, + 'size' => filesize($tempfile), + 'tmp_name' => $tempfile, + 'error' => $errcode + ); + } + else + { + if ( preg_match('/\[\]$/', $currval_name) ) + { + if ( !isset($_POST[$currval_name]) || ( isset($_POST[$currval_name]) && !is_array($_POST[$currval_name]) ) ) + $_POST[$currval_name] = array(); + + $_POST[$currval_name][] = $currval_data; + } + else + { + $_POST[$currval_name] = $currval_data; + } + } + } + + if ( $data_trim === "--$boundary" ) + { + // switch from "data" mode to "headers" mode + $currval_name = ''; + $currval_data = ''; + $currval_type = ''; + $have_a_file = false; + $mode = 'headers'; + } + else if ( $data_trim === "--$boundary--" ) + { + // end of request + break; + } + else if ( ( empty($data_trim) && ( ( HTTPD_SOCKET_LAYER == 'Raw' && empty($last_line) ) || HTTPD_SOCKET_LAYER != 'Raw' ) ) && $mode == 'headers' ) + { + // start of data + $mode = 'data'; + } + else if ( $mode == 'headers' ) + { + // read header + // we're only looking for Content-Disposition and Content-Type + if ( preg_match('#^Content-Disposition: form-data; name="([^"\a\t\r\n]+)"(?:; filename="([^"\a\t\r\n]+)")?#i', $data_trim, $match) ) + { + // content-disposition header, set name and mode. + $currval_name = $match[1]; + if ( isset($match[2]) ) + { + $have_a_file = $match[2]; + } + else + { + $have_a_file = false; + } + } + else if ( preg_match('#^Content-Type: ([a-z0-9-]+/[a-z0-9/-]+)$#i', $data_trim, $match) ) + { + $currval_type = $match[1]; + } + } + else if ( $mode == 'data' ) + { + $currval_data .= $data; + } + $last_line = $data_trim; + $i++; + } + } + else + { + if ( isset($_SERVER['HTTP_CONTENT_LENGTH']) ) + { + $postdata = $remote->read_binary(intval($_SERVER['HTTP_CONTENT_LENGTH'])); + } + else + { + $postdata = $remote->read_normal(8388608); + } + if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]%-]+)(=[^ &]+)?/i', $postdata, $matches) ) + { + if ( isset($matches[1]) ) + { + foreach ( $matches[0] as $i => $_ ) + { + $currval_name =& $matches[2][$i]; + $currval_data = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true; + $currval_name = urldecode($currval_name); + + if ( preg_match('/\[\]$/', $currval_name) ) + { + $basename = preg_replace('/\[\]$/', '', $currval_name); + if ( !isset($_POST[$basename]) || ( isset($_POST[$basename]) && !is_array($_POST[$basename]) ) ) + $_POST[$basename] = array(); + + $_POST[$basename][] = $currval_data; + } + else + { + $_POST[$currval_name] = $currval_data; + } + } + } } } } @@ -1603,7 +1784,7 @@ { foreach ( $array as $key => $value ) { - if ( preg_match('/^([^\[\]]+)\[([^\]]+)\]/', $key, $match) ) + if ( preg_match('/^([^\[\]]+)\[([^\]]*)\]/', $key, $match) ) { $parent =& $match[1]; $child =& $match[2]; @@ -1611,7 +1792,14 @@ { $array[$parent] = array(); } - $array[$parent][$child] = $value; + if ( empty($child) ) + { + $array[$parent][] = $value; + } + else + { + $array[$parent][$child] = $value; + } unset($array[$key]); $array[$parent] = $this->parse_multi_depth_array($array[$parent]); } @@ -1625,85 +1813,33 @@ function _ipc_event() { - $pid = getmypid() . ':' . $this->parent_pid; - - // decide which socket to use - if ( defined('HTTPD_WS_CHILD') ) - $sock =& $this->parent_sock; - else - $sock =& $this->child_sock; - - // try to read the event - // this sometimes gets hung up because socket_set_timeout() doesn't seem to work on its own set of - // functions (it only works on PHP's normal streams) - if ( $line = @fgets($sock, 1024) ) - { - $line = trim($line); - list($action, $param) = explode(' ', $line); - switch($action) + /* + case 'set_addr': + $this->bind_address = $param; + break; + case 'set_port': + $this->port = intval($param); + break; + case 'set_fork': + $this->allow_fork = ( $param == '1' ); + break; + case 'reboot': + if ( !$this->threader->is_child() ) { - case 'exit': - // this is to prevent zombie children - pcntl_waitpid(intval($param), $status); - // we know this child is dead now, remove them from the list - foreach ( $this->child_list as $i => $pid ) + list(, $addr, $port, $fork) = explode(' ', $line); + $fork = ( $fork === '1' ); + $this->reboot($addr, intval($port), $fork); + } + break; + default: + if ( isset($this->ipc_handlers[$action]) ) { - if ( $pid === intval($param) ) - { - unset($this->child_list[$i]); - $this->child_list = array_values($this->child_list); - break; - } + @call_user_func($this->ipc_handlers[$action], $line); } break; - case 'die': - // only do this if this is a child (both security and design) - if ( defined('HTTPD_WS_CHILD') ) - { - if ( function_exists('status') ) - { - status('Received shutdown request, complying'); - } - $this->send_ipc_event("exit " . getmypid()); - exit(0); - } - break; - default: - break; } - } + */ } - - /** - * Send an IPC event. - * @param string Data to write to the socket, newline will be added automatically - */ - - function send_ipc_event($data) - { - if ( defined('HTTPD_WS_CHILD') ) - $sock =& $this->parent_sock; - else - $sock =& $this->child_sock; - - $data = rtrim($data, "\r\n") . "\n"; - @fwrite($sock, $data); - - // if we're a child, signal the parent - if ( defined('HTTPD_WS_CHILD') ) - { - posix_kill($this->parent_pid, SIGUSR2); - } - // if we're the parent, signal all children - else - { - foreach ( $this->child_list as $pid ) - { - posix_kill($pid, SIGUSR2); - } - } - } - } /** @@ -1717,6 +1853,12 @@ function tcp_listen($address, $port) { + // do we have socket functions? + if ( !function_exists('socket_create') ) + { + burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.'); + } + $this->sock = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')); if ( !$this->sock ) throw new Exception('Could not create socket'); @@ -1823,6 +1965,12 @@ function tcp_listen($address, $port) { + // does PHP support this? + if ( !function_exists('stream_socket_server') ) + { + burnout('System does not support stream functions. Please rebuild your PHP or install an appropriate extension.'); + } + $this->sock = @stream_socket_server("tcp://$address:$port", $errno, $errstr); if ( !$this->sock ) throw new Exception("Could not create the socket: error $errno: $errstr"); @@ -1837,13 +1985,16 @@ { @stream_socket_shutdown($this->sock, STREAM_SHUT_RDWR); } - fclose($this->sock); + while ( !@fclose($this->sock) ) + { + usleep(100000); + } } } function accept() { - // the goal of a custom accept() with *_select() is to tick every 5 seconds to allow signals. + // the goal of a custom accept() with *_select() is to tick every 200ms to allow signals. stream_set_blocking($this->sock, 1); $timeout = 5; $selection = @stream_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), $timeout);