MediaWiki  master
ForkController.php
Go to the documentation of this file.
1 <?php
23 
35  protected $children = [];
36 
38  protected $childNumber = 0;
39 
41  protected $termReceived = false;
42 
44  protected $flags = 0;
45 
47  protected $procsToStart = 0;
48 
49  protected static $RESTARTABLE_SIGNALS = [];
50 
55  private const RESTART_ON_ERROR = 1;
56 
61  public function __construct( $numProcs, $flags = 0 ) {
62  if ( !wfIsCLI() ) {
63  throw new MWException( "ForkController cannot be used from the web." );
64  } elseif ( !extension_loaded( 'pcntl' ) ) {
65  throw new MWException( 'ForkController requires pcntl extension to be installed.' );
66  } elseif ( !extension_loaded( 'posix' ) ) {
67  throw new MWException( 'ForkController requires posix extension to be installed.' );
68  }
69  $this->procsToStart = $numProcs;
70  $this->flags = $flags;
71 
72  // Define this only after confirming PCNTL support
73  self::$RESTARTABLE_SIGNALS = [
74  SIGFPE, SIGILL, SIGSEGV, SIGBUS,
75  SIGABRT, SIGSYS, SIGPIPE, SIGXCPU,SIGXFSZ,
76  ];
77  }
78 
90  public function start() {
91  // Trap SIGTERM
92  pcntl_signal( SIGTERM, [ $this, 'handleTermSignal' ], false );
93 
94  do {
95  // Start child processes
96  if ( $this->procsToStart ) {
97  if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) {
98  return 'child';
99  }
100  $this->procsToStart = 0;
101  }
102 
103  // Check child status
104  $status = false;
105  $deadPid = pcntl_wait( $status );
106 
107  if ( $deadPid > 0 ) {
108  // Respond to child process termination
109  unset( $this->children[$deadPid] );
110  if ( $this->flags & self::RESTART_ON_ERROR ) {
111  if ( pcntl_wifsignaled( $status ) ) {
112  // Restart if the signal was abnormal termination
113  // Don't restart if it was deliberately killed
114  $signal = pcntl_wtermsig( $status );
115  if ( in_array( $signal, self::$RESTARTABLE_SIGNALS ) ) {
116  echo "Worker exited with signal $signal, restarting\n";
117  $this->procsToStart++;
118  }
119  } elseif ( pcntl_wifexited( $status ) ) {
120  // Restart on non-zero exit status
121  $exitStatus = pcntl_wexitstatus( $status );
122  if ( $exitStatus != 0 ) {
123  echo "Worker exited with status $exitStatus, restarting\n";
124  $this->procsToStart++;
125  } else {
126  echo "Worker exited normally\n";
127  }
128  }
129  }
130  // Throttle restarts
131  if ( $this->procsToStart ) {
132  usleep( 500000 );
133  }
134  }
135 
136  // Run signal handlers
137  if ( function_exists( 'pcntl_signal_dispatch' ) ) {
138  pcntl_signal_dispatch();
139  } else {
140  declare( ticks = 1 ) {
141  // @phan-suppress-next-line PhanPluginDuplicateExpressionAssignment
142  $status = $status;
143  }
144  }
145  // Respond to TERM signal
146  if ( $this->termReceived ) {
147  foreach ( $this->children as $childPid => $unused ) {
148  posix_kill( $childPid, SIGTERM );
149  }
150  $this->termReceived = false;
151  }
152  } while ( count( $this->children ) );
153  pcntl_signal( SIGTERM, SIG_DFL );
154  return 'done';
155  }
156 
163  public function getChildNumber() {
164  return $this->childNumber;
165  }
166 
167  protected function prepareEnvironment() {
168  // Don't share DB, storage, or memcached connections
169  MediaWikiServices::resetChildProcessServices();
173  }
174 
181  protected function forkWorkers( $numProcs ) {
182  $this->prepareEnvironment();
183 
184  // Create the child processes
185  for ( $i = 0; $i < $numProcs; $i++ ) {
186  // Do the fork
187  $pid = pcntl_fork();
188  if ( $pid === -1 || $pid === false ) {
189  echo "Error creating child processes\n";
190  exit( 1 );
191  }
192 
193  if ( !$pid ) {
194  $this->initChild();
195  $this->childNumber = $i;
196  return 'child';
197  } else {
198  // This is the parent process
199  $this->children[$pid] = true;
200  }
201  }
202 
203  return 'parent';
204  }
205 
206  protected function initChild() {
207  $this->children = null;
208  pcntl_signal( SIGTERM, SIG_DFL );
209  }
210 
211  protected function handleTermSignal( $signal ) {
212  $this->termReceived = true;
213  }
214 }
ObjectCache\clear
static clear()
Clear all the cached instances.
Definition: ObjectCache.php:282
ForkController\RESTART_ON_ERROR
const RESTART_ON_ERROR
Pass this flag to __construct() to cause the class to automatically restart workers that exit with no...
Definition: ForkController.php:55
ForkController
Class for managing forking command line scripts.
Definition: ForkController.php:33
ForkController\$procsToStart
int $procsToStart
Definition: ForkController.php:47
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:202
ForkController\handleTermSignal
handleTermSignal( $signal)
Definition: ForkController.php:211
JobQueueGroup\destroySingletons
static destroySingletons()
Destroy the singleton instances.
Definition: JobQueueGroup.php:124
RedisConnectionPool\destroySingletons
static destroySingletons()
Destroy all singleton() instances.
Definition: RedisConnectionPool.php:166
ForkController\start
start()
Start the child processes.
Definition: ForkController.php:90
ForkController\getChildNumber
getChildNumber()
Get the number of the child currently running.
Definition: ForkController.php:163
ForkController\forkWorkers
forkWorkers( $numProcs)
Fork a number of worker processes.
Definition: ForkController.php:181
MWException
MediaWiki exception.
Definition: MWException.php:29
ForkController\$RESTARTABLE_SIGNALS
static $RESTARTABLE_SIGNALS
Definition: ForkController.php:49
ForkController\$children
array null $children
Definition: ForkController.php:35
ForkController\$flags
int $flags
Definition: ForkController.php:44
wfIsCLI
wfIsCLI()
Check if we are running from the commandline.
Definition: GlobalFunctions.php:1713
ForkController\$childNumber
int $childNumber
Definition: ForkController.php:38
ForkController\prepareEnvironment
prepareEnvironment()
Definition: ForkController.php:167
ForkController\$termReceived
bool $termReceived
Definition: ForkController.php:41
ForkController\__construct
__construct( $numProcs, $flags=0)
Definition: ForkController.php:61
ForkController\initChild
initChild()
Definition: ForkController.php:206