MediaWiki  master
ForkController.php
Go to the documentation of this file.
1 <?php
23 
34  protected $children = [], $childNumber = 0;
35  protected $termReceived = false;
36  protected $flags = 0, $procsToStart = 0;
37 
38  protected static $restartableSignals = [
39  SIGFPE,
40  SIGILL,
41  SIGSEGV,
42  SIGBUS,
43  SIGABRT,
44  SIGSYS,
45  SIGPIPE,
46  SIGXCPU,
47  SIGXFSZ,
48  ];
49 
54  const RESTART_ON_ERROR = 1;
55 
56  public function __construct( $numProcs, $flags = 0 ) {
57  if ( !wfIsCLI() ) {
58  throw new MWException( "ForkController cannot be used from the web." );
59  }
60  $this->procsToStart = $numProcs;
61  $this->flags = $flags;
62  }
63 
75  public function start() {
76  // Trap SIGTERM
77  pcntl_signal( SIGTERM, [ $this, 'handleTermSignal' ], false );
78 
79  do {
80  // Start child processes
81  if ( $this->procsToStart ) {
82  if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) {
83  return 'child';
84  }
85  $this->procsToStart = 0;
86  }
87 
88  // Check child status
89  $status = false;
90  $deadPid = pcntl_wait( $status );
91 
92  if ( $deadPid > 0 ) {
93  // Respond to child process termination
94  unset( $this->children[$deadPid] );
95  if ( $this->flags & self::RESTART_ON_ERROR ) {
96  if ( pcntl_wifsignaled( $status ) ) {
97  // Restart if the signal was abnormal termination
98  // Don't restart if it was deliberately killed
99  $signal = pcntl_wtermsig( $status );
100  if ( in_array( $signal, self::$restartableSignals ) ) {
101  echo "Worker exited with signal $signal, restarting\n";
102  $this->procsToStart++;
103  }
104  } elseif ( pcntl_wifexited( $status ) ) {
105  // Restart on non-zero exit status
106  $exitStatus = pcntl_wexitstatus( $status );
107  if ( $exitStatus != 0 ) {
108  echo "Worker exited with status $exitStatus, restarting\n";
109  $this->procsToStart++;
110  } else {
111  echo "Worker exited normally\n";
112  }
113  }
114  }
115  // Throttle restarts
116  if ( $this->procsToStart ) {
117  usleep( 500000 );
118  }
119  }
120 
121  // Run signal handlers
122  if ( function_exists( 'pcntl_signal_dispatch' ) ) {
123  pcntl_signal_dispatch();
124  } else {
125  declare( ticks = 1 ) {
126  // @phan-suppress-next-line PhanPluginDuplicateExpressionAssignment
127  $status = $status;
128  }
129  }
130  // Respond to TERM signal
131  if ( $this->termReceived ) {
132  foreach ( $this->children as $childPid => $unused ) {
133  posix_kill( $childPid, SIGTERM );
134  }
135  $this->termReceived = false;
136  }
137  } while ( count( $this->children ) );
138  pcntl_signal( SIGTERM, SIG_DFL );
139  return 'done';
140  }
141 
148  public function getChildNumber() {
149  return $this->childNumber;
150  }
151 
152  protected function prepareEnvironment() {
153  global $wgMemc;
154  // Don't share DB, storage, or memcached connections
155  MediaWikiServices::resetChildProcessServices();
160  $wgMemc = null;
161  }
162 
169  protected function forkWorkers( $numProcs ) {
170  $this->prepareEnvironment();
171 
172  // Create the child processes
173  for ( $i = 0; $i < $numProcs; $i++ ) {
174  // Do the fork
175  $pid = pcntl_fork();
176  if ( $pid === -1 || $pid === false ) {
177  echo "Error creating child processes\n";
178  exit( 1 );
179  }
180 
181  if ( !$pid ) {
182  $this->initChild();
183  $this->childNumber = $i;
184  return 'child';
185  } else {
186  // This is the parent process
187  $this->children[$pid] = true;
188  }
189  }
190 
191  return 'parent';
192  }
193 
194  protected function initChild() {
195  global $wgMemc, $wgMainCacheType;
196  $wgMemc = wfGetCache( $wgMainCacheType );
197  $this->children = null;
198  pcntl_signal( SIGTERM, SIG_DFL );
199  }
200 
201  protected function handleTermSignal( $signal ) {
202  $this->termReceived = true;
203  }
204 }
forkWorkers( $numProcs)
Fork a number of worker processes.
wfGetCache( $cacheType)
Get a specific cache object.
const RESTART_ON_ERROR
Pass this flag to __construct() to cause the class to automatically restart workers that exit with no...
static destroySingletons()
Destroy all singleton() instances.
static destroySingleton()
Destroy the singleton instance.
$wgMemc
Definition: Setup.php:774
handleTermSignal( $signal)
start()
Start the child processes.
$wgMainCacheType
Main cache type.
Class for managing forking command line scripts.
getChildNumber()
Get the number of the child currently running.
wfIsCLI()
Check if we are running from the commandline.
static $restartableSignals
static clear()
Clear all the cached instances.
__construct( $numProcs, $flags=0)
static destroySingletons()
Destroy the singleton instances.