5 use Drupal\Component\Datetime\TimeInterface;
6 use Drupal\Component\Utility\Timer;
7 use Drupal\Core\Extension\ModuleHandlerInterface;
8 use Drupal\Core\Queue\QueueWorkerManagerInterface;
9 use Drupal\Core\Queue\RequeueException;
10 use Drupal\Core\State\StateInterface;
11 use Drupal\Core\Lock\LockBackendInterface;
12 use Drupal\Core\Queue\QueueFactory;
13 use Drupal\Core\Session\AnonymousUserSession;
14 use Drupal\Core\Session\AccountSwitcherInterface;
15 use Drupal\Core\Queue\SuspendQueueException;
16 use Psr\Log\LoggerInterface;
17 use Psr\Log\NullLogger;
20 * The Drupal core Cron service.
22 class Cron implements CronInterface {
25 * The module handler service.
27 * @var \Drupal\Core\Extension\ModuleHandlerInterface
29 protected $moduleHandler;
34 * @var \Drupal\Core\Lock\LockBackendInterface
41 * @var \Drupal\Core\Queue\QueueFactory
43 protected $queueFactory;
48 * @var \Drupal\Core\State\StateInterface
53 * The account switcher service.
55 * @var \Drupal\Core\Session\AccountSwitcherInterface
57 protected $accountSwitcher;
62 * @var \Psr\Log\LoggerInterface
67 * The queue plugin manager.
69 * @var \Drupal\Core\Queue\QueueWorkerManagerInterface
71 protected $queueManager;
76 * @var \Drupal\Component\Datetime\TimeInterface
81 * Constructs a cron object.
83 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
85 * @param \Drupal\Core\Lock\LockBackendInterface $lock
87 * @param \Drupal\Core\Queue\QueueFactory $queue_factory
89 * @param \Drupal\Core\State\StateInterface $state
91 * @param \Drupal\Core\Session\AccountSwitcherInterface $account_switcher
92 * The account switching service.
93 * @param \Psr\Log\LoggerInterface $logger
95 * @param \Drupal\Core\Queue\QueueWorkerManagerInterface $queue_manager
96 * The queue plugin manager.
97 * @param \Drupal\Component\Datetime\TimeInterface $time
100 public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountSwitcherInterface $account_switcher, LoggerInterface $logger, QueueWorkerManagerInterface $queue_manager, TimeInterface $time = NULL) {
101 $this->moduleHandler = $module_handler;
103 $this->queueFactory = $queue_factory;
104 $this->state = $state;
105 $this->accountSwitcher = $account_switcher;
106 $this->logger = $logger;
107 $this->queueManager = $queue_manager;
108 $this->time = $time ?: \Drupal::service('datetime.time');
114 public function run() {
115 // Allow execution to continue even if the request gets cancelled.
116 @ignore_user_abort(TRUE);
118 // Force the current user to anonymous to ensure consistent permissions on
120 $this->accountSwitcher->switchTo(new AnonymousUserSession());
122 // Try to allocate enough time to run all the hook_cron implementations.
123 drupal_set_time_limit(240);
127 // Try to acquire cron lock.
128 if (!$this->lock->acquire('cron', 900.0)) {
129 // Cron is still running normally.
130 $this->logger->warning('Attempting to re-run cron while it is already running.');
133 $this->invokeCronHandlers();
134 $this->setCronLastTime();
136 // Release cron lock.
137 $this->lock->release('cron');
139 // Return TRUE so other functions can check if it did run successfully
143 // Process cron queues.
144 $this->processQueues();
147 $this->accountSwitcher->switchBack();
153 * Records and logs the request time for this cron invocation.
155 protected function setCronLastTime() {
157 $request_time = $this->time->getRequestTime();
158 $this->state->set('system.cron_last', $request_time);
159 $this->logger->notice('Cron run completed.');
163 * Processes cron queues.
165 protected function processQueues() {
166 // Grab the defined cron queues.
167 foreach ($this->queueManager->getDefinitions() as $queue_name => $info) {
168 if (isset($info['cron'])) {
169 // Make sure every queue exists. There is no harm in trying to recreate
170 // an existing queue.
171 $this->queueFactory->get($queue_name)->createQueue();
173 $queue_worker = $this->queueManager->createInstance($queue_name);
174 $end = time() + (isset($info['cron']['time']) ? $info['cron']['time'] : 15);
175 $queue = $this->queueFactory->get($queue_name);
176 $lease_time = isset($info['cron']['time']) ?: NULL;
177 while (time() < $end && ($item = $queue->claimItem($lease_time))) {
179 $queue_worker->processItem($item->data);
180 $queue->deleteItem($item);
182 catch (RequeueException $e) {
183 // The worker requested the task be immediately requeued.
184 $queue->releaseItem($item);
186 catch (SuspendQueueException $e) {
187 // If the worker indicates there is a problem with the whole queue,
188 // release the item and skip to the next queue.
189 $queue->releaseItem($item);
191 watchdog_exception('cron', $e);
193 // Skip to the next queue.
196 catch (\Exception $e) {
197 // In case of any other kind of exception, log it and leave the item
198 // in the queue to be processed again later.
199 watchdog_exception('cron', $e);
207 * Invokes any cron handlers implementing hook_cron.
209 protected function invokeCronHandlers() {
210 $module_previous = '';
212 // If detailed logging isn't enabled, don't log individual execution times.
213 $time_logging_enabled = \Drupal::config('system.cron')->get('logging');
214 $logger = $time_logging_enabled ? $this->logger : new NullLogger();
216 // Iterate through the modules calling their cron handlers (if any):
217 foreach ($this->moduleHandler->getImplementations('cron') as $module) {
219 if (!$module_previous) {
220 $logger->notice('Starting execution of @module_cron().', [
221 '@module' => $module,
225 $logger->notice('Starting execution of @module_cron(), execution of @module_previous_cron() took @time.', [
226 '@module' => $module,
227 '@module_previous' => $module_previous,
228 '@time' => Timer::read('cron_' . $module_previous) . 'ms',
231 Timer::start('cron_' . $module);
233 // Do not let an exception thrown by one module disturb another.
235 $this->moduleHandler->invoke($module, 'cron');
237 catch (\Exception $e) {
238 watchdog_exception('cron', $e);
241 Timer::stop('cron_' . $module);
242 $module_previous = $module;
244 if ($module_previous) {
245 $logger->notice('Execution of @module_previous_cron() took @time.', [
246 '@module_previous' => $module_previous,
247 '@time' => Timer::read('cron_' . $module_previous) . 'ms',