Index: trunk/phase3/maintenance/runJobs.php |
— | — | @@ -20,9 +20,8 @@ |
21 | 21 | echo "Invalid argument to --procs\n"; |
22 | 22 | exit( 1 ); |
23 | 23 | } |
24 | | - $fc = new ForkController; |
25 | | - if ( $fc->forkWorkers( $procs ) == 'parent' ) { |
26 | | - $fc->runParent(); |
| 24 | + $fc = new ForkController( $procs ); |
| 25 | + if ( $fc->start( $procs ) != 'child' ) { |
27 | 26 | exit( 0 ); |
28 | 27 | } |
29 | 28 | } |
Index: trunk/phase3/maintenance/gearman/gearman.inc |
— | — | @@ -11,6 +11,11 @@ |
12 | 12 | $this->complete( array( 'result' => true ) ); |
13 | 13 | socket_close( $this->conn ); |
14 | 14 | |
| 15 | + # Close some more sockets |
| 16 | + wfGetLBFactory()->shutdown(); |
| 17 | + global $wgMemc; |
| 18 | + $wgMemc->disconnect_all(); |
| 19 | + |
15 | 20 | # Find PHP |
16 | 21 | $php = readlink( '/proc/' . posix_getpid() . '/exe' ); |
17 | 22 | |
Index: trunk/phase3/maintenance/gearman/gearmanWorker.php |
— | — | @@ -10,9 +10,8 @@ |
11 | 11 | echo "Invalid number of processes, please specify a number between 1 and 1000\n"; |
12 | 12 | exit( 1 ); |
13 | 13 | } |
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' ) { |
17 | 16 | exit( 0 ); |
18 | 17 | } |
19 | 18 | } |
Index: trunk/phase3/includes/ForkController.php |
— | — | @@ -4,17 +4,112 @@ |
5 | 5 | * Class for managing forking command line scripts. |
6 | 6 | * Currently just does forking and process control, but it could easily be extended |
7 | 7 | * to provide IPC and job dispatch. |
| 8 | + * |
| 9 | + * This class requires the posix and pcntl extensions. |
8 | 10 | */ |
9 | 11 | class ForkController { |
10 | 12 | var $children = array(); |
11 | 13 | var $termReceived = false; |
| 14 | + var $flags = 0, $procsToStart = 0; |
12 | 15 | |
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 ) { |
14 | 35 | if ( php_sapi_name() != 'cli' ) { |
15 | 36 | throw new MWException( "MultiProcess cannot be used from the web." ); |
16 | 37 | } |
| 38 | + $this->procsToStart = $numProcs; |
| 39 | + $this->flags = $flags; |
17 | 40 | } |
18 | 41 | |
| 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 | + |
19 | 114 | protected function prepareEnvironment() { |
20 | 115 | global $wgCaches, $wgMemc; |
21 | 116 | // Don't share DB or memcached connections |
— | — | @@ -25,15 +120,8 @@ |
26 | 121 | |
27 | 122 | /** |
28 | 123 | * 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. |
36 | 124 | */ |
37 | | - public function forkWorkers( $numProcs ) { |
| 125 | + protected function forkWorkers( $numProcs ) { |
38 | 126 | global $wgMemc, $wgCaches, $wgMainCacheType; |
39 | 127 | |
40 | 128 | $this->prepareEnvironment(); |
— | — | @@ -49,7 +137,6 @@ |
50 | 138 | |
51 | 139 | if ( !$pid ) { |
52 | 140 | $this->initChild(); |
53 | | - $this->children = null; |
54 | 141 | return 'child'; |
55 | 142 | } else { |
56 | 143 | // This is the parent process |
— | — | @@ -60,41 +147,10 @@ |
61 | 148 | return 'parent'; |
62 | 149 | } |
63 | 150 | |
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 | | - |
96 | 151 | protected function initChild() { |
97 | 152 | global $wgMemc, $wgMainCacheType; |
98 | 153 | $wgMemc = wfGetCache( $wgMainCacheType ); |
| 154 | + $this->children = null; |
99 | 155 | } |
100 | 156 | |
101 | 157 | protected function handleTermSignal( $signal ) { |