MediaWiki
REL1_19
|
00001 <?php 00002 00012 class ForkController { 00013 var $children = array(); 00014 var $termReceived = false; 00015 var $flags = 0, $procsToStart = 0; 00016 00017 static $restartableSignals = array( 00018 SIGFPE, 00019 SIGILL, 00020 SIGSEGV, 00021 SIGBUS, 00022 SIGABRT, 00023 SIGSYS, 00024 SIGPIPE, 00025 SIGXCPU, 00026 SIGXFSZ, 00027 ); 00028 00033 const RESTART_ON_ERROR = 1; 00034 00035 public function __construct( $numProcs, $flags = 0 ) { 00036 if ( php_sapi_name() != 'cli' ) { 00037 throw new MWException( "ForkController cannot be used from the web." ); 00038 } 00039 $this->procsToStart = $numProcs; 00040 $this->flags = $flags; 00041 } 00042 00053 public function start() { 00054 // Trap SIGTERM 00055 pcntl_signal( SIGTERM, array( $this, 'handleTermSignal' ), false ); 00056 00057 do { 00058 // Start child processes 00059 if ( $this->procsToStart ) { 00060 if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) { 00061 return 'child'; 00062 } 00063 $this->procsToStart = 0; 00064 } 00065 00066 // Check child status 00067 $status = false; 00068 $deadPid = pcntl_wait( $status ); 00069 00070 if ( $deadPid > 0 ) { 00071 // Respond to child process termination 00072 unset( $this->children[$deadPid] ); 00073 if ( $this->flags & self::RESTART_ON_ERROR ) { 00074 if ( pcntl_wifsignaled( $status ) ) { 00075 // Restart if the signal was abnormal termination 00076 // Don't restart if it was deliberately killed 00077 $signal = pcntl_wtermsig( $status ); 00078 if ( in_array( $signal, self::$restartableSignals ) ) { 00079 echo "Worker exited with signal $signal, restarting\n"; 00080 $this->procsToStart++; 00081 } 00082 } elseif ( pcntl_wifexited( $status ) ) { 00083 // Restart on non-zero exit status 00084 $exitStatus = pcntl_wexitstatus( $status ); 00085 if ( $exitStatus != 0 ) { 00086 echo "Worker exited with status $exitStatus, restarting\n"; 00087 $this->procsToStart++; 00088 } else { 00089 echo "Worker exited normally\n"; 00090 } 00091 } 00092 } 00093 // Throttle restarts 00094 if ( $this->procsToStart ) { 00095 usleep( 500000 ); 00096 } 00097 } 00098 00099 // Run signal handlers 00100 if ( function_exists( 'pcntl_signal_dispatch' ) ) { 00101 pcntl_signal_dispatch(); 00102 } else { 00103 declare (ticks=1) { $status = $status; } 00104 } 00105 // Respond to TERM signal 00106 if ( $this->termReceived ) { 00107 foreach ( $this->children as $childPid => $unused ) { 00108 posix_kill( $childPid, SIGTERM ); 00109 } 00110 $this->termReceived = false; 00111 } 00112 } while ( count( $this->children ) ); 00113 pcntl_signal( SIGTERM, SIG_DFL ); 00114 return 'done'; 00115 } 00116 00117 protected function prepareEnvironment() { 00118 global $wgMemc; 00119 // Don't share DB or memcached connections 00120 wfGetLBFactory()->destroyInstance(); 00121 ObjectCache::clear(); 00122 $wgMemc = null; 00123 } 00124 00130 protected function forkWorkers( $numProcs ) { 00131 $this->prepareEnvironment(); 00132 00133 // Create the child processes 00134 for ( $i = 0; $i < $numProcs; $i++ ) { 00135 // Do the fork 00136 $pid = pcntl_fork(); 00137 if ( $pid === -1 || $pid === false ) { 00138 echo "Error creating child processes\n"; 00139 exit( 1 ); 00140 } 00141 00142 if ( !$pid ) { 00143 $this->initChild(); 00144 return 'child'; 00145 } else { 00146 // This is the parent process 00147 $this->children[$pid] = true; 00148 } 00149 } 00150 00151 return 'parent'; 00152 } 00153 00154 protected function initChild() { 00155 global $wgMemc, $wgMainCacheType; 00156 $wgMemc = wfGetCache( $wgMainCacheType ); 00157 $this->children = null; 00158 pcntl_signal( SIGTERM, SIG_DFL ); 00159 } 00160 00161 protected function handleTermSignal( $signal ) { 00162 $this->termReceived = true; 00163 } 00164 }