From ac0cee8f4577b0eacb48af6b45c7f9b2b30d7c1e Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Tue, 23 Sep 2014 10:47:47 +0100 Subject: [PATCH] Issue #2038275 by tim.plunkett, mr.baileys, Sam Hermans: Convert hook_queue_info() to plugin system. --- core/core.services.yml | 5 +- .../Drupal/Core/Annotation/QueueWorker.php | 65 ++++++++++ core/lib/Drupal/Core/Cron.php | 22 +++- core/lib/Drupal/Core/Queue/QueueFactory.php | 2 +- .../lib/Drupal/Core/Queue/QueueWorkerBase.php | 22 ++++ .../Core/Queue/QueueWorkerInterface.php | 46 +++++++ .../Drupal/Core/Queue/QueueWorkerManager.php | 65 ++++++++++ .../Queue/QueueWorkerManagerInterface.php | 17 +++ core/modules/aggregator/aggregator.module | 17 --- .../Plugin/QueueWorker/AggregatorRefresh.php | 31 +++++ .../src/Tests/AggregatorCronTest.php | 10 +- core/modules/locale/locale.module | 74 +---------- .../Plugin/QueueWorker/LocaleTranslation.php | 122 ++++++++++++++++++ core/modules/system/system.api.php | 76 +---------- .../cron_queue_test/cron_queue_test.module | 38 ------ .../QueueWorker/CronQueueTestBrokenQueue.php | 32 +++++ .../QueueWorker/CronQueueTestException.php | 28 ++++ 17 files changed, 459 insertions(+), 213 deletions(-) create mode 100644 core/lib/Drupal/Core/Annotation/QueueWorker.php create mode 100644 core/lib/Drupal/Core/Queue/QueueWorkerBase.php create mode 100644 core/lib/Drupal/Core/Queue/QueueWorkerInterface.php create mode 100644 core/lib/Drupal/Core/Queue/QueueWorkerManager.php create mode 100644 core/lib/Drupal/Core/Queue/QueueWorkerManagerInterface.php create mode 100644 core/modules/aggregator/src/Plugin/QueueWorker/AggregatorRefresh.php create mode 100644 core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php delete mode 100644 core/modules/system/tests/modules/cron_queue_test/cron_queue_test.module create mode 100644 core/modules/system/tests/modules/cron_queue_test/src/Plugin/QueueWorker/CronQueueTestBrokenQueue.php create mode 100644 core/modules/system/tests/modules/cron_queue_test/src/Plugin/QueueWorker/CronQueueTestException.php diff --git a/core/core.services.yml b/core/core.services.yml index d3f91ee028bb..f5ed5db9d743 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -162,7 +162,7 @@ services: arguments: ['@typed_data_manager'] cron: class: Drupal\Core\Cron - arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user', '@session_manager', '@logger.channel.cron'] + arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user', '@session_manager', '@logger.channel.cron', '@plugin.manager.queue_worker'] diff.formatter: class: Drupal\Core\Diff\DiffFormatter arguments: ['@config.factory'] @@ -353,6 +353,9 @@ services: plugin.manager.display_variant: class: Drupal\Core\Display\VariantManager parent: default_plugin_manager + plugin.manager.queue_worker: + class: Drupal\Core\Queue\QueueWorkerManager + parent: default_plugin_manager plugin.cache_clearer: class: Drupal\Core\Plugin\CachedDiscoveryClearer paramconverter.menu_link: diff --git a/core/lib/Drupal/Core/Annotation/QueueWorker.php b/core/lib/Drupal/Core/Annotation/QueueWorker.php new file mode 100644 index 000000000000..85a2296fd19d --- /dev/null +++ b/core/lib/Drupal/Core/Annotation/QueueWorker.php @@ -0,0 +1,65 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Annotation\QueueWorker. + */ + +namespace Drupal\Core\Annotation; + +use Drupal\Component\Annotation\Plugin; + +/** + * Declare queue workers that need to be run periodically. + * + * While there can be only one hook_cron() process running at the same time, + * there can be any number of processes defined here running. Because of + * this, long running tasks are much better suited for this API. Items queued + * in hook_cron() might be processed in the same cron run if there are not many + * items in the queue, otherwise it might take several requests, which can be + * run in parallel. + * + * You can create queues, add items to them, claim them, etc. without using a + * QueueWorker plugin if you want, however, you need to take care of processing + * the items in the queue in that case. See \Drupal\Core\Cron for an example. + * + * Plugin Namespace: Plugin\QueueWorker + * + * For a working example, see + * \Drupal\aggregator\Plugin\QueueWorker\AggregatorRefresh. + * + * @see \Drupal\Core\Queue\QueueWorkerInterface + * @see \Drupal\Core\Queue\QueueWorkerBase + * @see \Drupal\Core\Queue\QueueWorkerManager + * @see plugin_api + * + * @Annotation + */ +class QueueWorker extends Plugin { + + /** + * The plugin ID. + * + * @var string + */ + public $id; + + /** + * The human-readable title of the plugin. + * + * @ingroup plugin_translatable + * + * @var \Drupal\Core\Annotation\Translation + */ + public $title; + + /** + * An associative array containing the optional key: + * - time: (optional) How much time Drupal cron should spend on calling + * this worker in seconds. Defaults to 15. + * + * @var array (optional) + */ + public $cron; + +} diff --git a/core/lib/Drupal/Core/Cron.php b/core/lib/Drupal/Core/Cron.php index 969f5ec3b7c5..bc256f8e4ff7 100644 --- a/core/lib/Drupal/Core/Cron.php +++ b/core/lib/Drupal/Core/Cron.php @@ -8,6 +8,7 @@ namespace Drupal\Core; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Queue\QueueWorkerManagerInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Lock\LockBackendInterface; use Drupal\Core\Queue\QueueFactory; @@ -71,6 +72,13 @@ class Cron implements CronInterface { */ protected $logger; + /** + * The queue plugin manager. + * + * @var \Drupal\Core\Queue\QueueWorkerManagerInterface + */ + protected $queueManager; + /** * Constructs a cron object. * @@ -88,8 +96,10 @@ class Cron implements CronInterface { * The session manager. * @param \Psr\Log\LoggerInterface $logger * A logger instance. + * @param \Drupal\Core\Queue\QueueWorkerManagerInterface + * The queue plugin manager. */ - public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountProxyInterface $current_user, SessionManagerInterface $session_manager, LoggerInterface $logger) { + public function __construct(ModuleHandlerInterface $module_handler, LockBackendInterface $lock, QueueFactory $queue_factory, StateInterface $state, AccountProxyInterface $current_user, SessionManagerInterface $session_manager, LoggerInterface $logger, QueueWorkerManagerInterface $queue_manager) { $this->moduleHandler = $module_handler; $this->lock = $lock; $this->queueFactory = $queue_factory; @@ -97,6 +107,7 @@ public function __construct(ModuleHandlerInterface $module_handler, LockBackendI $this->currentUser = $current_user; $this->sessionManager = $session_manager; $this->logger = $logger; + $this->queueManager = $queue_manager; } /** @@ -162,21 +173,18 @@ protected function setCronLastTime() { */ protected function processQueues() { // Grab the defined cron queues. - $queues = $this->moduleHandler->invokeAll('queue_info'); - $this->moduleHandler->alter('queue_info', $queues); - - foreach ($queues as $queue_name => $info) { + foreach ($this->queueManager->getDefinitions() as $queue_name => $info) { if (isset($info['cron'])) { // Make sure every queue exists. There is no harm in trying to recreate // an existing queue. $this->queueFactory->get($queue_name)->createQueue(); - $callback = $info['worker callback']; + $queue_worker = $this->queueManager->createInstance($queue_name); $end = time() + (isset($info['cron']['time']) ? $info['cron']['time'] : 15); $queue = $this->queueFactory->get($queue_name); while (time() < $end && ($item = $queue->claimItem())) { try { - call_user_func_array($callback, array($item->data)); + $queue_worker->processItem($item->data); $queue->deleteItem($item); } catch (SuspendQueueException $e) { diff --git a/core/lib/Drupal/Core/Queue/QueueFactory.php b/core/lib/Drupal/Core/Queue/QueueFactory.php index 6dce837991d8..70824c31c7d6 100644 --- a/core/lib/Drupal/Core/Queue/QueueFactory.php +++ b/core/lib/Drupal/Core/Queue/QueueFactory.php @@ -50,7 +50,7 @@ function __construct(Settings $settings) { * least once is important, FALSE if scalability is the main concern. Defaults * to FALSE. * - * @return \Drupal\Core\QueueStore\QueueInterface + * @return \Drupal\Core\Queue\QueueInterface * A queue implementation for the given name. */ public function get($name, $reliable = FALSE) { diff --git a/core/lib/Drupal/Core/Queue/QueueWorkerBase.php b/core/lib/Drupal/Core/Queue/QueueWorkerBase.php new file mode 100644 index 000000000000..7fe3417113b8 --- /dev/null +++ b/core/lib/Drupal/Core/Queue/QueueWorkerBase.php @@ -0,0 +1,22 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Queue\QueueWorkerBase. + */ + +namespace Drupal\Core\Queue; + +use Drupal\Component\Plugin\PluginBase; + +/** + * Provides a base implementation for a QueueWorker plugin. + * + * @see \Drupal\Core\Queue\QueueWorkerInterface + * @see \Drupal\Core\Queue\QueueWorkerManager + * @see \Drupal\Core\Annotation\QueueWorker + * @see plugin_api + */ +abstract class QueueWorkerBase extends PluginBase implements QueueWorkerInterface { + +} diff --git a/core/lib/Drupal/Core/Queue/QueueWorkerInterface.php b/core/lib/Drupal/Core/Queue/QueueWorkerInterface.php new file mode 100644 index 000000000000..be6e3357e605 --- /dev/null +++ b/core/lib/Drupal/Core/Queue/QueueWorkerInterface.php @@ -0,0 +1,46 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Queue\QueueWorkerInterface. + */ + +namespace Drupal\Core\Queue; + +use Drupal\Component\Plugin\PluginInspectionInterface; + +/** + * Defines an interface for a QueueWorker plugin. + * + * @see \Drupal\Core\Queue\QueueWorkerBase + * @see \Drupal\Core\Queue\QueueWorkerManager + * @see \Drupal\Core\Annotation\QueueWorker + * @see plugin_api + */ +interface QueueWorkerInterface extends PluginInspectionInterface { + + /** + * Works on a single queue item. + * + * @param mixed $data + * The data that was passed to + * \Drupal\Core\Queue\QueueInterface::createItem() when the item was queued. + * + * @throws \Exception + * A QueueWorker plugin may throw an exception to indicate there was a + * problem. The cron process will log the exception, and leave the item in + * the queue to be processed again later. + * @throws \Drupal\Core\Queue\SuspendQueueException + * More specifically, a SuspendQueueException should be thrown when a + * QueueWorker plugin is aware that the problem will affect all subsequent + * workers of its queue. For example, a callback that makes HTTP requests + * may find that the remote server is not responding. The cron process will + * behave as with a normal Exception, and in addition will not attempt to + * process further items from the current item's queue during the current + * cron run. + * + * @see \Drupal\Core\Cron::processQueues() + */ + public function processItem($data); + +} diff --git a/core/lib/Drupal/Core/Queue/QueueWorkerManager.php b/core/lib/Drupal/Core/Queue/QueueWorkerManager.php new file mode 100644 index 000000000000..6339065e572a --- /dev/null +++ b/core/lib/Drupal/Core/Queue/QueueWorkerManager.php @@ -0,0 +1,65 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Queue\QueueWorkerManager. + */ + +namespace Drupal\Core\Queue; + +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\DefaultPluginManager; + +/** + * Defines the queue worker manager. + * + * @see \Drupal\Core\Queue\QueueWorkerInterface + * @see \Drupal\Core\Queue\QueueWorkerBase + * @see \Drupal\Core\Annotation\QueueWorker + * @see plugin_api + */ +class QueueWorkerManager extends DefaultPluginManager implements QueueWorkerManagerInterface { + + /** + * Constructs an QueueWorkerManager object. + * + * @param \Traversable $namespaces + * An object that implements \Traversable which contains the root paths + * keyed by the corresponding namespace to look for plugin implementations. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + */ + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + parent::__construct('Plugin/QueueWorker', $namespaces, $module_handler, 'Drupal\Core\Queue\QueueWorkerInterface', 'Drupal\Core\Annotation\QueueWorker'); + + $this->setCacheBackend($cache_backend, 'queue_plugins'); + $this->alterInfo('queue_info'); + } + + /** + * {@inheritdoc} + */ + public function processDefinition(&$definition, $plugin_id) { + parent::processDefinition($definition, $plugin_id); + + // Assign a default time if a cron is specified. + if (isset($definition['cron'])) { + $definition['cron'] += [ + 'time' => 15, + ]; + } + } + + /** + * {@inheritdoc} + * + * @return \Drupal\Core\Queue\QueueWorkerInterface + */ + public function createInstance($plugin_id, array $configuration = []) { + return parent::createInstance($plugin_id, $configuration); + } + +} diff --git a/core/lib/Drupal/Core/Queue/QueueWorkerManagerInterface.php b/core/lib/Drupal/Core/Queue/QueueWorkerManagerInterface.php new file mode 100644 index 000000000000..e80776ba5d1f --- /dev/null +++ b/core/lib/Drupal/Core/Queue/QueueWorkerManagerInterface.php @@ -0,0 +1,17 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Queue\QueueWorkerManagerInterface. + */ + +namespace Drupal\Core\Queue; + +use Drupal\Component\Plugin\PluginManagerInterface; + +/** + * Provides an interface for a queue worker manager. + */ +interface QueueWorkerManagerInterface extends PluginManagerInterface { + +} diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index 1cd23c0ead94..f4ce31e5237f 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -6,7 +6,6 @@ */ use Drupal\aggregator\Entity\Feed; -use Drupal\aggregator\FeedInterface; use Drupal\Component\Utility\Xss; use Drupal\Core\Routing\RouteMatchInterface; @@ -161,22 +160,6 @@ function aggregator_cron() { } } -/** - * Implements hook_queue_info(). - */ -function aggregator_queue_info() { - $queues['aggregator_feeds'] = array( - 'title' => t('Aggregator refresh'), - 'worker callback' => function (FeedInterface $feed) { - $feed->refreshItems(); - }, - 'cron' => array( - 'time' => 60, - ), - ); - return $queues; -} - /** * Renders the HTML content safely, as allowed. * diff --git a/core/modules/aggregator/src/Plugin/QueueWorker/AggregatorRefresh.php b/core/modules/aggregator/src/Plugin/QueueWorker/AggregatorRefresh.php new file mode 100644 index 000000000000..d3267eba427c --- /dev/null +++ b/core/modules/aggregator/src/Plugin/QueueWorker/AggregatorRefresh.php @@ -0,0 +1,31 @@ +<?php + +/** + * @file + * Contains \Drupal\aggregator\Plugin\QueueWorker\AggregatorRefresh. + */ + +namespace Drupal\aggregator\Plugin\QueueWorker; + +use Drupal\aggregator\FeedInterface; +use Drupal\Core\Queue\QueueWorkerBase; + +/** + * @QueueWorker( + * id = "aggregator_feeds", + * title = @Translation("Aggregator refresh"), + * cron = {"time" = 60} + * ) + */ +class AggregatorRefresh extends QueueWorkerBase { + + /** + * {@inheritdoc} + */ + public function processItem($data) { + if ($data instanceof FeedInterface) { + $data->refreshItems(); + } + } + +} diff --git a/core/modules/aggregator/src/Tests/AggregatorCronTest.php b/core/modules/aggregator/src/Tests/AggregatorCronTest.php index 8a8ee7d82fcc..59b638610887 100644 --- a/core/modules/aggregator/src/Tests/AggregatorCronTest.php +++ b/core/modules/aggregator/src/Tests/AggregatorCronTest.php @@ -21,11 +21,11 @@ public function testCron() { $this->createSampleNodes(); $feed = $this->createFeed(); $this->cronRun(); - $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(), 'Expected number of items in database.'); + $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField()); $this->deleteFeedItems($feed); - $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(), 'Expected number of items in database.'); + $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField()); $this->cronRun(); - $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(), 'Expected number of items in database.'); + $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField()); // Test feed locking when queued for update. $this->deleteFeedItems($feed); @@ -36,7 +36,7 @@ public function testCron() { )) ->execute(); $this->cronRun(); - $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(), 'Expected number of items in database.'); + $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField()); db_update('aggregator_feed') ->condition('fid', $feed->id()) ->fields(array( @@ -44,6 +44,6 @@ public function testCron() { )) ->execute(); $this->cronRun(); - $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(), 'Expected number of items in database.'); + $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField()); } } diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 5740e4a8993f..fe938a3a930a 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -361,88 +361,18 @@ function locale_themes_uninstalled($themes) { /** * Implements hook_cron(). * - * @see locale_queue_info() + * @see \Drupal\locale\Plugin\QueueWorker\LocaleTranslation */ function locale_cron() { // Update translations only when an update frequency was set by the admin // and a translatable language was set. - // Update tasks are added to the queue here but processed by Drupal's cron - // using the cron worker defined in locale_queue_info(). + // Update tasks are added to the queue here but processed by Drupal's cron. if ($frequency = \Drupal::config('locale.settings')->get('translation.update_interval_days') && locale_translatable_language_list()) { module_load_include('translation.inc', 'locale'); locale_cron_fill_queue(); } } -/** - * Implements hook_queue_info(). - */ -function locale_queue_info() { - $queues['locale_translation'] = array( - 'title' => t('Update translations'), - 'worker callback' => 'locale_translation_worker', - 'cron' => array( - 'time' => 30, - ), - ); - return $queues; -} - -/** - * Callback: Executes interface translation queue tasks. - * - * The translation update functions executed here are batch operations which - * are also used in translation update batches. The batch functions may need to - * be executed multiple times to complete their task, typically this is the - * translation import function. When a batch function is not finished, a new - * queue task is created and added to the end of the queue. The batch context - * data is needed to continue the batch task is stored in the queue with the - * queue data. - * - * @param array $data - * Queue data array containing: - * - Function name. - * - Array of function arguments. Optionally contains the batch context data. - * - * @see locale_queue_info() - */ -function locale_translation_worker($data) { - module_load_include('batch.inc', 'locale'); - list($function, $args) = $data; - - // We execute batch operation functions here to check, download and import the - // translation files. Batch functions use a context variable as last argument - // which is passed by reference. When a batch operation is called for the - // first time a default batch context is created. When called iterative - // (usually the batch import function) the batch context is passed through via - // the queue and is part of the $data. - $last = count($args) - 1; - if (!is_array($args[$last]) || !isset($args[$last]['finished'])) { - $batch_context = array( - 'sandbox' => array(), - 'results' => array(), - 'finished' => 1, - 'message' => '', - ); - } - else { - $batch_context = $args[$last]; - unset ($args[$last]); - } - $args = array_merge($args, array(&$batch_context)); - - // Call the batch operation function. - call_user_func_array($function, $args); - - // If the batch operation is not finished we create a new queue task to - // continue the task. This is typically the translation import task. - if ($batch_context['finished'] < 1) { - unset($batch_context['strings']); - $queue = \Drupal::queue('locale_translation', TRUE); - $queue->createItem(array($function, $args)); - } -} - /** * Imports translations when new modules or themes are installed. * diff --git a/core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php b/core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php new file mode 100644 index 000000000000..a850b0d51336 --- /dev/null +++ b/core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php @@ -0,0 +1,122 @@ +<?php + +/** + * @file + * Contains \Drupal\locale\Plugin\QueueWorker\LocaleTranslation. + */ + +namespace Drupal\locale\Plugin\QueueWorker; + +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Queue\QueueInterface; +use Drupal\Core\Queue\QueueWorkerBase; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Executes interface translation queue tasks. + * + * @QueueWorker( + * id = "locale_translation", + * title = @Translation("Update translations"), + * cron = {"time" = 30} + * ) + */ +class LocaleTranslation extends QueueWorkerBase implements ContainerFactoryPluginInterface { + + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The queue object. + * + * @var \Drupal\Core\Queue\QueueInterface + */ + protected $queue; + + /** + * Constructs a new LocaleTranslation object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * @param \Drupal\Core\Queue\QueueInterface $queue + * The queue object. + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, ModuleHandlerInterface $module_handler, QueueInterface $queue) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->moduleHandler = $module_handler; + $this->queue = $queue; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('module_handler'), + $container->get('queue')->get('locale_translation', TRUE) + ); + } + + /** + * {@inheritdoc} + * + * The translation update functions executed here are batch operations which + * are also used in translation update batches. The batch functions may need + * to be executed multiple times to complete their task, typically this is the + * translation import function. When a batch function is not finished, a new + * queue task is created and added to the end of the queue. The batch context + * data is needed to continue the batch task is stored in the queue with the + * queue data. + */ + public function processItem($data) { + $this->moduleHandler->loadInclude('locale', 'batch.inc'); + list($function, $args) = $data; + + // We execute batch operation functions here to check, download and import + // the translation files. Batch functions use a context variable as last + // argument which is passed by reference. When a batch operation is called + // for the first time a default batch context is created. When called + // iterative (usually the batch import function) the batch context is passed + // through via the queue and is part of the $data. + $last = count($args) - 1; + if (!is_array($args[$last]) || !isset($args[$last]['finished'])) { + $batch_context = [ + 'sandbox' => [], + 'results' => [], + 'finished' => 1, + 'message' => '', + ]; + } + else { + $batch_context = $args[$last]; + unset ($args[$last]); + } + $args = array_merge($args, [&$batch_context]); + + // Call the batch operation function. + call_user_func_array($function, $args); + + // If the batch operation is not finished we create a new queue task to + // continue the task. This is typically the translation import task. + if ($batch_context['finished'] < 1) { + unset($batch_context['strings']); + $this->queue->createItem([$function, $args]); + } + } + +} diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index ae416ed556bc..3c3020f9b353 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -62,8 +62,8 @@ function hook_hook_info() { * Long-running tasks and tasks that could time out, such as retrieving remote * data, sending email, and intensive file tasks, should use the queue API * instead of executing the tasks directly. To do this, first define one or - * more queues via hook_queue_info(). Then, add items that need to be - * processed to the defined queues. + * more queues via a \Drupal\Core\Annotation\QueueWorker plugin. Then, add items + * that need to be processed to the defined queues. */ function hook_cron() { // Short-running operation example, not using a queue: @@ -98,46 +98,6 @@ function hook_data_type_info_alter(&$data_types) { $data_types['email']['class'] = '\Drupal\mymodule\Type\Email'; } -/** - * Declare queues holding items that need to be run periodically. - * - * While there can be only one hook_cron() process running at the same time, - * there can be any number of processes defined here running. Because of - * this, long running tasks are much better suited for this API. Items queued - * in hook_cron() might be processed in the same cron run if there are not many - * items in the queue, otherwise it might take several requests, which can be - * run in parallel. - * - * You can create queues, add items to them, claim them, etc without declaring - * the queue in this hook if you want, however, you need to take care of - * processing the items in the queue in that case. - * - * @return - * An associative array where the key is the queue name and the value is - * again an associative array. Possible keys are: - * - 'worker callback': A PHP callable to call that is an implementation of - * callback_queue_worker(). - * - 'cron': (optional) An associative array containing the optional key: - * - 'time': (optional) How much time Drupal cron should spend on calling - * this worker in seconds. Defaults to 15. - * If the cron key is not defined, the queue will not be processed by cron, - * and must be processed by other means. - * - * @see hook_cron() - * @see hook_queue_info_alter() - */ -function hook_queue_info() { - $queues['aggregator_feeds'] = array( - 'title' => t('Aggregator refresh'), - 'worker callback' => array('Drupal\my_module\MyClass', 'aggregatorRefresh'), - // Only needed if this queue should be processed by cron. - 'cron' => array( - 'time' => 60, - ), - ); - return $queues; -} - /** * Alter cron queue information before cron runs. * @@ -147,7 +107,8 @@ function hook_queue_info() { * @param array $queues * An array of cron queue information. * - * @see hook_queue_info() + * @see \Drupal\Core\QueueWorker\QueueWorkerInterface + * @see \Drupal\Core\Annotation\QueueWorker * @see \Drupal\Core\Cron */ function hook_queue_info_alter(&$queues) { @@ -156,35 +117,6 @@ function hook_queue_info_alter(&$queues) { $queues['aggregator_feeds']['cron']['time'] = 90; } -/** - * Work on a single queue item. - * - * Callback for hook_queue_info(). - * - * @param $queue_item_data - * The data that was passed to \Drupal\Core\Queue\QueueInterface::createItem() - * when the item was queued. - * - * @throws \Exception - * The worker callback may throw an exception to indicate there was a problem. - * The cron process will log the exception, and leave the item in the queue to - * be processed again later. - * @throws \Drupal\Core\Queue\SuspendQueueException - * More specifically, a SuspendQueueException should be thrown when the - * callback is aware that the problem will affect all subsequent workers of - * its queue. For example, a callback that makes HTTP requests may find that - * the remote server is not responding. The cron process will behave as with a - * normal Exception, and in addition will not attempt to process further items - * from the current item's queue during the current cron run. - * - * @see \Drupal\Core\Cron::run() - */ -function callback_queue_worker($queue_item_data) { - $node = node_load($queue_item_data); - $node->title = 'Updated title'; - $node->save(); -} - /** * Allows modules to declare their own Form API element types and specify their * default values. diff --git a/core/modules/system/tests/modules/cron_queue_test/cron_queue_test.module b/core/modules/system/tests/modules/cron_queue_test/cron_queue_test.module deleted file mode 100644 index c46fdb903c62..000000000000 --- a/core/modules/system/tests/modules/cron_queue_test/cron_queue_test.module +++ /dev/null @@ -1,38 +0,0 @@ -<?php - -function cron_queue_test_queue_info() { - $queues['cron_queue_test_exception'] = array( - 'title' => t('Exception test'), - 'worker callback' => 'cron_queue_test_exception', - // Only needed if this queue should be processed by cron. - 'cron' => array( - 'time' => 60, - ), - ); - $queues['cron_queue_test_broken_queue'] = array( - 'title' => t('Broken queue test'), - 'worker callback' => 'cron_queue_test_broken_queue', - // Only needed if this queue should be processed by cron. - 'cron' => array( - 'time' => 60, - ), - ); - - return $queues; -} - -function cron_queue_test_exception($item) { - throw new Exception('That is not supposed to happen.'); -} - -/** - * Implements callback_queue_worker(). - * - * This queue is declared broken if the queue item data is 'crash'. - */ -function cron_queue_test_broken_queue($queue_item_data) { - if ($queue_item_data == 'crash') { - throw new \Drupal\Core\Queue\SuspendQueueException('The queue is broken.'); - } - // Do nothing otherwise. -} diff --git a/core/modules/system/tests/modules/cron_queue_test/src/Plugin/QueueWorker/CronQueueTestBrokenQueue.php b/core/modules/system/tests/modules/cron_queue_test/src/Plugin/QueueWorker/CronQueueTestBrokenQueue.php new file mode 100644 index 000000000000..f642a4d1bf7e --- /dev/null +++ b/core/modules/system/tests/modules/cron_queue_test/src/Plugin/QueueWorker/CronQueueTestBrokenQueue.php @@ -0,0 +1,32 @@ +<?php + +/** + * @file + * Contains \Drupal\cron_queue_test\Plugin\QueueWorker\CronQueueTestBrokenQueue. + */ + +namespace Drupal\cron_queue_test\Plugin\QueueWorker; + +use Drupal\Core\Queue\QueueWorkerBase; +use Drupal\Core\Queue\SuspendQueueException; + +/** + * @QueueWorker( + * id = "cron_queue_test_broken_queue", + * title = @Translation("Broken queue test"), + * cron = {"time" = 60} + * ) + */ +class CronQueueTestBrokenQueue extends QueueWorkerBase { + + /** + * {@inheritdoc} + */ + public function processItem($data) { + if ($data == 'crash') { + throw new SuspendQueueException('The queue is broken.'); + } + // Do nothing otherwise. + } + +} diff --git a/core/modules/system/tests/modules/cron_queue_test/src/Plugin/QueueWorker/CronQueueTestException.php b/core/modules/system/tests/modules/cron_queue_test/src/Plugin/QueueWorker/CronQueueTestException.php new file mode 100644 index 000000000000..ff322094ada1 --- /dev/null +++ b/core/modules/system/tests/modules/cron_queue_test/src/Plugin/QueueWorker/CronQueueTestException.php @@ -0,0 +1,28 @@ +<?php + +/** + * @file + * Contains \Drupal\cron_queue_test\Plugin\QueueWorker\CronQueueTestException. + */ + +namespace Drupal\cron_queue_test\Plugin\QueueWorker; + +use Drupal\Core\Queue\QueueWorkerBase; + +/** + * @QueueWorker( + * id = "cron_queue_test_exception", + * title = @Translation("Exception test"), + * cron = {"time" = 60} + * ) + */ +class CronQueueTestException extends QueueWorkerBase { + + /** + * {@inheritdoc} + */ + public function processItem($data) { + throw new \Exception('That is not supposed to happen.'); + } + +} -- GitLab