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();
172  }
173 
180  protected function forkWorkers( $numProcs ) {
181  $this->prepareEnvironment();
182 
183  // Create the child processes
184  for ( $i = 0; $i < $numProcs; $i++ ) {
185  // Do the fork
186  $pid = pcntl_fork();
187  if ( $pid === -1 || $pid === false ) {
188  echo "Error creating child processes\n";
189  exit( 1 );
190  }
191 
192  if ( !$pid ) {
193  $this->initChild();
194  $this->childNumber = $i;
195  return 'child';
196  } else {
197  // This is the parent process
198  $this->children[$pid] = true;
199  }
200  }
201 
202  return 'parent';
203  }
204 
205  protected function initChild() {
206  $this->children = null;
207  pcntl_signal( SIGTERM, SIG_DFL );
208  }
209 
210  protected function handleTermSignal( $signal ) {
211  $this->termReceived = true;
212  }
213 }
wfIsCLI()
Check if we are running from the commandline.
Class for managing forking command line scripts.
__construct( $numProcs, $flags=0)
getChildNumber()
Get the number of the child currently running.
start()
Start the child processes.
array null $children
forkWorkers( $numProcs)
Fork a number of worker processes.
static $RESTARTABLE_SIGNALS
handleTermSignal( $signal)
MediaWiki exception.
Definition: MWException.php:29
Service locator for MediaWiki core services.
static clear()
Clear all the cached instances.
static destroySingletons()
Destroy all singleton() instances.