# HG changeset patch # User Dan # Date 1244828902 14400 # Node ID 487a16c7117cc552465e991d5b854b693e7c5a6f # Parent 2f39cb7f54c42a1831d9143cf74bc0e16f8d02ae Considerably improved reliability of reboot code. Still producing respawn errors so currently disabled. diff -r 2f39cb7f54c4 -r 487a16c7117c multithreading.php --- a/multithreading.php Fri Jun 12 13:47:12 2009 -0400 +++ b/multithreading.php Fri Jun 12 13:48:22 2009 -0400 @@ -313,7 +313,7 @@ $this->ipc_send($command); } // we're good - @call_user_func($this->ipc_actions[$command['action']], $command, $this); + call_user_func($this->ipc_actions[$command['action']], $command, $this); } /** diff -r 2f39cb7f54c4 -r 487a16c7117c webserver.php --- a/webserver.php Fri Jun 12 13:47:12 2009 -0400 +++ b/webserver.php Fri Jun 12 13:48:22 2009 -0400 @@ -220,6 +220,13 @@ var $reboot_sent = false; /** + * Tracks old allow_fork setting for during reboots + * @var bool + */ + + var $old_allow_fork = false; + + /** * Constructor. * @param string IPv4 address to bind to * @param int Port number @@ -334,7 +341,7 @@ $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n"; $this->parent_pid = getmypid(); $this->threader = new Threader(); - $this->threader->ipc_register('ws_reboot', array(&$this, 'reboot')); + $this->threader->ipc_register('ws_reboot', array(&$this, 'reboot_ipc')); // create a UUID $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() )); @@ -384,76 +391,60 @@ 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; + // The goal of the reboot() function is to only initiate the reboot - not + // actually start it. This is because reboot() is often called from signal + // handlers, thus trying to kill all children can result in a lockup. - // - // 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; + // if we're a child, we have to tell the parent to reboot. during the next + // server loop, the child will die on its own (ok, with a little help from + // the reboot_sent flag) - // 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 ) - { - // we are the parent and have been asked to respawn. there are (presumably) - // still children running around; when one of them dies, we'll receive a - // SIGCHLD, but often times this is already being called from the SIGUSR2 - // handler. whoops. - if ( function_exists('status') ) - status('Waiting on all children'); - - // 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(); + 'action' => 'ws_reboot', + 'addr' => $addr, + 'port' => $port, + 'fork' => $allow_fork + )); } else { - // not sure what to do in this particular scenario. + // If we're not a child, finish the job + $this->reboot_ipc(array( + 'addr' => $addr, + 'port' => $port, + 'fork' => $allow_fork + )); + } + } + + /** + * Internal method called from an IPC reboot event + * @access private + */ + + function reboot_ipc($params) + { + if ( function_exists('status') ) + status('IPC reboot is in stage 2'); + + $this->reboot_sent = true; + $this->old_allow_fork = $this->allow_fork; + + if ( is_string($params['addr']) ) + { + $this->bind_address = $params['addr']; + } + if ( is_int($params['port']) ) + { + $this->port = $params['port']; + } + if ( is_bool($params['fork']) ) + { + $this->allow_fork = $params['fork']; } } @@ -468,6 +459,7 @@ if ( function_exists('status') ) status('Respawn event sent'); + $this->server->destroy(); unset($this->server); @@ -516,8 +508,8 @@ ## STAGE 0: CLEANUP FROM PREVIOUS RUN ## - // if this is a child process, we're finished - close up shop - if ( $this->threader->is_child() && !$this->in_keepalive ) + // if this is a child process, we may need to exit here. + if ( $this->threader->is_child() && ( !$this->in_keepalive || $this->reboot_sent ) ) { if ( function_exists('status') ) status('Exiting child process'); @@ -527,6 +519,28 @@ exit(0); } + // If we're waiting on a reboot, take care of it now + if ( $this->reboot_sent ) + { + if ( $this->old_allow_fork ) + { + // we are the parent and have been asked to respawn. there are (presumably) + // still children running around; when one of them dies, we'll receive a + // SIGCHLD, but often times this is already being called from the SIGUSR2 + // handler. whoops. + if ( function_exists('status') ) + status('Waiting on all children'); + + // this is the parent, and there are children present + $this->threader->kill_all_children(); + } + + if ( function_exists('status') ) + status('Reboot is a go'); + + $this->respawn(); + } + ## ## STAGE 1: LISTENER AND INIT ##