MediaWiki master
ForkController.php
Go to the documentation of this file.
1<?php
22
24use ObjectCache;
26use 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( 500_000 );
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
179 ObjectCache::clear();
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
225class_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.
Helper class to manage Redis connections.
static destroySingletons()
Destroy all singleton() instances.