X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2FForkController.php;h=9cacef54973310d64f7b56742ed5d3c9c494ae64;hb=a2a9b469908464da9f2a42dcf48412f62215cd6b;hp=689b9c8de2945e241230471bf1920f2a29aaa74b;hpb=abbf76b7b1782d6484519fa3e2d308981560b59e;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/ForkController.php b/includes/ForkController.php index 689b9c8de2..9cacef5497 100644 --- a/includes/ForkController.php +++ b/includes/ForkController.php @@ -2,84 +2,105 @@ /** * Class for managing forking command line scripts. - * Currently just does forking and process control, but it could easily be extended + * Currently just does forking and process control, but it could easily be extended * to provide IPC and job dispatch. + * + * This class requires the posix and pcntl extensions. + * + * @ingroup Maintenance */ class ForkController { var $children = array(); var $termReceived = false; + var $flags = 0, $procsToStart = 0; - public function __construct() { - if ( php_sapi_name() != 'cli' ) { - throw new MWException( "MultiProcess cannot be used from the web." ); - } - } - - protected function prepareEnvironment() { - global $wgCaches, $wgMemc; - // Don't share DB or memcached connections - wfGetLBFactory()->destroyInstance(); - $wgCaches = array(); - unset( $wgMemc ); - } + static $restartableSignals = array( + SIGFPE, + SIGILL, + SIGSEGV, + SIGBUS, + SIGABRT, + SIGSYS, + SIGPIPE, + SIGXCPU, + SIGXFSZ, + ); /** - * Fork a number of worker processes. - * - * This should only be called from the command line. It should be called - * as early as possible during execution. It will return 'child' in the - * child processes and 'parent' in the parent process. The parent process - * should then call monitor(). - * - * This function requires the posix and pcntl extensions. + * Pass this flag to __construct() to cause the class to automatically restart + * workers that exit with non-zero exit status or a signal such as SIGSEGV. */ - public function forkWorkers( $numProcs ) { - global $wgMemc, $wgCaches, $wgMainCacheType; - - $this->prepareEnvironment(); - - // Create the child processes - for ( $i = 0; $i < $numProcs; $i++ ) { - // Do the fork - $pid = pcntl_fork(); - if ( $pid === -1 || $pid === false ) { - echo "Error creating child processes\n"; - exit( 1 ); - } + const RESTART_ON_ERROR = 1; - if ( !$pid ) { - $this->initChild(); - $this->children = null; - return 'child'; - } else { - // This is the parent process - $this->children[$pid] = true; - } + public function __construct( $numProcs, $flags = 0 ) { + if ( php_sapi_name() != 'cli' ) { + throw new MWException( "ForkController cannot be used from the web." ); } - - return 'parent'; + $this->procsToStart = $numProcs; + $this->flags = $flags; } /** - * The parent process main loop + * Start the child processes. + * + * This should only be called from the command line. It should be called + * as early as possible during execution. + * + * This will return 'child' in the child processes. In the parent process, + * it will run until all the child processes exit or a TERM signal is + * received. It will then return 'done'. */ - public function runParent() { + public function start() { // Trap SIGTERM pcntl_signal( SIGTERM, array( $this, 'handleTermSignal' ), false ); do { + // Start child processes + if ( $this->procsToStart ) { + if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) { + return 'child'; + } + $this->procsToStart = 0; + } + + // Check child status $status = false; $deadPid = pcntl_wait( $status ); if ( $deadPid > 0 ) { + // Respond to child process termination unset( $this->children[$deadPid] ); + if ( $this->flags & self::RESTART_ON_ERROR ) { + if ( pcntl_wifsignaled( $status ) ) { + // Restart if the signal was abnormal termination + // Don't restart if it was deliberately killed + $signal = pcntl_wtermsig( $status ); + if ( in_array( $signal, self::$restartableSignals ) ) { + echo "Worker exited with signal $signal, restarting\n"; + $this->procsToStart++; + } + } elseif ( pcntl_wifexited( $status ) ) { + // Restart on non-zero exit status + $exitStatus = pcntl_wexitstatus( $status ); + if ( $exitStatus != 0 ) { + echo "Worker exited with status $exitStatus, restarting\n"; + $this->procsToStart++; + } else { + echo "Worker exited normally\n"; + } + } + } + // Throttle restarts + if ( $this->procsToStart ) { + usleep( 500000 ); + } } // Run signal handlers if ( function_exists( 'pcntl_signal_dispatch' ) ) { pcntl_signal_dispatch(); } else { - declare (ticks=1) { $status = $status; } + declare (ticks=1) { $status = $status; } } // Respond to TERM signal if ( $this->termReceived ) { @@ -90,11 +111,51 @@ class ForkController { } } while ( count( $this->children ) ); pcntl_signal( SIGTERM, SIG_DFL ); + return 'done'; + } + + protected function prepareEnvironment() { + global $wgMemc; + // Don't share DB or memcached connections + wfGetLBFactory()->destroyInstance(); + ObjectCache::clear(); + $wgMemc = null; + } + + /** + * Fork a number of worker processes. + * + * @return string + */ + protected function forkWorkers( $numProcs ) { + $this->prepareEnvironment(); + + // Create the child processes + for ( $i = 0; $i < $numProcs; $i++ ) { + // Do the fork + $pid = pcntl_fork(); + if ( $pid === -1 || $pid === false ) { + echo "Error creating child processes\n"; + exit( 1 ); + } + + if ( !$pid ) { + $this->initChild(); + return 'child'; + } else { + // This is the parent process + $this->children[$pid] = true; + } + } + + return 'parent'; } protected function initChild() { global $wgMemc, $wgMainCacheType; $wgMemc = wfGetCache( $wgMainCacheType ); + $this->children = null; + pcntl_signal( SIGTERM, SIG_DFL ); } protected function handleTermSignal( $signal ) {