MediaWiki
REL1_22
|
00001 <?php 00032 class ForkController { 00033 var $children = array(); 00034 var $termReceived = false; 00035 var $flags = 0, $procsToStart = 0; 00036 00037 static $restartableSignals = array( 00038 SIGFPE, 00039 SIGILL, 00040 SIGSEGV, 00041 SIGBUS, 00042 SIGABRT, 00043 SIGSYS, 00044 SIGPIPE, 00045 SIGXCPU, 00046 SIGXFSZ, 00047 ); 00048 00053 const RESTART_ON_ERROR = 1; 00054 00055 public function __construct( $numProcs, $flags = 0 ) { 00056 if ( PHP_SAPI != 'cli' ) { 00057 throw new MWException( "ForkController cannot be used from the web." ); 00058 } 00059 $this->procsToStart = $numProcs; 00060 $this->flags = $flags; 00061 } 00062 00074 public function start() { 00075 // Trap SIGTERM 00076 pcntl_signal( SIGTERM, array( $this, 'handleTermSignal' ), false ); 00077 00078 do { 00079 // Start child processes 00080 if ( $this->procsToStart ) { 00081 if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) { 00082 return 'child'; 00083 } 00084 $this->procsToStart = 0; 00085 } 00086 00087 // Check child status 00088 $status = false; 00089 $deadPid = pcntl_wait( $status ); 00090 00091 if ( $deadPid > 0 ) { 00092 // Respond to child process termination 00093 unset( $this->children[$deadPid] ); 00094 if ( $this->flags & self::RESTART_ON_ERROR ) { 00095 if ( pcntl_wifsignaled( $status ) ) { 00096 // Restart if the signal was abnormal termination 00097 // Don't restart if it was deliberately killed 00098 $signal = pcntl_wtermsig( $status ); 00099 if ( in_array( $signal, self::$restartableSignals ) ) { 00100 echo "Worker exited with signal $signal, restarting\n"; 00101 $this->procsToStart++; 00102 } 00103 } elseif ( pcntl_wifexited( $status ) ) { 00104 // Restart on non-zero exit status 00105 $exitStatus = pcntl_wexitstatus( $status ); 00106 if ( $exitStatus != 0 ) { 00107 echo "Worker exited with status $exitStatus, restarting\n"; 00108 $this->procsToStart++; 00109 } else { 00110 echo "Worker exited normally\n"; 00111 } 00112 } 00113 } 00114 // Throttle restarts 00115 if ( $this->procsToStart ) { 00116 usleep( 500000 ); 00117 } 00118 } 00119 00120 // Run signal handlers 00121 if ( function_exists( 'pcntl_signal_dispatch' ) ) { 00122 pcntl_signal_dispatch(); 00123 } else { 00124 declare( ticks = 1 ) { 00125 $status = $status; 00126 } 00127 } 00128 // Respond to TERM signal 00129 if ( $this->termReceived ) { 00130 foreach ( $this->children as $childPid => $unused ) { 00131 posix_kill( $childPid, SIGTERM ); 00132 } 00133 $this->termReceived = false; 00134 } 00135 } while ( count( $this->children ) ); 00136 pcntl_signal( SIGTERM, SIG_DFL ); 00137 return 'done'; 00138 } 00139 00140 protected function prepareEnvironment() { 00141 global $wgMemc; 00142 // Don't share DB, storage, or memcached connections 00143 wfGetLBFactory()->destroyInstance(); 00144 FileBackendGroup::destroySingleton(); 00145 LockManagerGroup::destroySingletons(); 00146 ObjectCache::clear(); 00147 $wgMemc = null; 00148 } 00149 00155 protected function forkWorkers( $numProcs ) { 00156 $this->prepareEnvironment(); 00157 00158 // Create the child processes 00159 for ( $i = 0; $i < $numProcs; $i++ ) { 00160 // Do the fork 00161 $pid = pcntl_fork(); 00162 if ( $pid === -1 || $pid === false ) { 00163 echo "Error creating child processes\n"; 00164 exit( 1 ); 00165 } 00166 00167 if ( !$pid ) { 00168 $this->initChild(); 00169 return 'child'; 00170 } else { 00171 // This is the parent process 00172 $this->children[$pid] = true; 00173 } 00174 } 00175 00176 return 'parent'; 00177 } 00178 00179 protected function initChild() { 00180 global $wgMemc, $wgMainCacheType; 00181 $wgMemc = wfGetCache( $wgMainCacheType ); 00182 $this->children = null; 00183 pcntl_signal( SIGTERM, SIG_DFL ); 00184 } 00185 00186 protected function handleTermSignal( $signal ) { 00187 $this->termReceived = true; 00188 } 00189 }