Considerably improved reliability of reboot code. Still producing respawn errors so currently disabled.
authorDan
Fri, 12 Jun 2009 13:48:22 -0400
changeset 76 487a16c7117c
parent 75 2f39cb7f54c4
child 77 e5f1f45ea7e2
Considerably improved reliability of reboot code. Still producing respawn errors so currently disabled.
multithreading.php
webserver.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);
   }
   
   /**
--- 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
       ##