diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc index 4f3d350..700e578 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 have 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.'), 'warning'); + } } 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 have 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.'), 'warning'); + } } $form_state['redirect'] = 'admin/content/comment'; } diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index ae9278c..662da16 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..1a9e50f 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 have 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.'), 'warning'); + } } $form_state['redirect'] = 'admin/content'; } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 0c3cfb7..2999fea 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..e6c85c7 100644 --- a/core/modules/system/system.queue.inc +++ b/core/modules/system/system.queue.inc @@ -94,6 +94,18 @@ class DrupalQueue { } return $queues[$name]; } + + /** + * Add 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 b22d30f..895db53 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -2299,9 +2299,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(); @@ -2329,7 +2335,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; } /**