How does the Drupal 6 Batch API work?

I successfully use the Batch API for processing, which usually results in PHP timeouts or memory errors, and it works great.

I looked at the code a bit, but I still don't understand what is going on behind the scenes.

Can someone familiar with the process describe how it works?

+4
source share
2 answers

I looked at the code a bit, but I still don't understand what is going on behind the scenes.

Can someone familiar with the process describe how it works?

What happens is that in order to avoid PHP timeouts, the browser periodically passes through AJAX the URL ( http://example.com/batch?id= $ id), which causes the execution of batch operations.
See _ batch_page () , which is a function called by system_batch_page () , a menu callback for the "batch" path.

function _batch_page() { $batch = &batch_get(); // Retrieve the current state of batch from db. if (isset($_REQUEST['id']) && $data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d AND token = '%s'", $_REQUEST['id'], drupal_get_token($_REQUEST['id'])))) { $batch = unserialize($data); } else { return FALSE; } // Register database update for end of processing. register_shutdown_function('_batch_shutdown'); $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; $output = NULL; switch ($op) { case 'start': $output = _batch_start(); break; case 'do': // JS-version AJAX callback. _batch_do(); break; case 'do_nojs': // Non-JS progress page. $output = _batch_progress_page_nojs(); break; case 'finished': $output = _batch_finished(); break; } return $output; } 

In _ batch_progress_page_nojs () you will see the following code.

  $url = url($batch['url'], array('query' => array('id' => $batch['id'], 'op' => $new_op))); drupal_set_html_head('<meta http-equiv="Refresh" content="0; URL=' . $url . '">'); $output = theme('progress_bar', $percentage, $message); return $output; 

Setting the Refresh meta tag will refresh the page.

Similar code is present in Drupal 7; the difference is that the code has been ported and it uses the new features implemented by Drupal 7.

  // Merge required query parameters for batch processing into those provided by // batch_set() or hook_batch_alter(). $batch['url_options']['query']['id'] = $batch['id']; $batch['url_options']['query']['op'] = $new_op; $url = url($batch['url'], $batch['url_options']); $element = array( '#tag' => 'meta', '#attributes' => array( 'http-equiv' => 'Refresh', 'content' => '0; URL=' . $url, ), ); drupal_add_html_head($element, 'batch_progress_meta_refresh'); return theme('progress_bar', array('percent' => $percentage, 'message' => $message)); 

When JavaScript is enabled, the code that does all the work is in the batch.js file.

 /** * Attaches the batch behavior to progress bars. */ Drupal.behaviors.batch = function (context) { // This behavior attaches by ID, so is only valid once on a page. if ($('#progress.batch-processed').size()) { return; } $('#progress', context).addClass('batch-processed').each(function () { var holder = this; var uri = Drupal.settings.batch.uri; var initMessage = Drupal.settings.batch.initMessage; var errorMessage = Drupal.settings.batch.errorMessage; // Success: redirect to the summary. var updateCallback = function (progress, status, pb) { if (progress == 100) { pb.stopMonitoring(); window.location = uri+'&op=finished'; } }; var errorCallback = function (pb) { var div = document.createElement('p'); div.className = 'error'; $(div).html(errorMessage); $(holder).prepend(div); $('#wait').hide(); }; var progress = new Drupal.progressBar('updateprogress', updateCallback, "POST", errorCallback); progress.setProgress(-1, initMessage); $(holder).append(progress.element); progress.startMonitoring(uri+'&op=do', 10); }); }; 

Querying the batch URL starts with progress.startMonitoring(uri+'&op=do', 10) . The batch.js file depends on the functionality open in the Drupal.progressBar , which is defined in the progress.js file.

Similar code is used in Drupal 7, which uses a slightly different version of batch.js and progress.js .

 (function ($) { /** * Attaches the batch behavior to progress bars. */ Drupal.behaviors.batch = { attach: function (context, settings) { $('#progress', context).once('batch', function () { var holder = $(this); // Success: redirect to the summary. var updateCallback = function (progress, status, pb) { if (progress == 100) { pb.stopMonitoring(); window.location = settings.batch.uri + '&op=finished'; } }; var errorCallback = function (pb) { holder.prepend($('<p class="error"></p>').html(settings.batch.errorMessage)); $('#wait').hide(); }; var progress = new Drupal.progressBar('updateprogress', updateCallback, 'POST', errorCallback); progress.setProgress(-1, settings.batch.initMessage); holder.append(progress.element); progress.startMonitoring(settings.batch.uri + '&op=do', 10); }); } }; })(jQuery); 

The differences are that with Drupal 7 all jQuery code is wrapped in (function ($) { })(jQuery); and that jQuery as soon as the plugin is included in Drupal 7 . Drupal 7 also sets WAI-ARIA attributes for compatibility with screen readers; this also happens in HTML added from JavaScript code, such as the following in the progress.js file.

  // The WAI-ARIA setting aria-live="polite" will announce changes after users // have completed their current activity and not interrupt the screen reader. this.element = $('<div class="progress" aria-live="polite"></div>').attr('id', id); this.element.html('<div class="bar"><div class="filled"></div></div>' + '<div class="percentage"></div>' + '<div class="message">&nbsp;</div>'); 

When servicing a batch page, Drupal sets _ batch_shutdown () as a shutdown callback; when PHP shuts down due to a timeout, the function updates the array of packages in the database.

 // Drupal 6. function _batch_shutdown() { if ($batch = batch_get()) { db_query("UPDATE {batch} SET batch = '%s' WHERE bid = %d", serialize($batch), $batch['id']); } } 
 // Drupal 7. function _batch_shutdown() { if ($batch = batch_get()) { db_update('batch') ->fields(array('batch' => serialize($batch))) ->condition('bid', $batch['id']) ->execute(); } } 
+5
source

From a great implementation example :

Each callback for each operation will be repeated again and again until $ context ['finished'] is set to 1. After each pass, the batch.inc package check its timer and see if it is time for a new HTTP request, i.e. . when more than 1 minute has passed since the last request.

For an entire batch that is processed very quickly, only one http request may be required, even if it is repeated through a callback several times, while slower processes can initiate a new HTTP request at each iteration of the callback.

This means that you should configure the processing for each iteration only as much as you can do without php timeout, and then let batch.inc decide if it needs to make a new HTTP request.

In other words: you have to split your batch of tasks into pieces (or individual tasks) that will not time out. Drupal will end its current call and open a new HTTP request if it sees an approaching PHP timeout.

+1
source

All Articles