Cron.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. namespace Drupal\Core;
  3. use Drupal\Component\Datetime\TimeInterface;
  4. use Drupal\Component\Utility\Timer;
  5. use Drupal\Core\Extension\ModuleHandlerInterface;
  6. use Drupal\Core\Queue\QueueWorkerManagerInterface;
  7. use Drupal\Core\Queue\RequeueException;
  8. use Drupal\Core\State\StateInterface;
  9. use Drupal\Core\Lock\LockBackendInterface;
  10. use Drupal\Core\Queue\QueueFactory;
  11. use Drupal\Core\Session\AnonymousUserSession;
  12. use Drupal\Core\Session\AccountSwitcherInterface;
  13. use Drupal\Core\Queue\SuspendQueueException;
  14. use Psr\Log\LoggerInterface;
  15. use Psr\Log\NullLogger;
  16. /**
  17. * The Drupal core Cron service.
  18. */
  19. class Cron implements CronInterface {
  20. /**
  21. * The module handler service.
  22. *
  23. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  24. */
  25. protected $moduleHandler;
  26. /**
  27. * The lock service.
  28. *
  29. * @var \Drupal\Core\Lock\LockBackendInterface
  30. */
  31. protected $lock;
  32. /**
  33. * The queue service.
  34. *
  35. * @var \Drupal\Core\Queue\QueueFactory
  36. */
  37. protected $queueFactory;
  38. /**
  39. * The state service.
  40. *
  41. * @var \Drupal\Core\State\StateInterface
  42. */
  43. protected $state;
  44. /**
  45. * The account switcher service.
  46. *
  47. * @var \Drupal\Core\Session\AccountSwitcherInterface
  48. */
  49. protected $accountSwitcher;
  50. /**
  51. * A logger instance.
  52. *
  53. * @var \Psr\Log\LoggerInterface
  54. */
  55. protected $logger;
  56. /**
  57. * The queue plugin manager.
  58. *
  59. * @var \Drupal\Core\Queue\QueueWorkerManagerInterface
  60. */
  61. protected $queueManager;
  62. /**
  63. * The time service.
  64. *
  65. * @var \Drupal\Component\Datetime\TimeInterface
  66. */
  67. protected $time;
  68. /**
  69. * Constructs a cron object.
  70. *
  71. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  72. * The module handler
  73. * @param \Drupal\Core\Lock\LockBackendInterface $lock
  74. * The lock service.
  75. * @param \Drupal\Core\Queue\QueueFactory $queue_factory
  76. * The queue service.
  77. * @param \Drupal\Core\State\StateInterface $state
  78. * The state service.
  79. * @param \Drupal\Core\Session\AccountSwitcherInterface $account_switcher
  80. * The account switching service.
  81. * @param \Psr\Log\LoggerInterface $logger
  82. * A logger instance.
  83. * @param \Drupal\Core\Queue\QueueWorkerManagerInterface $queue_manager
  84. * The queue plugin manager.
  85. * @param \Drupal\Component\Datetime\TimeInterface $time
  86. * The time service.
  87. */
  88. public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountSwitcherInterface $account_switcher, LoggerInterface $logger, QueueWorkerManagerInterface $queue_manager, TimeInterface $time = NULL) {
  89. $this->moduleHandler = $module_handler;
  90. $this->lock = $lock;
  91. $this->queueFactory = $queue_factory;
  92. $this->state = $state;
  93. $this->accountSwitcher = $account_switcher;
  94. $this->logger = $logger;
  95. $this->queueManager = $queue_manager;
  96. $this->time = $time ?: \Drupal::service('datetime.time');
  97. }
  98. /**
  99. * {@inheritdoc}
  100. */
  101. public function run() {
  102. // Allow execution to continue even if the request gets cancelled.
  103. @ignore_user_abort(TRUE);
  104. // Force the current user to anonymous to ensure consistent permissions on
  105. // cron runs.
  106. $this->accountSwitcher->switchTo(new AnonymousUserSession());
  107. // Try to allocate enough time to run all the hook_cron implementations.
  108. drupal_set_time_limit(240);
  109. $return = FALSE;
  110. // Try to acquire cron lock.
  111. if (!$this->lock->acquire('cron', 900.0)) {
  112. // Cron is still running normally.
  113. $this->logger->warning('Attempting to re-run cron while it is already running.');
  114. }
  115. else {
  116. $this->invokeCronHandlers();
  117. $this->setCronLastTime();
  118. // Release cron lock.
  119. $this->lock->release('cron');
  120. // Return TRUE so other functions can check if it did run successfully
  121. $return = TRUE;
  122. }
  123. // Process cron queues.
  124. $this->processQueues();
  125. // Restore the user.
  126. $this->accountSwitcher->switchBack();
  127. return $return;
  128. }
  129. /**
  130. * Records and logs the request time for this cron invocation.
  131. */
  132. protected function setCronLastTime() {
  133. // Record cron time.
  134. $request_time = $this->time->getRequestTime();
  135. $this->state->set('system.cron_last', $request_time);
  136. $this->logger->notice('Cron run completed.');
  137. }
  138. /**
  139. * Processes cron queues.
  140. */
  141. protected function processQueues() {
  142. // Grab the defined cron queues.
  143. foreach ($this->queueManager->getDefinitions() as $queue_name => $info) {
  144. if (isset($info['cron'])) {
  145. // Make sure every queue exists. There is no harm in trying to recreate
  146. // an existing queue.
  147. $this->queueFactory->get($queue_name)->createQueue();
  148. $queue_worker = $this->queueManager->createInstance($queue_name);
  149. $end = time() + (isset($info['cron']['time']) ? $info['cron']['time'] : 15);
  150. $queue = $this->queueFactory->get($queue_name);
  151. $lease_time = isset($info['cron']['time']) ?: NULL;
  152. while (time() < $end && ($item = $queue->claimItem($lease_time))) {
  153. try {
  154. $queue_worker->processItem($item->data);
  155. $queue->deleteItem($item);
  156. }
  157. catch (RequeueException $e) {
  158. // The worker requested the task be immediately requeued.
  159. $queue->releaseItem($item);
  160. }
  161. catch (SuspendQueueException $e) {
  162. // If the worker indicates there is a problem with the whole queue,
  163. // release the item and skip to the next queue.
  164. $queue->releaseItem($item);
  165. watchdog_exception('cron', $e);
  166. // Skip to the next queue.
  167. continue 2;
  168. }
  169. catch (\Exception $e) {
  170. // In case of any other kind of exception, log it and leave the item
  171. // in the queue to be processed again later.
  172. watchdog_exception('cron', $e);
  173. }
  174. }
  175. }
  176. }
  177. }
  178. /**
  179. * Invokes any cron handlers implementing hook_cron.
  180. */
  181. protected function invokeCronHandlers() {
  182. $module_previous = '';
  183. // If detailed logging isn't enabled, don't log individual execution times.
  184. $time_logging_enabled = \Drupal::config('system.cron')->get('logging');
  185. $logger = $time_logging_enabled ? $this->logger : new NullLogger();
  186. // Iterate through the modules calling their cron handlers (if any):
  187. foreach ($this->moduleHandler->getImplementations('cron') as $module) {
  188. if (!$module_previous) {
  189. $logger->notice('Starting execution of @module_cron().', [
  190. '@module' => $module,
  191. ]);
  192. }
  193. else {
  194. $logger->notice('Starting execution of @module_cron(), execution of @module_previous_cron() took @time.', [
  195. '@module' => $module,
  196. '@module_previous' => $module_previous,
  197. '@time' => Timer::read('cron_' . $module_previous) . 'ms',
  198. ]);
  199. }
  200. Timer::start('cron_' . $module);
  201. // Do not let an exception thrown by one module disturb another.
  202. try {
  203. $this->moduleHandler->invoke($module, 'cron');
  204. }
  205. catch (\Exception $e) {
  206. watchdog_exception('cron', $e);
  207. }
  208. Timer::stop('cron_' . $module);
  209. $module_previous = $module;
  210. }
  211. if ($module_previous) {
  212. $logger->notice('Execution of @module_previous_cron() took @time.', [
  213. '@module_previous' => $module_previous,
  214. '@time' => Timer::read('cron_' . $module_previous) . 'ms',
  215. ]);
  216. }
  217. }
  218. }