MediaWiki  master
ForkController.php
Go to the documentation of this file.
1 <?php
22 
24 use ObjectCache;
26 use RuntimeException;
27 
40  protected $children = [];
41 
43  protected $childNumber = 0;
44 
46  protected $termReceived = false;
47 
49  protected $flags = 0;
50 
52  protected $procsToStart = 0;
53 
54  protected static $RESTARTABLE_SIGNALS = [];
55 
60  private const RESTART_ON_ERROR = 1;
61 
66  public function __construct( $numProcs, $flags = 0 ) {
67  if ( !wfIsCLI() ) {
68  throw new RuntimeException( "MediaWiki\Maintenance\ForkController cannot be used from the web." );
69  } elseif ( !extension_loaded( 'pcntl' ) ) {
70  throw new RuntimeException(
71  'MediaWiki\Maintenance\ForkController requires pcntl extension to be installed.'
72  );
73  } elseif ( !extension_loaded( 'posix' ) ) {
74  throw new RuntimeException(
75  'MediaWiki\Maintenance\ForkController requires posix extension to be installed.'
76  );
77  }
78  $this->procsToStart = $numProcs;
79  $this->flags = $flags;
80 
81  // Define this only after confirming PCNTL support
82  self::$RESTARTABLE_SIGNALS = [
83  SIGFPE, SIGILL, SIGSEGV, SIGBUS,
84  SIGABRT, SIGSYS, SIGPIPE, SIGXCPU, SIGXFSZ,
85  ];
86  }
87 
99  public function start() {
100  // Trap SIGTERM
101  pcntl_signal( SIGTERM, [ $this, 'handleTermSignal' ], false );
102 
103  do {
104  // Start child processes
105  if ( $this->procsToStart ) {
106  if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) {
107  return 'child';
108  }
109  $this->procsToStart = 0;
110  }
111 
112  // Check child status
113  $status = false;
114  $deadPid = pcntl_wait( $status );
115 
116  if ( $deadPid > 0 ) {
117  // Respond to child process termination
118  unset( $this->children[$deadPid] );
119  if ( $this->flags & self::RESTART_ON_ERROR ) {
120  if ( pcntl_wifsignaled( $status ) ) {
121  // Restart if the signal was abnormal termination
122  // Don't restart if it was deliberately killed
123  $signal = pcntl_wtermsig( $status );
124  if ( in_array( $signal, self::$RESTARTABLE_SIGNALS ) ) {
125  echo "Worker exited with signal $signal, restarting\n";
126  $this->procsToStart++;
127  }
128  } elseif ( pcntl_wifexited( $status ) ) {
129  // Restart on non-zero exit status
130  $exitStatus = pcntl_wexitstatus( $status );
131  if ( $exitStatus != 0 ) {
132  echo "Worker exited with status $exitStatus, restarting\n";
133  $this->procsToStart++;
134  } else {
135  echo "Worker exited normally\n";
136  }
137  }
138  }
139  // Throttle restarts
140  if ( $this->procsToStart ) {
141  usleep( 500000 );
142  }
143  }
144 
145  // Run signal handlers
146  if ( function_exists( 'pcntl_signal_dispatch' ) ) {
147  pcntl_signal_dispatch();
148  } else {
149  declare( ticks=1 ) {
150  // @phan-suppress-next-line PhanPluginDuplicateExpressionAssignment
151  $status = $status;
152  }
153  }
154  // Respond to TERM signal
155  if ( $this->termReceived ) {
156  foreach ( $this->children as $childPid => $unused ) {
157  posix_kill( $childPid, SIGTERM );
158  }
159  $this->termReceived = false;
160  }
161  } while ( count( $this->children ) );
162  pcntl_signal( SIGTERM, SIG_DFL );
163  return 'done';
164  }
165 
172  public function getChildNumber() {
173  return $this->childNumber;
174  }
175 
176  protected function prepareEnvironment() {
177  // Don't share DB, storage, or memcached connections
181  }
182 
189  protected function forkWorkers( $numProcs ) {
190  $this->prepareEnvironment();
191 
192  // Create the child processes
193  for ( $i = 0; $i < $numProcs; $i++ ) {
194  // Do the fork
195  $pid = pcntl_fork();
196  if ( $pid === -1 ) {
197  echo "Error creating child processes\n";
198  exit( 1 );
199  }
200 
201  if ( !$pid ) {
202  $this->initChild();
203  $this->childNumber = $i;
204  return 'child';
205  } else {
206  // This is the parent process
207  $this->children[$pid] = true;
208  }
209  }
210 
211  return 'parent';
212  }
213 
214  protected function initChild() {
215  $this->children = null;
216  pcntl_signal( SIGTERM, SIG_DFL );
217  }
218 
219  protected function handleTermSignal( $signal ) {
220  $this->termReceived = true;
221  }
222 }
223 
227 class_alias( ForkController::class, 'ForkController' );
wfIsCLI()
Check if we are running from the commandline.
Manage forking inside CLI maintenance scripts.
start()
Start the child processes.
getChildNumber()
Get the number of the child currently running.
forkWorkers( $numProcs)
Fork a number of worker processes.
Service locator for MediaWiki core services.
static resetChildProcessServices()
Resets any services that may have become stale after a child processö returns from after pcntl_fork()...
Functions to get cache objects.
Definition: ObjectCache.php:67
static clear()
Clear all the cached instances.
Helper class to manage Redis connections.
static destroySingletons()
Destroy all singleton() instances.