Cron.php 7.4 KB

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