r47852 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r47851‎ | r47852 | r47853 >
Date:04:18, 27 February 2009
Author:tstarling
Status:ok
Tags:
Comment:
* Added restart-on-error feature to ForkController, changed the interface to suit.
* Close some sockets before pcntl_exec() in an attempt to delay file handle exhaustion.
Modified paths:
  • /trunk/phase3/includes/ForkController.php (modified) (history)
  • /trunk/phase3/maintenance/gearman/gearman.inc (modified) (history)
  • /trunk/phase3/maintenance/gearman/gearmanWorker.php (modified) (history)
  • /trunk/phase3/maintenance/runJobs.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/runJobs.php
@@ -20,9 +20,8 @@
2121 echo "Invalid argument to --procs\n";
2222 exit( 1 );
2323 }
24 - $fc = new ForkController;
25 - if ( $fc->forkWorkers( $procs ) == 'parent' ) {
26 - $fc->runParent();
 24+ $fc = new ForkController( $procs );
 25+ if ( $fc->start( $procs ) != 'child' ) {
2726 exit( 0 );
2827 }
2928 }
Index: trunk/phase3/maintenance/gearman/gearman.inc
@@ -11,6 +11,11 @@
1212 $this->complete( array( 'result' => true ) );
1313 socket_close( $this->conn );
1414
 15+ # Close some more sockets
 16+ wfGetLBFactory()->shutdown();
 17+ global $wgMemc;
 18+ $wgMemc->disconnect_all();
 19+
1520 # Find PHP
1621 $php = readlink( '/proc/' . posix_getpid() . '/exe' );
1722
Index: trunk/phase3/maintenance/gearman/gearmanWorker.php
@@ -10,9 +10,8 @@
1111 echo "Invalid number of processes, please specify a number between 1 and 1000\n";
1212 exit( 1 );
1313 }
14 - $fc = new ForkController;
15 - if ( $fc->forkWorkers( $procs ) == 'parent' ) {
16 - $fc->runParent();
 14+ $fc = new ForkController( $procs, ForkController::RESTART_ON_ERROR );
 15+ if ( $fc->start() != 'child' ) {
1716 exit( 0 );
1817 }
1918 }
Index: trunk/phase3/includes/ForkController.php
@@ -4,17 +4,112 @@
55 * Class for managing forking command line scripts.
66 * Currently just does forking and process control, but it could easily be extended
77 * to provide IPC and job dispatch.
 8+ *
 9+ * This class requires the posix and pcntl extensions.
810 */
911 class ForkController {
1012 var $children = array();
1113 var $termReceived = false;
 14+ var $flags = 0, $procsToStart = 0;
1215
13 - public function __construct() {
 16+ static $restartableSignals = array(
 17+ SIGFPE,
 18+ SIGILL,
 19+ SIGSEGV,
 20+ SIGBUS,
 21+ SIGABRT,
 22+ SIGSYS,
 23+ SIGPIPE,
 24+ SIGXCPU,
 25+ SIGXFSZ,
 26+ );
 27+
 28+ /**
 29+ * Pass this flag to __construct() to cause the class to automatically restart
 30+ * workers that exit with non-zero exit status or a signal such as SIGSEGV.
 31+ */
 32+ const RESTART_ON_ERROR = 1;
 33+
 34+ public function __construct( $numProcs, $flags = 0 ) {
1435 if ( php_sapi_name() != 'cli' ) {
1536 throw new MWException( "MultiProcess cannot be used from the web." );
1637 }
 38+ $this->procsToStart = $numProcs;
 39+ $this->flags = $flags;
1740 }
1841
 42+ /**
 43+ * Start the child processes.
 44+ *
 45+ * This should only be called from the command line. It should be called
 46+ * as early as possible during execution.
 47+ *
 48+ * This will return 'child' in the child processes. In the parent process,
 49+ * it will run until all the child processes exit or a TERM signal is
 50+ * received. It will then return 'done'.
 51+ */
 52+ public function start() {
 53+ // Trap SIGTERM
 54+ pcntl_signal( SIGTERM, array( $this, 'handleTermSignal' ), false );
 55+
 56+ do {
 57+ // Start child processes
 58+ if ( $this->procsToStart ) {
 59+ if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) {
 60+ return 'child';
 61+ }
 62+ $this->procsToStart = 0;
 63+ }
 64+
 65+ // Check child status
 66+ $status = false;
 67+ $deadPid = pcntl_wait( $status );
 68+
 69+ if ( $deadPid > 0 ) {
 70+ // Respond to child process termination
 71+ unset( $this->children[$deadPid] );
 72+ if ( $this->flags & self::RESTART_ON_ERROR ) {
 73+ if ( pcntl_wifsignaled( $status ) ) {
 74+ // Restart if the signal was abnormal termination
 75+ // Don't restart if it was deliberately killed
 76+ $signal = pcntl_wtermsig( $status );
 77+ if ( in_array( $signal, self::$restartableSignals ) ) {
 78+ echo "Worker exited with signal $signal, restarting\n";
 79+ $this->procsToStart++;
 80+ }
 81+ } elseif ( pcntl_wifexited( $status ) ) {
 82+ // Restart on non-zero exit status
 83+ $exitStatus = pcntl_wexitstatus( $status );
 84+ if ( $exitStatus > 0 ) {
 85+ echo "Worker exited with status $exitStatus, restarting\n";
 86+ $this->procsToStart++;
 87+ }
 88+ }
 89+ }
 90+ // Throttle restarts
 91+ if ( $this->procsToStart ) {
 92+ usleep( 500000 );
 93+ }
 94+ }
 95+
 96+ // Run signal handlers
 97+ if ( function_exists( 'pcntl_signal_dispatch' ) ) {
 98+ pcntl_signal_dispatch();
 99+ } else {
 100+ declare (ticks=1) { $status = $status; }
 101+ }
 102+ // Respond to TERM signal
 103+ if ( $this->termReceived ) {
 104+ foreach ( $this->children as $childPid => $unused ) {
 105+ posix_kill( $childPid, SIGTERM );
 106+ }
 107+ $this->termReceived = false;
 108+ }
 109+ } while ( count( $this->children ) );
 110+ pcntl_signal( SIGTERM, SIG_DFL );
 111+ return 'done';
 112+ }
 113+
19114 protected function prepareEnvironment() {
20115 global $wgCaches, $wgMemc;
21116 // Don't share DB or memcached connections
@@ -25,15 +120,8 @@
26121
27122 /**
28123 * Fork a number of worker processes.
29 - *
30 - * This should only be called from the command line. It should be called
31 - * as early as possible during execution. It will return 'child' in the
32 - * child processes and 'parent' in the parent process. The parent process
33 - * should then call monitor().
34 - *
35 - * This function requires the posix and pcntl extensions.
36124 */
37 - public function forkWorkers( $numProcs ) {
 125+ protected function forkWorkers( $numProcs ) {
38126 global $wgMemc, $wgCaches, $wgMainCacheType;
39127
40128 $this->prepareEnvironment();
@@ -49,7 +137,6 @@
50138
51139 if ( !$pid ) {
52140 $this->initChild();
53 - $this->children = null;
54141 return 'child';
55142 } else {
56143 // This is the parent process
@@ -60,41 +147,10 @@
61148 return 'parent';
62149 }
63150
64 - /**
65 - * The parent process main loop
66 - */
67 - public function runParent() {
68 - // Trap SIGTERM
69 - pcntl_signal( SIGTERM, array( $this, 'handleTermSignal' ), false );
70 -
71 - do {
72 - $status = false;
73 - $deadPid = pcntl_wait( $status );
74 -
75 - if ( $deadPid > 0 ) {
76 - unset( $this->children[$deadPid] );
77 - }
78 -
79 - // Run signal handlers
80 - if ( function_exists( 'pcntl_signal_dispatch' ) ) {
81 - pcntl_signal_dispatch();
82 - } else {
83 - declare (ticks=1) { $status = $status; }
84 - }
85 - // Respond to TERM signal
86 - if ( $this->termReceived ) {
87 - foreach ( $this->children as $childPid => $unused ) {
88 - posix_kill( $childPid, SIGTERM );
89 - }
90 - $this->termReceived = false;
91 - }
92 - } while ( count( $this->children ) );
93 - pcntl_signal( SIGTERM, SIG_DFL );
94 - }
95 -
96151 protected function initChild() {
97152 global $wgMemc, $wgMainCacheType;
98153 $wgMemc = wfGetCache( $wgMainCacheType );
 154+ $this->children = null;
99155 }
100156
101157 protected function handleTermSignal( $signal ) {

Follow-up revisions

RevisionCommit summaryAuthorDate
r80129Follow up r47852. Unlike forkWorkers(), the start() method doesn't get the $p...platonides23:07, 12 January 2011

Status & tagging log