ForkController.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. <?php
  2. /**
  3. * Class for managing forking command line scripts.
  4. * Currently just does forking and process control, but it could easily be extended
  5. * to provide IPC and job dispatch.
  6. *
  7. * This class requires the posix and pcntl extensions.
  8. */
  9. class ForkController {
  10. var $children = array();
  11. var $termReceived = false;
  12. var $flags = 0, $procsToStart = 0;
  13. static $restartableSignals = array(
  14. SIGFPE,
  15. SIGILL,
  16. SIGSEGV,
  17. SIGBUS,
  18. SIGABRT,
  19. SIGSYS,
  20. SIGPIPE,
  21. SIGXCPU,
  22. SIGXFSZ,
  23. );
  24. /**
  25. * Pass this flag to __construct() to cause the class to automatically restart
  26. * workers that exit with non-zero exit status or a signal such as SIGSEGV.
  27. */
  28. const RESTART_ON_ERROR = 1;
  29. public function __construct( $numProcs, $flags = 0 ) {
  30. if ( php_sapi_name() != 'cli' ) {
  31. throw new MWException( "MultiProcess cannot be used from the web." );
  32. }
  33. $this->procsToStart = $numProcs;
  34. $this->flags = $flags;
  35. }
  36. /**
  37. * Start the child processes.
  38. *
  39. * This should only be called from the command line. It should be called
  40. * as early as possible during execution.
  41. *
  42. * This will return 'child' in the child processes. In the parent process,
  43. * it will run until all the child processes exit or a TERM signal is
  44. * received. It will then return 'done'.
  45. */
  46. public function start() {
  47. // Trap SIGTERM
  48. pcntl_signal( SIGTERM, array( $this, 'handleTermSignal' ), false );
  49. do {
  50. // Start child processes
  51. if ( $this->procsToStart ) {
  52. if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) {
  53. return 'child';
  54. }
  55. $this->procsToStart = 0;
  56. }
  57. // Check child status
  58. $status = false;
  59. $deadPid = pcntl_wait( $status );
  60. if ( $deadPid > 0 ) {
  61. // Respond to child process termination
  62. unset( $this->children[$deadPid] );
  63. if ( $this->flags & self::RESTART_ON_ERROR ) {
  64. if ( pcntl_wifsignaled( $status ) ) {
  65. // Restart if the signal was abnormal termination
  66. // Don't restart if it was deliberately killed
  67. $signal = pcntl_wtermsig( $status );
  68. if ( in_array( $signal, self::$restartableSignals ) ) {
  69. echo "Worker exited with signal $signal, restarting\n";
  70. $this->procsToStart++;
  71. }
  72. } elseif ( pcntl_wifexited( $status ) ) {
  73. // Restart on non-zero exit status
  74. $exitStatus = pcntl_wexitstatus( $status );
  75. if ( $exitStatus > 0 ) {
  76. echo "Worker exited with status $exitStatus, restarting\n";
  77. $this->procsToStart++;
  78. }
  79. }
  80. }
  81. // Throttle restarts
  82. if ( $this->procsToStart ) {
  83. usleep( 500000 );
  84. }
  85. }
  86. // Run signal handlers
  87. if ( function_exists( 'pcntl_signal_dispatch' ) ) {
  88. pcntl_signal_dispatch();
  89. } else {
  90. declare (ticks=1) { $status = $status; }
  91. }
  92. // Respond to TERM signal
  93. if ( $this->termReceived ) {
  94. foreach ( $this->children as $childPid => $unused ) {
  95. posix_kill( $childPid, SIGTERM );
  96. }
  97. $this->termReceived = false;
  98. }
  99. } while ( count( $this->children ) );
  100. pcntl_signal( SIGTERM, SIG_DFL );
  101. return 'done';
  102. }
  103. protected function prepareEnvironment() {
  104. global $wgCaches, $wgMemc;
  105. // Don't share DB or memcached connections
  106. wfGetLBFactory()->destroyInstance();
  107. $wgCaches = array();
  108. unset( $wgMemc );
  109. }
  110. /**
  111. * Fork a number of worker processes.
  112. */
  113. protected function forkWorkers( $numProcs ) {
  114. global $wgMemc, $wgCaches, $wgMainCacheType;
  115. $this->prepareEnvironment();
  116. // Create the child processes
  117. for ( $i = 0; $i < $numProcs; $i++ ) {
  118. // Do the fork
  119. $pid = pcntl_fork();
  120. if ( $pid === -1 || $pid === false ) {
  121. echo "Error creating child processes\n";
  122. exit( 1 );
  123. }
  124. if ( !$pid ) {
  125. $this->initChild();
  126. return 'child';
  127. } else {
  128. // This is the parent process
  129. $this->children[$pid] = true;
  130. }
  131. }
  132. return 'parent';
  133. }
  134. protected function initChild() {
  135. global $wgMemc, $wgMainCacheType;
  136. $wgMemc = wfGetCache( $wgMainCacheType );
  137. $this->children = null;
  138. pcntl_signal( SIGTERM, SIG_DFL );
  139. }
  140. protected function handleTermSignal( $signal ) {
  141. $this->termReceived = true;
  142. }
  143. }