Considerably improved reliability of reboot code. Still producing respawn errors so currently disabled.
--- 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);
}
/**
--- 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
##