PageRenderTime 48ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/kernel/private/classes/ezpcontentpublishingqueueprocessor.php

https://github.com/eeggenberger/ezpublish
PHP | 269 lines | 180 code | 22 blank | 67 comment | 10 complexity | 05eb738d2ffa34ee0881c53f8c2e1ac4 MD5 | raw file
  1. <?php
  2. /**
  3. * File containing the ezpContentPublishingQueueProcessor class.
  4. *
  5. * @copyright Copyright (C) 1999-2011 eZ Systems AS. All rights reserved.
  6. * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
  7. * @version //autogentag//
  8. * @package kernel
  9. * @subpackage content
  10. */
  11. // required for proper signal handling
  12. declare( ticks=1 );
  13. /**
  14. * This class manages the publishing queue through ezpContentPublishingProcess persistent objects
  15. * @package kernel
  16. * @subpackage content
  17. */
  18. class ezpContentPublishingQueueProcessor
  19. {
  20. public function __construct()
  21. {
  22. $this->contentINI = eZINI::instance( 'content.ini' );
  23. $this->allowedPublishingSlots = $this->contentINI->variable( 'PublishingSettings', 'PublishingProcessSlots' );
  24. $this->sleepInterval = $this->contentINI->variable( 'PublishingSettings', 'AsynchronousPollingInterval' );
  25. // output to log by default
  26. $this->setOutput( new ezpAsynchronousPublisherLogOutput );
  27. // Queue reader handler
  28. $this->queueReader = $this->contentINI->variable( 'PublishingSettings', 'AsynchronousPublishingQueueReader' );
  29. $reflection = new ReflectionClass( $this->queueReader );
  30. if ( !$reflection->implementsInterface( 'ezpContentPublishingQueueReaderInterface' ) )
  31. throw new Exception( "Configured asynchronous publishing queue reader doesn't implement ezpContentPublishingQueueReaderInterface", __CLASS__ );
  32. call_user_func( array( $this->queueReader, 'init' ) );
  33. }
  34. /**
  35. * Singleton class loader
  36. * @return ezpContentPublishingQueueProcessor
  37. */
  38. public static function instance()
  39. {
  40. if ( !self::$instance instanceof ezpContentPublishingQueueProcessor )
  41. {
  42. self::$instance = new ezpContentPublishingQueueProcessor();
  43. // signal handler for children termination signal
  44. self::$instance->setSignalHandler();
  45. }
  46. return self::$instance;
  47. }
  48. /**
  49. * Checks if a publishing slot is available
  50. * @return ezpContentPublishingQueueProcessor
  51. */
  52. private function isSlotAvailable()
  53. {
  54. return ( ezpContentPublishingProcess::currentWorkingProcessCount() < $this->allowedPublishingSlots );
  55. }
  56. /**
  57. * Main method: infinite method that monitors queued objects, and starts
  58. * the publishinbg processes if allowed
  59. *
  60. * @return void
  61. */
  62. public function run()
  63. {
  64. // use DB exceptions so that errors can be fully handled
  65. eZDB::setErrorHandling( eZDB::ERROR_HANDLING_EXCEPTIONS );
  66. $this->cleanupDeadProcesses();
  67. while ( $this->canProcess )
  68. {
  69. try {
  70. /**
  71. * @var ezpContentPublishingProcess
  72. */
  73. $publishingItem = call_user_func( array( $this->queueReader, 'next' ) );
  74. if ( $publishingItem !== false )
  75. {
  76. if ( !$this->isSlotAvailable() )
  77. {
  78. $this->out->write( "No slot is available", 'async.log' );
  79. sleep ( 1 );
  80. continue;
  81. }
  82. else
  83. {
  84. $this->out->write( "Processing item #" . $publishingItem->attribute( 'ezcontentobject_version_id' ), 'async.log' );
  85. $pid = $publishingItem->publish();
  86. $this->currentJobs[$pid] = $publishingItem;
  87. // In the event that a signal for this pid was caught before we get here, it will be in our
  88. // signalQueue array
  89. // Process it now as if we'd just received the signal
  90. if( isset( $this->signalQueue[$pid] ) )
  91. {
  92. $this->out->write( "found $pid in the signal queue, processing it now" );
  93. $this->childSignalHandler( SIGCHLD, $pid, $this->signalQueue[$pid] );
  94. unset( $this->signalQueue[$pid] );
  95. }
  96. }
  97. }
  98. else
  99. {
  100. sleep( $this->sleepInterval );
  101. }
  102. } catch ( eZDBException $e ) {
  103. $this->out->write( "Database error #" . $e->getCode() . ": " . $e->getMessage() );
  104. // force the DB connection closed so that it is recreated
  105. try {
  106. $db = eZDB::instance();
  107. $db->close();
  108. $db = null;
  109. eZDB::setInstance( null );
  110. } catch( eZDBException $e ) {
  111. // Do nothing, this will be retried until the DB is back up
  112. }
  113. sleep( 1 );
  114. }
  115. }
  116. }
  117. /**
  118. * Checks WORKING processes, and removes from the queue those who are dead
  119. *
  120. * @return void
  121. */
  122. private function cleanupDeadProcesses()
  123. {
  124. $processes = ezpContentPublishingProcess::fetchProcesses( ezpContentPublishingProcess::STATUS_WORKING );
  125. foreach( $processes as $process )
  126. {
  127. if ( !$process->isAlive() )
  128. {
  129. $process->reset();
  130. }
  131. }
  132. }
  133. /**
  134. * Child process signal handler
  135. */
  136. public function childSignalHandler( $signo, $pid = null, $status = null )
  137. {
  138. // If no pid is provided, that means we're getting the signal from the system. Let's figure out
  139. // which child process ended
  140. if( $pid === null )
  141. {
  142. $pid = pcntl_waitpid( -1, $status, WNOHANG );
  143. }
  144. //Make sure we get all of the exited children
  145. while( $pid > 0 )
  146. {
  147. if( $pid && isset( $this->currentJobs[$pid] ) )
  148. {
  149. $exitCode = pcntl_wexitstatus( $status );
  150. if ( $exitCode != 0 )
  151. {
  152. $this->out->write( "Process #{$pid} of object version #".$this->currentJobs[$pid]->attribute( 'ezcontentobject_version_id' ) . " exited with status {$exitCode}" );
  153. // this is required as the MySQL connection might be closed anytime by a fork
  154. // this method is asynchronous, and might be triggered by any signal
  155. // the only way is to use a dedicated DB connection, and close it afterwards
  156. eZDB::setInstance( eZDB::instance( false, false, true ) );
  157. $this->currentJobs[$pid]->reset();
  158. eZDB::instance()->close();
  159. eZDB::setInstance( null );
  160. }
  161. unset( $this->currentJobs[$pid] );
  162. }
  163. // A job has finished before the parent process could even note that it had been launched
  164. // Let's make note of it and handle it when the parent process is ready for it
  165. // echo "..... Adding $pid to the signal queue ..... \n";
  166. elseif( $pid )
  167. {
  168. $this->signalQueue[$pid] = $status;
  169. }
  170. $pid = pcntl_waitpid( -1, $status, WNOHANG );
  171. }
  172. return true;
  173. }
  174. /**
  175. * Set the process signal handler
  176. */
  177. private function setSignalHandler()
  178. {
  179. pcntl_signal( SIGCHLD, array( $this, 'childSignalHandler' ) );
  180. }
  181. /**
  182. * Stops processing the queue, and cleanup what's currently running
  183. */
  184. public static function terminate()
  185. {
  186. self::instance()->_terminate();
  187. }
  188. private function _terminate()
  189. {
  190. $this->canProcess = false;
  191. if ( empty( $this->currentJobs ) )
  192. {
  193. $this->out->write( 'No waiting children, bye' );
  194. }
  195. while( !empty( $this->currentJobs ) )
  196. {
  197. $this->out->write( count( $this->currentJobs ) . ' jobs remaining' );
  198. sleep( 1 );
  199. }
  200. }
  201. /**
  202. * Sets the used output class
  203. *
  204. * @param ezpAsynchronousPublisherOutput $output
  205. * @return void
  206. */
  207. public function setOutput( ezpAsynchronousPublisherOutput $output )
  208. {
  209. if ( !$output instanceof ezpAsynchronousPublisherOutput )
  210. throw new Exception( "Invalid output handler" );
  211. $this->out = $output;
  212. }
  213. /**
  214. * @var eZINI
  215. */
  216. private $contentINI;
  217. /**
  218. * Allowed slots
  219. * @var int
  220. */
  221. private $allowedPublishingSlots = null;
  222. /**
  223. * Singleton instance of ezpContentPublishingQueueProcessor
  224. * @var ezpContentPublishingQueueProcessor
  225. */
  226. private static $instance = null;
  227. private $canProcess = true;
  228. /**
  229. * Currently running jobs
  230. * @var array
  231. */
  232. private $currentJobs = array();
  233. /**
  234. * Output manager
  235. * @var ezpAsynchronousPublisherOutput
  236. */
  237. private $out;
  238. }
  239. ?>