4 * Class for managing forking command line scripts.
5 * Currently just does forking and process control, but it could easily be extended
6 * to provide IPC and job dispatch.
8 * This class requires the posix and pcntl extensions.
10 * @ingroup Maintenance
12 class ForkController
{
13 var $children = array();
14 var $termReceived = false;
15 var $flags = 0, $procsToStart = 0;
17 static $restartableSignals = array(
30 * Pass this flag to __construct() to cause the class to automatically restart
31 * workers that exit with non-zero exit status or a signal such as SIGSEGV.
33 const RESTART_ON_ERROR
= 1;
35 public function __construct( $numProcs, $flags = 0 ) {
36 if ( php_sapi_name() != 'cli' ) {
37 throw new MWException( "ForkController cannot be used from the web." );
39 $this->procsToStart
= $numProcs;
40 $this->flags
= $flags;
44 * Start the child processes.
46 * This should only be called from the command line. It should be called
47 * as early as possible during execution.
49 * This will return 'child' in the child processes. In the parent process,
50 * it will run until all the child processes exit or a TERM signal is
51 * received. It will then return 'done'.
54 public function start() {
56 pcntl_signal( SIGTERM
, array( $this, 'handleTermSignal' ), false );
59 // Start child processes
60 if ( $this->procsToStart
) {
61 if ( $this->forkWorkers( $this->procsToStart
) == 'child' ) {
64 $this->procsToStart
= 0;
69 $deadPid = pcntl_wait( $status );
72 // Respond to child process termination
73 unset( $this->children
[$deadPid] );
74 if ( $this->flags
& self
::RESTART_ON_ERROR
) {
75 if ( pcntl_wifsignaled( $status ) ) {
76 // Restart if the signal was abnormal termination
77 // Don't restart if it was deliberately killed
78 $signal = pcntl_wtermsig( $status );
79 if ( in_array( $signal, self
::$restartableSignals ) ) {
80 echo "Worker exited with signal $signal, restarting\n";
81 $this->procsToStart++
;
83 } elseif ( pcntl_wifexited( $status ) ) {
84 // Restart on non-zero exit status
85 $exitStatus = pcntl_wexitstatus( $status );
86 if ( $exitStatus != 0 ) {
87 echo "Worker exited with status $exitStatus, restarting\n";
88 $this->procsToStart++
;
90 echo "Worker exited normally\n";
95 if ( $this->procsToStart
) {
100 // Run signal handlers
101 if ( function_exists( 'pcntl_signal_dispatch' ) ) {
102 pcntl_signal_dispatch();
104 declare (ticks
=1) { $status = $status; }
106 // Respond to TERM signal
107 if ( $this->termReceived
) {
108 foreach ( $this->children
as $childPid => $unused ) {
109 posix_kill( $childPid, SIGTERM
);
111 $this->termReceived
= false;
113 } while ( count( $this->children
) );
114 pcntl_signal( SIGTERM
, SIG_DFL
);
118 protected function prepareEnvironment() {
120 // Don't share DB, storage, or memcached connections
121 wfGetLBFactory()->destroyInstance();
122 FileBackendGroup
::destroySingleton();
123 LockManagerGroup
::destroySingleton();
124 ObjectCache
::clear();
129 * Fork a number of worker processes.
133 protected function forkWorkers( $numProcs ) {
134 $this->prepareEnvironment();
136 // Create the child processes
137 for ( $i = 0; $i < $numProcs; $i++
) {
140 if ( $pid === -1 ||
$pid === false ) {
141 echo "Error creating child processes\n";
149 // This is the parent process
150 $this->children
[$pid] = true;
157 protected function initChild() {
158 global $wgMemc, $wgMainCacheType;
159 $wgMemc = wfGetCache( $wgMainCacheType );
160 $this->children
= null;
161 pcntl_signal( SIGTERM
, SIG_DFL
);
164 protected function handleTermSignal( $signal ) {
165 $this->termReceived
= true;