diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc
index 4f3d350..94b62bf 100644
--- a/core/modules/comment/comment.admin.inc
+++ b/core/modules/comment/comment.admin.inc
@@ -161,7 +161,10 @@ function comment_admin_overview_submit($form, &$form_state) {
$cids = $form_state['values']['comments'];
if ($operation == 'delete') {
- comment_delete_multiple($cids);
+ $deleted = comment_delete_multiple($cids);
+ if ($deleted != count($cids)) {
+ drupal_set_message(format_plural(count($cids) - $deleted, '1 comment has been scheduled for deletion on the next cron run. Run cron now to finish deleting this comment immediately.', '@count comments have been scheduled for deletion on the next cron run. Run cron now to finish deleting these comments immediately.', array('@cron' => 'admin/logs/status/run-cron')));
+ }
}
else {
foreach ($cids as $cid => $value) {
@@ -228,11 +231,16 @@ function comment_multiple_delete_confirm($form, &$form_state) {
*/
function comment_multiple_delete_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
- comment_delete_multiple(array_keys($form_state['values']['comments']));
+ $deleted = comment_delete_multiple(array_keys($form_state['values']['comments']));
cache_clear_all();
$count = count($form_state['values']['comments']);
- watchdog('content', 'Deleted @count comments.', array('@count' => $count));
- drupal_set_message(format_plural($count, 'Deleted 1 comment.', 'Deleted @count comments.'));
+ watchdog('content', 'Deleted @count comments.', array('@count' => $deleted));
+ drupal_set_message(format_plural($deleted, 'Deleted 1 comment.', 'Deleted @count comments.'));
+
+ // In case the jobs get enqueued, we warn user.
+ if ($count != $deleted) {
+ drupal_set_message(format_plural($count - $deleted, '1 comment has been scheduled for deletion on the next cron run. Run cron now to finish deleting this comment immediately.', '@count comments have been scheduled for deletion on the next cron run. Run cron now to finish deleting these comments immediately.', array('@cron' => 'admin/logs/status/run-cron')));
+ }
}
$form_state['redirect'] = 'admin/content/comment';
}
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index ae9278c..bf4f383 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -1598,8 +1598,14 @@ function comment_delete($cid) {
*
* @param $cids
* The comment to delete.
+ *
+ * @return
+ * The number of deleted comments.
*/
function comment_delete_multiple($cids) {
+ // Use a queue to process large datasets.
+ $cids = system_workers_bulk_split(__FUNCTION__, $cids);
+
$comments = comment_load_multiple($cids);
if ($comments) {
$transaction = db_transaction();
@@ -1624,7 +1630,11 @@ function comment_delete_multiple($cids) {
watchdog_exception('comment', $e);
throw $e;
}
+ // Return the number for comments deleted.
+ return count($cids);
}
+ // No comments were passed to delete.
+ return 0;
}
/**
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index 8f46df4..fd19e8a 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -587,10 +587,15 @@ function node_multiple_delete_confirm($form, &$form_state, $nodes) {
function node_multiple_delete_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
- node_delete_multiple(array_keys($form_state['values']['nodes']));
+ $deleted = node_delete_multiple(array_keys($form_state['values']['nodes']));
$count = count($form_state['values']['nodes']);
- watchdog('content', 'Deleted @count posts.', array('@count' => $count));
- drupal_set_message(format_plural($count, 'Deleted 1 post.', 'Deleted @count posts.'));
+ watchdog('content', 'Deleted @count posts.', array('@count' => $deleted));
+ drupal_set_message(format_plural($deleted, 'Deleted 1 post.', 'Deleted @count posts.'));
+
+ // In case the jobs get enqueued, we warn user.
+ if ($count != $deleted) {
+ drupal_set_message(format_plural($count - $deleted, '1 node has been scheduled for deletion on the next cron run. Run cron now to finish deleting this comment immediately.', '@count nodes have been scheduled for deletion on the next cron run. Run cron now to finish deleting these nodes immediately.', array('@cron' => 'admin/logs/status/run-cron')));
+ }
}
$form_state['redirect'] = 'admin/content';
}
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 81ee841..7a90790 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1189,10 +1189,16 @@ function node_delete($nid) {
*
* @param $nids
* An array of node IDs.
+ *
+ * @return
+ * The number of deleted nodes.
*/
function node_delete_multiple($nids) {
$transaction = db_transaction();
if (!empty($nids)) {
+ // Use a queue to process large datasets.
+ $nids = system_workers_bulk_split(__FUNCTION__, $nids);
+
$nodes = node_load_multiple($nids, array());
try {
@@ -1234,7 +1240,12 @@ function node_delete_multiple($nids) {
// Clear the page and block and node_load_multiple caches.
entity_get_controller('node')->resetCache();
+
+ // Return the number of nodes deleted
+ return count($nids);
}
+ // No nodes were passed to delete.
+ return 0;
}
/**
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index f4261e1..bbae41c 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -3032,6 +3032,54 @@ function system_cron() {
}
/**
+ * Implements hook_cron_queue_info().
+ */
+function system_cron_queue_info() {
+ $queues['system_workers'] = array(
+ 'worker callback' => 'system_queue_worker',
+ 'time' => 15,
+ 'reliable' => TRUE,
+ );
+ return $queues;
+}
+
+/**
+ * Runs a simple job queue worker.
+ */
+function system_queue_worker($data) {
+ return call_user_func_array($data['callback'], $data['arguments']);
+}
+
+/**
+ * Splits large datasets into smaller subsets for processing.
+ *
+ * When the dataset is larger than the allowed threshold defined by the
+ * 'queue_bulk_threshold' configuration variable, a subset is returned and the
+ * remaining items are added to the queue for later processing.
+ *
+ * @param $callback
+ * The callback for the job being added.
+ * @param $items
+ * An array of items to be processed.
+ *
+ * @return
+ * A subset of items to be processed that does not exceed the bulk threshold.
+ */
+function system_workers_bulk_split($callback, $items) {
+ $threshold = variable_get('queue_bulk_threshold', 20);
+
+ if (count($items) > $threshold) {
+ $jobs = array_chunk($items, $threshold);
+ $items = array_shift($jobs);
+ foreach ($jobs as $job) {
+ DrupalQueue::addJob($callback, array($job));
+ }
+ }
+
+ return $items;
+}
+
+/**
* Implements hook_flush_caches().
*/
function system_flush_caches() {
diff --git a/core/modules/system/system.queue.inc b/core/modules/system/system.queue.inc
index 00d3940..4862f79 100644
--- a/core/modules/system/system.queue.inc
+++ b/core/modules/system/system.queue.inc
@@ -94,6 +94,18 @@ class DrupalQueue {
}
return $queues[$name];
}
+
+ /**
+ * Adds a simple job to the queue.
+ *
+ * @param $callback
+ * The name of the function to be called when the queue is processed next.
+ * @param $arguments
+ * The arguments to the callback.
+ */
+ public static function addJob($callback, $arguments = array()) {
+ return DrupalQueue::get('system_workers', TRUE)->createItem(array('callback' => $callback, 'arguments' => $arguments));
+ }
}
interface DrupalQueueInterface {
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index abf7477..0f31653 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -2436,9 +2436,15 @@ function user_delete($uid) {
*
* @param $uids
* An array of user IDs.
+ *
+ * @return
+ * The number of deleted users.
*/
function user_delete_multiple(array $uids) {
if (!empty($uids)) {
+ // Enqueue larger operations.
+ $uids = system_workers_bulk_split(__FUNCTION__, $uids);
+
$accounts = user_load_multiple($uids, array());
$transaction = db_transaction();
@@ -2466,7 +2472,12 @@ function user_delete_multiple(array $uids) {
throw $e;
}
entity_get_controller('user')->resetCache();
+
+ // Return the number of users deleted.
+ return count($uids);
}
+ // No users were passed to delete.
+ return 0;
}
/**