[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> ForkController.php (source)

   1  <?php
   2  /**
   3   * Class for managing forking command line scripts.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   */
  22  
  23  /**
  24   * Class for managing forking command line scripts.
  25   * Currently just does forking and process control, but it could easily be extended
  26   * to provide IPC and job dispatch.
  27   *
  28   * This class requires the posix and pcntl extensions.
  29   *
  30   * @ingroup Maintenance
  31   */
  32  class ForkController {
  33      protected $children = array(), $childNumber = 0;
  34      protected $termReceived = false;
  35      protected $flags = 0, $procsToStart = 0;
  36  
  37      protected static $restartableSignals = array(
  38          SIGFPE,
  39          SIGILL,
  40          SIGSEGV,
  41          SIGBUS,
  42          SIGABRT,
  43          SIGSYS,
  44          SIGPIPE,
  45          SIGXCPU,
  46          SIGXFSZ,
  47      );
  48  
  49      /**
  50       * Pass this flag to __construct() to cause the class to automatically restart
  51       * workers that exit with non-zero exit status or a signal such as SIGSEGV.
  52       */
  53      const RESTART_ON_ERROR = 1;
  54  
  55  	public function __construct( $numProcs, $flags = 0 ) {
  56          if ( PHP_SAPI != 'cli' ) {
  57              throw new MWException( "ForkController cannot be used from the web." );
  58          }
  59          $this->procsToStart = $numProcs;
  60          $this->flags = $flags;
  61      }
  62  
  63      /**
  64       * Start the child processes.
  65       *
  66       * This should only be called from the command line. It should be called
  67       * as early as possible during execution.
  68       *
  69       * This will return 'child' in the child processes. In the parent process,
  70       * it will run until all the child processes exit or a TERM signal is
  71       * received. It will then return 'done'.
  72       * @return string
  73       */
  74  	public function start() {
  75          // Trap SIGTERM
  76          pcntl_signal( SIGTERM, array( $this, 'handleTermSignal' ), false );
  77  
  78          do {
  79              // Start child processes
  80              if ( $this->procsToStart ) {
  81                  if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) {
  82                      return 'child';
  83                  }
  84                  $this->procsToStart = 0;
  85              }
  86  
  87              // Check child status
  88              $status = false;
  89              $deadPid = pcntl_wait( $status );
  90  
  91              if ( $deadPid > 0 ) {
  92                  // Respond to child process termination
  93                  unset( $this->children[$deadPid] );
  94                  if ( $this->flags & self::RESTART_ON_ERROR ) {
  95                      if ( pcntl_wifsignaled( $status ) ) {
  96                          // Restart if the signal was abnormal termination
  97                          // Don't restart if it was deliberately killed
  98                          $signal = pcntl_wtermsig( $status );
  99                          if ( in_array( $signal, self::$restartableSignals ) ) {
 100                              echo "Worker exited with signal $signal, restarting\n";
 101                              $this->procsToStart++;
 102                          }
 103                      } elseif ( pcntl_wifexited( $status ) ) {
 104                          // Restart on non-zero exit status
 105                          $exitStatus = pcntl_wexitstatus( $status );
 106                          if ( $exitStatus != 0 ) {
 107                              echo "Worker exited with status $exitStatus, restarting\n";
 108                              $this->procsToStart++;
 109                          } else {
 110                              echo "Worker exited normally\n";
 111                          }
 112                      }
 113                  }
 114                  // Throttle restarts
 115                  if ( $this->procsToStart ) {
 116                      usleep( 500000 );
 117                  }
 118              }
 119  
 120              // Run signal handlers
 121              if ( function_exists( 'pcntl_signal_dispatch' ) ) {
 122                  pcntl_signal_dispatch();
 123              } else {
 124                  declare( ticks = 1 ) {
 125                      $status = $status;
 126                  }
 127              }
 128              // Respond to TERM signal
 129              if ( $this->termReceived ) {
 130                  foreach ( $this->children as $childPid => $unused ) {
 131                      posix_kill( $childPid, SIGTERM );
 132                  }
 133                  $this->termReceived = false;
 134              }
 135          } while ( count( $this->children ) );
 136          pcntl_signal( SIGTERM, SIG_DFL );
 137          return 'done';
 138      }
 139  
 140      /**
 141       * Get the number of the child currently running. Note, this
 142       * is not the pid, but rather which of the total number of children
 143       * we are
 144       * @return int
 145       */
 146  	public function getChildNumber() {
 147          return $this->childNumber;
 148      }
 149  
 150  	protected function prepareEnvironment() {
 151          global $wgMemc;
 152          // Don't share DB, storage, or memcached connections
 153          wfGetLBFactory()->destroyInstance();
 154          FileBackendGroup::destroySingleton();
 155          LockManagerGroup::destroySingletons();
 156          ObjectCache::clear();
 157          $wgMemc = null;
 158      }
 159  
 160      /**
 161       * Fork a number of worker processes.
 162       *
 163       * @param int $numProcs
 164       * @return string
 165       */
 166  	protected function forkWorkers( $numProcs ) {
 167          $this->prepareEnvironment();
 168  
 169          // Create the child processes
 170          for ( $i = 0; $i < $numProcs; $i++ ) {
 171              // Do the fork
 172              $pid = pcntl_fork();
 173              if ( $pid === -1 || $pid === false ) {
 174                  echo "Error creating child processes\n";
 175                  exit( 1 );
 176              }
 177  
 178              if ( !$pid ) {
 179                  $this->initChild();
 180                  $this->childNumber = $i;
 181                  return 'child';
 182              } else {
 183                  // This is the parent process
 184                  $this->children[$pid] = true;
 185              }
 186          }
 187  
 188          return 'parent';
 189      }
 190  
 191  	protected function initChild() {
 192          global $wgMemc, $wgMainCacheType;
 193          $wgMemc = wfGetCache( $wgMainCacheType );
 194          $this->children = null;
 195          pcntl_signal( SIGTERM, SIG_DFL );
 196      }
 197  
 198  	protected function handleTermSignal( $signal ) {
 199          $this->termReceived = true;
 200      }
 201  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1