MediaWiki master
ForkController.php
Go to the documentation of this file.
1<?php
22
25use RuntimeException;
26
39 protected $children = [];
40
42 protected $childNumber = 0;
43
45 protected $termReceived = false;
46
48 protected $flags = 0;
49
51 protected $procsToStart = 0;
52
53 protected static $RESTARTABLE_SIGNALS = [];
54
59 private const RESTART_ON_ERROR = 1;
60
65 public function __construct( $numProcs, $flags = 0 ) {
66 if ( !wfIsCLI() ) {
67 throw new RuntimeException( "MediaWiki\Maintenance\ForkController cannot be used from the web." );
68 } elseif ( !extension_loaded( 'pcntl' ) ) {
69 throw new RuntimeException(
70 'MediaWiki\Maintenance\ForkController requires pcntl extension to be installed.'
71 );
72 } elseif ( !extension_loaded( 'posix' ) ) {
73 throw new RuntimeException(
74 'MediaWiki\Maintenance\ForkController requires posix extension to be installed.'
75 );
76 }
77 $this->procsToStart = $numProcs;
78 $this->flags = $flags;
79
80 // Define this only after confirming PCNTL support
81 self::$RESTARTABLE_SIGNALS = [
82 SIGFPE, SIGILL, SIGSEGV, SIGBUS,
83 SIGABRT, SIGSYS, SIGPIPE, SIGXCPU, SIGXFSZ,
84 ];
85 }
86
98 public function start() {
99 // Trap SIGTERM
100 pcntl_signal( SIGTERM, [ $this, 'handleTermSignal' ], false );
101
102 do {
103 // Start child processes
104 if ( $this->procsToStart ) {
105 if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) {
106 return 'child';
107 }
108 $this->procsToStart = 0;
109 }
110
111 // Check child status
112 $status = false;
113 $deadPid = pcntl_wait( $status );
114
115 if ( $deadPid > 0 ) {
116 // Respond to child process termination
117 unset( $this->children[$deadPid] );
118 if ( $this->flags & self::RESTART_ON_ERROR ) {
119 if ( pcntl_wifsignaled( $status ) ) {
120 // Restart if the signal was abnormal termination
121 // Don't restart if it was deliberately killed
122 $signal = pcntl_wtermsig( $status );
123 if ( in_array( $signal, self::$RESTARTABLE_SIGNALS ) ) {
124 echo "Worker exited with signal $signal, restarting\n";
125 $this->procsToStart++;
126 }
127 } elseif ( pcntl_wifexited( $status ) ) {
128 // Restart on non-zero exit status
129 $exitStatus = pcntl_wexitstatus( $status );
130 if ( $exitStatus != 0 ) {
131 echo "Worker exited with status $exitStatus, restarting\n";
132 $this->procsToStart++;
133 } else {
134 echo "Worker exited normally\n";
135 }
136 }
137 }
138 // Throttle restarts
139 if ( $this->procsToStart ) {
140 usleep( 500_000 );
141 }
142 }
143
144 // Run signal handlers
145 if ( function_exists( 'pcntl_signal_dispatch' ) ) {
146 pcntl_signal_dispatch();
147 } else {
148 declare( ticks=1 ) {
149 // @phan-suppress-next-line PhanPluginDuplicateExpressionAssignment
150 $status = $status;
151 }
152 }
153 // Respond to TERM signal
154 if ( $this->termReceived ) {
155 foreach ( $this->children as $childPid => $unused ) {
156 posix_kill( $childPid, SIGTERM );
157 }
158 $this->termReceived = false;
159 }
160 } while ( count( $this->children ) );
161 pcntl_signal( SIGTERM, SIG_DFL );
162 return 'done';
163 }
164
171 public function getChildNumber() {
172 return $this->childNumber;
173 }
174
175 protected function prepareEnvironment() {
176 // Don't share DB, storage, or memcached connections
178 MediaWikiServices::getInstance()->getObjectCacheFactory()->clear();
180 }
181
188 protected function forkWorkers( $numProcs ) {
189 $this->prepareEnvironment();
190
191 // Create the child processes
192 for ( $i = 0; $i < $numProcs; $i++ ) {
193 // Do the fork
194 $pid = pcntl_fork();
195 if ( $pid === -1 ) {
196 echo "Error creating child processes\n";
197 exit( 1 );
198 }
199
200 if ( !$pid ) {
201 $this->initChild();
202 $this->childNumber = $i;
203 return 'child';
204 } else {
205 // This is the parent process
206 $this->children[$pid] = true;
207 }
208 }
209
210 return 'parent';
211 }
212
213 protected function initChild() {
214 $this->children = null;
215 pcntl_signal( SIGTERM, SIG_DFL );
216 }
217
218 protected function handleTermSignal( $signal ) {
219 $this->termReceived = true;
220 }
221}
222
224class_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()...
static getInstance()
Returns the global default instance of the top level service locator.
Helper class to manage Redis connections.
static destroySingletons()
Destroy all singleton() instances.