4 * Drupal 7 engine for the Batch API
7 use Drush\Log\LogLevel;
10 * Main loop for the Drush batch API.
12 * Saves a record of the batch into the database, and progressively call $command to
13 * process the operations.
16 * The command to call to process the batch.
19 function _drush_backend_batch_process($command = 'batch-process', $args, $options) {
20 $batch =& batch_get();
23 $process_info = array(
26 $batch += $process_info;
28 // The batch is now completely built. Allow other modules to make changes
29 // to the batch so that it is easier to reuse batch processes in other
31 if (drush_drupal_major_version() >= 8) {
32 \Drupal::moduleHandler()->alter('batch', $batch);
35 drupal_alter('batch', $batch);
38 // Assign an arbitrary id: don't rely on a serial column in the 'batch'
39 // table, since non-progressive batches skip database storage completely.
40 $batch['id'] = db_next_id();
41 $args[] = $batch['id'];
43 $batch['progressive'] = TRUE;
45 // Move operations to a job queue. Non-progressive batches will use a
46 // memory-based queue.
47 foreach ($batch['sets'] as $key => $batch_set) {
48 _batch_populate_queue($batch, $key);
51 drush_include_engine('drupal', 'environment');
53 if (drush_drupal_major_version() >= 8) {
54 /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */
55 $batch_storage = \Drupal::service('batch.storage');
56 $batch_storage->create($batch);
61 'bid' => $batch['id'],
62 'timestamp' => REQUEST_TIME,
63 'token' => drush_get_token($batch['id']),
64 'batch' => serialize($batch),
70 // Not used in D8+ and possibly earlier.
74 $data = drush_invoke_process('@self', $command, $args, array('user' => drush_user_get_class()->getCurrentUserAsSingle()->id()));
76 $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE);
83 * Initialize the batch command and call the worker function.
85 * Loads the batch record from the database and sets up the requirements
86 * for the worker, such as registering the shutdown function.
89 * The batch id of the batch being processed.
91 function _drush_batch_command($id) {
92 $batch =& batch_get();
94 $data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(
95 ':bid' => $id))->fetchField();
98 $batch = unserialize($data);
104 if (!isset($batch['running'])) {
105 $batch['running'] = TRUE;
108 // Register database update for end of processing.
109 register_shutdown_function('_drush_batch_shutdown');
111 if (_drush_batch_worker()) {
112 _drush_batch_finished();
118 * Process batch operations
120 * Using the current $batch process each of the operations until the batch
121 * has been completed or half of the available memory for the process has been
124 function _drush_batch_worker() {
125 $batch =& batch_get();
126 $current_set =& _batch_current_set();
129 if (empty($current_set['start'])) {
130 $current_set['start'] = microtime(TRUE);
132 $queue = _batch_queue($current_set);
133 while (!$current_set['success']) {
134 // If this is the first time we iterate this batch set in the current
135 // request, we check if it requires an additional file for functions
137 if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
138 include_once DRUPAL_ROOT . '/' . $current_set['file'];
142 // Assume a single pass operation and set the completion level to 1 by
146 if ($item = $queue->claimItem()) {
147 list($function, $args) = $item->data;
149 // Build the 'context' array and execute the function call.
150 $batch_context = array(
151 'sandbox' => &$current_set['sandbox'],
152 'results' => &$current_set['results'],
153 'finished' => &$finished,
154 'message' => &$task_message,
156 // Magic wrap to catch changes to 'message' key.
157 $batch_context = new DrushBatchContext($batch_context);
159 // Tolerate recoverable errors.
160 // See https://github.com/drush-ops/drush/issues/1930
161 $halt_on_error = drush_get_option('halt-on-error', TRUE);
162 drush_set_option('halt-on-error', FALSE);
163 call_user_func_array($function, array_merge($args, array(&$batch_context)));
164 drush_set_option('halt-on-error', $halt_on_error);
166 $finished = $batch_context['finished'];
167 if ($finished >= 1) {
168 // Make sure this step is not counted twice when computing $current.
170 // Remove the processed operation and clear the sandbox.
171 $queue->deleteItem($item);
172 $current_set['count']--;
173 $current_set['sandbox'] = array();
177 // When all operations in the current batch set are completed, browse
178 // through the remaining sets, marking them 'successfully processed'
179 // along the way, until we find a set that contains operations.
180 // _batch_next_set() executes form submit handlers stored in 'control'
181 // sets (see form_execute_handlers()), which can in turn add new sets to
183 $set_changed = FALSE;
184 $old_set = $current_set;
185 while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
186 $current_set = &_batch_current_set();
187 $current_set['start'] = microtime(TRUE);
191 // At this point, either $current_set contains operations that need to be
192 // processed or all sets have been completed.
193 $queue = _batch_queue($current_set);
195 // If we are in progressive mode, break processing after 1 second.
196 if (drush_memory_limit() > 0 && (memory_get_usage() * 2) >= drush_memory_limit()) {
197 drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), LogLevel::BATCH);
198 // Record elapsed wall clock time.
199 $current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2);
204 // Reporting 100% progress will cause the whole batch to be considered
205 // processed. If processing was paused right after moving to a new set,
206 // we have to use the info from the new (unprocessed) set.
207 if ($set_changed && isset($current_set['queue'])) {
208 // Processing will continue with a fresh batch set.
209 $remaining = $current_set['count'];
210 $total = $current_set['total'];
211 $progress_message = $current_set['init_message'];
215 // Processing will continue with the current batch set.
216 $remaining = $old_set['count'];
217 $total = $old_set['total'];
218 $progress_message = $old_set['progress_message'];
221 $current = $total - $remaining + $finished;
222 $percentage = _batch_api_percentage($total, $current);
223 return ($percentage == 100);
227 * End the batch processing:
228 * Call the 'finished' callbacks to allow custom handling of results,
229 * and resolve page redirection.
231 function _drush_batch_finished() {
232 $batch = &batch_get();
234 // Execute the 'finished' callbacks for each batch set, if defined.
235 foreach ($batch['sets'] as $batch_set) {
236 if (isset($batch_set['finished'])) {
237 // Check if the set requires an additional file for function definitions.
238 if (isset($batch_set['file']) && is_file($batch_set['file'])) {
239 include_once DRUPAL_ROOT . '/' . $batch_set['file'];
241 if (is_callable($batch_set['finished'])) {
242 $queue = _batch_queue($batch_set);
243 $operations = $queue->getAllItems();
244 $elapsed = $batch_set['elapsed'] / 1000;
245 $elapsed = drush_drupal_major_version() >=8 ? \Drupal::service('date.formatter')->formatInterval($elapsed) : format_interval($elapsed);
246 $batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, $elapsed);
251 // Clean up the batch table and unset the static $batch variable.
252 if (drush_drupal_major_version() >= 8) {
253 /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */
254 $batch_storage = \Drupal::service('batch.storage');
255 $batch_storage->delete($batch['id']);
259 ->condition('bid', $batch['id'])
263 foreach ($batch['sets'] as $batch_set) {
264 if ($queue = _batch_queue($batch_set)) {
265 $queue->deleteQueue();
270 drush_set_option('drush_batch_process_finished', TRUE);
274 * Shutdown function: store the batch data for next request,
275 * or clear the table if the batch is finished.
277 function _drush_batch_shutdown() {
278 if ($batch = batch_get()) {
279 if (drush_drupal_major_version() >= 8) {
280 /** @var \Drupal\Core\Batch\BatchStorage $batch_storage */
281 $batch_storage = \Drupal::service('batch.storage');
282 $batch_storage->update($batch);
286 ->fields(array('batch' => serialize($batch)))
287 ->condition('bid', $batch['id'])