Cron.php 7.37 KB
Newer Older
1 2 3 4
<?php

namespace Drupal\Core;

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 12
use Drupal\Core\Lock\LockBackendInterface;
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;
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

/**
 * The Drupal core Cron service.
 */
class Cron implements CronInterface {

  /**
   * The module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The lock service.
   *
   * @var \Drupal\Core\Lock\LockBackendInterface
   */
  protected $lock;

  /**
   * The queue service.
   *
   * @var \Drupal\Core\Queue\QueueFactory
   */
  protected $queueFactory;

  /**
   * The state service.
   *
48
   * @var \Drupal\Core\State\StateInterface
49 50 51
   */
  protected $state;

52
  /**
53
   * The account switcher service.
54
   *
55
   * @var \Drupal\Core\Session\AccountSwitcherInterface
56
   */
57
  protected $accountSwitcher;
58

59 60 61 62 63 64 65
  /**
   * A logger instance.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

66 67 68 69 70 71 72
  /**
   * The queue plugin manager.
   *
   * @var \Drupal\Core\Queue\QueueWorkerManagerInterface
   */
  protected $queueManager;

73 74 75 76 77 78 79
  /**
   * The time service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $time;

80 81 82 83 84 85 86 87 88
  /**
   * Constructs a cron object.
   *
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler
   * @param \Drupal\Core\Lock\LockBackendInterface $lock
   *   The lock service.
   * @param \Drupal\Core\Queue\QueueFactory $queue_factory
   *   The queue service.
89
   * @param \Drupal\Core\State\StateInterface $state
90
   *   The state service.
91
   * @param \Drupal\Core\Session\AccountSwitcherInterface $account_switcher
92
   *   The account switching service.
93 94
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
95
   * @param \Drupal\Core\Queue\QueueWorkerManagerInterface $queue_manager
96
   *   The queue plugin manager.
97 98
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time service.
99
   */
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 102 103 104
    $this->moduleHandler = $module_handler;
    $this->lock = $lock;
    $this->queueFactory = $queue_factory;
    $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');
109 110 111 112 113 114 115 116 117 118 119
  }

  /**
   * {@inheritdoc}
   */
  public function run() {
    // Allow execution to continue even if the request gets cancelled.
    @ignore_user_abort(TRUE);

    // Force the current user to anonymous to ensure consistent permissions on
    // cron runs.
120
    $this->accountSwitcher->switchTo(new AnonymousUserSession());
121 122 123 124 125 126 127

    // Try to allocate enough time to run all the hook_cron implementations.
    drupal_set_time_limit(240);

    $return = FALSE;

    // 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.');
131 132
    }
    else {
133 134
      $this->invokeCronHandlers();
      $this->setCronLastTime();
135 136 137 138 139 140 141 142

      // Release cron lock.
      $this->lock->release('cron');

      // Return TRUE so other functions can check if it did run successfully
      $return = TRUE;
    }

143 144 145 146
    // Process cron queues.
    $this->processQueues();

    // Restore the user.
147
    $this->accountSwitcher->switchBack();
148 149 150 151 152 153 154 155 156

    return $return;
  }

  /**
   * Records and logs the request time for this cron invocation.
   */
  protected function setCronLastTime() {
    // Record cron time.
157 158
    $request_time = $this->time->getRequestTime();
    $this->state->set('system.cron_last', $request_time);
159
    $this->logger->notice('Cron run completed.');
160 161 162 163 164 165 166
  }

  /**
   * Processes cron queues.
   */
  protected function processQueues() {
    // Grab the defined cron queues.
167
    foreach ($this->queueManager->getDefinitions() as $queue_name => $info) {
168
      if (isset($info['cron'])) {
169 170 171 172
        // Make sure every queue exists. There is no harm in trying to recreate
        // an existing queue.
        $this->queueFactory->get($queue_name)->createQueue();

173
        $queue_worker = $this->queueManager->createInstance($queue_name);
174 175
        $end = time() + (isset($info['cron']['time']) ? $info['cron']['time'] : 15);
        $queue = $this->queueFactory->get($queue_name);
176 177
        $lease_time = isset($info['cron']['time']) ?: NULL;
        while (time() < $end && ($item = $queue->claimItem($lease_time))) {
178
          try {
179
            $queue_worker->processItem($item->data);
180 181
            $queue->deleteItem($item);
          }
182 183 184 185
          catch (RequeueException $e) {
            // The worker requested the task be immediately requeued.
            $queue->releaseItem($item);
          }
186 187 188 189 190 191 192 193 194 195
          catch (SuspendQueueException $e) {
            // If the worker indicates there is a problem with the whole queue,
            // release the item and skip to the next queue.
            $queue->releaseItem($item);

            watchdog_exception('cron', $e);

            // Skip to the next queue.
            continue 2;
          }
196
          catch (\Exception $e) {
197 198
            // In case of any other kind of exception, log it and leave the item
            // in the queue to be processed again later.
199 200 201 202 203
            watchdog_exception('cron', $e);
          }
        }
      }
    }
204
  }
205

206 207 208 209
  /**
   * Invokes any cron handlers implementing hook_cron.
   */
  protected function invokeCronHandlers() {
210 211
    $module_previous = '';

212 213 214 215
    // If detailed logging isn't enabled, don't log individual execution times.
    $time_logging_enabled = \Drupal::config('system.cron')->get('logging');
    $logger = $time_logging_enabled ? $this->logger : new NullLogger();

216 217
    // Iterate through the modules calling their cron handlers (if any):
    foreach ($this->moduleHandler->getImplementations('cron') as $module) {
218 219

      if (!$module_previous) {
220
        $logger->notice('Starting execution of @module_cron().', [
221 222 223 224
          '@module' => $module,
        ]);
      }
      else {
225
        $logger->notice('Starting execution of @module_cron(), execution of @module_previous_cron() took @time.', [
226 227 228 229 230 231 232
          '@module' => $module,
          '@module_previous' => $module_previous,
          '@time' => Timer::read('cron_' . $module_previous) . 'ms',
        ]);
      }
      Timer::start('cron_' . $module);

233 234 235 236 237 238 239
      // Do not let an exception thrown by one module disturb another.
      try {
        $this->moduleHandler->invoke($module, 'cron');
      }
      catch (\Exception $e) {
        watchdog_exception('cron', $e);
      }
240 241 242

      Timer::stop('cron_' . $module);
      $module_previous = $module;
243
    }
244
    if ($module_previous) {
245
      $logger->notice('Execution of @module_previous_cron() took @time.', [
246 247 248 249
        '@module_previous' => $module_previous,
        '@time' => Timer::read('cron_' . $module_previous) . 'ms',
      ]);
    }
250 251 252
  }

}