webserver.php
changeset 76 487a16c7117c
parent 68 32f6e2ee15ab
equal deleted inserted replaced
75:2f39cb7f54c4 76:487a16c7117c
   218    */
   218    */
   219   
   219   
   220   var $reboot_sent = false;
   220   var $reboot_sent = false;
   221   
   221   
   222   /**
   222   /**
       
   223    * Tracks old allow_fork setting for during reboots
       
   224    * @var bool
       
   225    */
       
   226   
       
   227   var $old_allow_fork = false;
       
   228   
       
   229   /**
   223    * Constructor.
   230    * Constructor.
   224    * @param string IPv4 address to bind to
   231    * @param string IPv4 address to bind to
   225    * @param int Port number
   232    * @param int Port number
   226    * @param int If port is under 1024, specify a user ID/name to switch to here
   233    * @param int If port is under 1024, specify a user ID/name to switch to here
   227    * @param int If port is under 1024, specify a group ID/name to switch to here
   234    * @param int If port is under 1024, specify a group ID/name to switch to here
   332     $this->bind_address = $address;
   339     $this->bind_address = $address;
   333     $this->port = $port;
   340     $this->port = $port;
   334     $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n";
   341     $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n";
   335     $this->parent_pid = getmypid();
   342     $this->parent_pid = getmypid();
   336     $this->threader = new Threader();
   343     $this->threader = new Threader();
   337     $this->threader->ipc_register('ws_reboot', array(&$this, 'reboot'));
   344     $this->threader->ipc_register('ws_reboot', array(&$this, 'reboot_ipc'));
   338     
   345     
   339     // create a UUID
   346     // create a UUID
   340     $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() ));
   347     $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() ));
   341     $this->uuid = substr($uuid_base, 0,  8) . '-' .
   348     $this->uuid = substr($uuid_base, 0,  8) . '-' .
   342                   substr($uuid_base, 8,  4) . '-' .
   349                   substr($uuid_base, 8,  4) . '-' .
   382   function reboot($addr = null, $port = null, $allow_fork = null)
   389   function reboot($addr = null, $port = null, $allow_fork = null)
   383   {
   390   {
   384     if ( function_exists('status') )
   391     if ( function_exists('status') )
   385       status('Reboot request has been received');
   392       status('Reboot request has been received');
   386     
   393     
   387     $addr = ( !is_string($addr) ) ? $this->bind_address : $addr;
   394     // The goal of the reboot() function is to only initiate the reboot - not
   388     $port = ( !is_int($port) ) ? $this->port : $port;
   395     // actually start it. This is because reboot() is often called from signal
   389     $fork = ( !is_bool($allow_fork) ) ? $this->allow_fork : $allow_fork;
   396     // handlers, thus trying to kill all children can result in a lockup.
   390     
   397     
   391     //
       
   392     // REBOOTING IS A COMPLICATED THING.
       
   393     // We need to ask all children to close any existing connections so
       
   394     // that all relevant socket resources can be freed. Then we need to
       
   395     // call the constructor again to respawn the server, and finally
       
   396     // re-enter the server loop.
       
   397     //
       
   398     // However, reboot() is often called from a PHP-based handler. This
       
   399     // means that some config page probably still needs to be sent. What
       
   400     // we can do here is send an IPC event that fires the actual reboot,
       
   401     // then return to allow the current page to finish up. We also need
       
   402     // to signal the current process to shut down any existing keep-
       
   403     // alive connections. This can be accomplished by setting in_keepalive
       
   404     // to false.
       
   405     //
       
   406     
       
   407     // Kill the entire child after this response is sent
       
   408     $this->in_keepalive = false;
       
   409     
       
   410     // If we're the parent process, we need to know that a reboot event
       
   411     // was fired, and thus the server's main socket needs to be destroyed
       
   412     // and recreated. This is just done with another boolean switch.
       
   413     $this->reboot_sent = true;
   398     $this->reboot_sent = true;
   414     
   399     
   415     // this is really to track if there are any children
   400     // if we're a child, we have to tell the parent to reboot. during the next
   416     $oldfork = $this->allow_fork;
   401     // server loop, the child will die on its own (ok, with a little help from
   417     
   402     // the reboot_sent flag)
   418     // Set our new server flags
   403     
   419     $this->bind_address = $addr;
       
   420     $this->port = $port;
       
   421     $this->allow_fork = $fork;
       
   422     
       
   423     // If we're a child, we have to tell the parent what the hell is
       
   424     // going on, and then get out of here as quickly as possible
       
   425     // (and other children should do the same). If this is a child,
       
   426     // fire an IPC reboot event. Else, fire a "die all" event
       
   427     if ( $this->threader->is_child() )
   404     if ( $this->threader->is_child() )
   428     {
   405     {
   429       if ( function_exists('status') )
       
   430         status('Signaling parent with parameter changes (fork = ' . intval($fork) . ') + reboot request');
       
   431       // this is the child
       
   432       $this->threader->ipc_send(array(
   406       $this->threader->ipc_send(array(
   433         'action' => 'ws_reboot',
   407           'action' => 'ws_reboot',
   434         'addr' => $addr,
   408           'addr' => $addr,
   435         'port' => $port,
   409           'port' => $port,
   436         'fork' => $fork
   410           'fork' => $allow_fork
   437       ));
   411         ));
   438     }
       
   439     else if ( !$this->threader->is_child() && $oldfork )
       
   440     {
       
   441       // we are the parent and have been asked to respawn. there are (presumably)
       
   442       // still children running around; when one of them dies, we'll receive a
       
   443       // SIGCHLD, but often times this is already being called from the SIGUSR2
       
   444       // handler. whoops.
       
   445       if ( function_exists('status') )
       
   446         status('Waiting on all children');
       
   447       
       
   448       // this is the parent, and there are children present
       
   449       $this->threader->kill_all_children();
       
   450       
       
   451       // all children are dead, we are ok to respawn
       
   452       $this->respawn();
       
   453     }
   412     }
   454     else
   413     else
   455     {
   414     {
   456       // not sure what to do in this particular scenario.
   415       // If we're not a child, finish the job
       
   416       $this->reboot_ipc(array(
       
   417           'addr' => $addr,
       
   418           'port' => $port,
       
   419           'fork' => $allow_fork
       
   420         ));
       
   421     }
       
   422   }
       
   423   
       
   424   /**
       
   425    * Internal method called from an IPC reboot event
       
   426    * @access private
       
   427    */
       
   428   
       
   429   function reboot_ipc($params)
       
   430   {
       
   431     if ( function_exists('status') )
       
   432       status('IPC reboot is in stage 2');
       
   433     
       
   434     $this->reboot_sent = true;
       
   435     $this->old_allow_fork = $this->allow_fork;
       
   436     
       
   437     if ( is_string($params['addr']) )
       
   438     {
       
   439       $this->bind_address = $params['addr'];
       
   440     }
       
   441     if ( is_int($params['port']) )
       
   442     {
       
   443       $this->port = $params['port'];
       
   444     }
       
   445     if ( is_bool($params['fork']) )
       
   446     {
       
   447       $this->allow_fork = $params['fork'];
   457     }
   448     }
   458   }
   449   }
   459   
   450   
   460   /**
   451   /**
   461    * Respawns the server. All children should be dead, and any client
   452    * Respawns the server. All children should be dead, and any client
   466   {
   457   {
   467     $this->reboot_sent = false;
   458     $this->reboot_sent = false;
   468     
   459     
   469     if ( function_exists('status') )
   460     if ( function_exists('status') )
   470       status('Respawn event sent');
   461       status('Respawn event sent');
       
   462     
   471     $this->server->destroy();
   463     $this->server->destroy();
   472     unset($this->server);
   464     unset($this->server);
   473     
   465     
   474     // try to spawn up to 10 times
   466     // try to spawn up to 10 times
   475     for ( $i = 0; $i < 10; $i++ )
   467     for ( $i = 0; $i < 10; $i++ )
   514     {
   506     {
   515       ##
   507       ##
   516       ## STAGE 0: CLEANUP FROM PREVIOUS RUN
   508       ## STAGE 0: CLEANUP FROM PREVIOUS RUN
   517       ##
   509       ##
   518       
   510       
   519       // if this is a child process, we're finished - close up shop
   511       // if this is a child process, we may need to exit here.
   520       if ( $this->threader->is_child() && !$this->in_keepalive )
   512       if ( $this->threader->is_child() && ( !$this->in_keepalive || $this->reboot_sent ) )
   521       {
   513       {
   522         if ( function_exists('status') )
   514         if ( function_exists('status') )
   523           status('Exiting child process');
   515           status('Exiting child process');
   524         
   516         
   525         $remote->destroy();
   517         $remote->destroy();
   526         
   518         
   527         exit(0);
   519         exit(0);
       
   520       }
       
   521       
       
   522       // If we're waiting on a reboot, take care of it now
       
   523       if ( $this->reboot_sent )
       
   524       {
       
   525         if ( $this->old_allow_fork )
       
   526         {
       
   527           // we are the parent and have been asked to respawn. there are (presumably)
       
   528           // still children running around; when one of them dies, we'll receive a
       
   529           // SIGCHLD, but often times this is already being called from the SIGUSR2
       
   530           // handler. whoops.
       
   531           if ( function_exists('status') )
       
   532             status('Waiting on all children');
       
   533           
       
   534           // this is the parent, and there are children present
       
   535           $this->threader->kill_all_children();
       
   536         }
       
   537         
       
   538         if ( function_exists('status') )
       
   539           status('Reboot is a go');
       
   540         
       
   541         $this->respawn();
   528       }
   542       }
   529       
   543       
   530       ##
   544       ##
   531       ## STAGE 1: LISTENER AND INIT
   545       ## STAGE 1: LISTENER AND INIT
   532       ##
   546       ##