diff options
author | Dries Buytaert <dries@buytaert.net> | 2009-08-22 16:01:10 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2009-08-22 16:01:10 +0000 |
commit | 4c028a19fbe9d5fc81f21bdce862f6c74a61b7ee (patch) | |
tree | aaf42312065ef361c46e4176092103a18c761798 /modules | |
parent | 06d296d5f96ee2a580e5d0c7a799775f52d71379 (diff) | |
download | brdo-4c028a19fbe9d5fc81f21bdce862f6c74a61b7ee.tar.gz brdo-4c028a19fbe9d5fc81f21bdce862f6c74a61b7ee.tar.bz2 |
- Patch #331611 by sun, joshmiller, TheRec, Rob Loach, Damien Tournoud: add a poormanscron-like feature to core.
Diffstat (limited to 'modules')
-rw-r--r-- | modules/system/system.admin.inc | 21 | ||||
-rw-r--r-- | modules/system/system.module | 115 | ||||
-rw-r--r-- | modules/system/system.test | 53 |
3 files changed, 185 insertions, 4 deletions
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 88c8f2f70..9bd326ff2 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1313,7 +1313,16 @@ function system_site_information_settings() { '#description' => t('Render all blocks on the default 404 (not found) page. Disabling blocks can help with performance but might leave users with a less functional site.'), '#default_value' => variable_get('site_404_blocks', 0) ); + $form['cron_safe_threshold'] = array( + '#type' => 'select', + '#title' => t('Automatically run cron'), + '#default_value' => variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD), + '#options' => array(0 => t('Never')) + drupal_map_assoc(array(3600, 10800, 21600, 43200, 86400, 604800), 'format_interval'), + '#description' => t('When enabled, the site will check whether cron has been run in the configured interval and automatically run it upon the next page request. For more information visit the <a href="@status-report-url">status report page</a>.', array('@status-report-url' => url('admin/reports/status'))), + ); + $form['#validate'][] = 'system_site_information_settings_validate'; + $form['#submit'][] = 'system_site_information_settings_submit'; return system_settings_form($form); } @@ -1334,6 +1343,18 @@ function system_site_information_settings_validate($form, &$form_state) { } /** + * Form submit handler for the site-information form. + */ +function system_site_information_settings_submit($form, &$form_state) { + // Clear caches when the cron threshold is changed to ensure that the cron + // image is not contained in cached pages. + $cron_threshold = variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD); + if (($cron_threshold > 0 && $form_state['input']['cron_safe_threshold'] == 0) || ($cron_threshold == 0 && $form_state['input']['cron_safe_threshold'] > 0)) { + cache_clear_all(); + } +} + +/** * Form builder; Configure error reporting settings. * * @ingroup forms diff --git a/modules/system/system.module b/modules/system/system.module index 5088b4cf8..e768944e3 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -42,6 +42,11 @@ define('DRUPAL_MINIMUM_PGSQL', '8.3'); define('DRUPAL_MAXIMUM_TEMP_FILE_AGE', 21600); /** + * Default interval for automatic cron executions in seconds. + */ +define('DRUPAL_CRON_DEFAULT_THRESHOLD', 10800); + +/** * New users will be set to the default time zone at registration. */ define('DRUPAL_USER_TIMEZONE_DEFAULT', 0); @@ -196,6 +201,9 @@ function system_theme() { 'arguments' => array('version' => NULL), ), 'system_compact_link' => array(), + 'system_run_cron_image' => array( + 'arguments' => array('image_path' => NULL), + ), )); } @@ -496,6 +504,12 @@ function system_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['system/run-cron-image'] = array( + 'title' => 'Execute cron', + 'page callback' => 'system_run_cron_image', + 'access callback' => 'system_run_cron_image_access', + 'type' => MENU_CALLBACK, + ); $items['admin'] = array( 'title' => 'Administer', 'access arguments' => array('access administration pages'), @@ -2968,3 +2982,104 @@ function system_retrieve_file($url, $destination = NULL, $overwrite = TRUE) { return $local; } + +/** + * Implement hook_page_alter(). + */ +function system_page_alter(&$page) { + // Automatic cron runs. + // @see system_run_cron_image() + if (system_run_cron_image_access()) { + $page['page_bottom']['run_cron'] = array( + // Trigger cron run via AJAX. + '#attached_js' => array( + '(function($){ $.get(' . drupal_to_js(url('system/run-cron-image')) . '); })(jQuery);' => array('type' => 'inline', 'scope' => 'header'), + ), + // Trigger cron run for clients not supporting JavaScript (fall-back). + '#markup' => theme('system_run_cron_image', 'system/run-cron-image'), + ); + } +} + +/** + * Menu callback; executes cron via an image callback. + * + * This callback runs cron in a separate HTTP request to prevent "mysterious" + * slow-downs of regular HTTP requests. It is either invoked via an AJAX request + * (if the client's browser supports JavaScript) or by an IMG tag directly in + * the page output (for clients not supporting JavaScript). For the latter case, + * we need to output a transparent 1x1 image, so the browser does not render the + * image's alternate text or a 'missing image placeholder'. The AJAX request + * does not process the returned output. + * + * @see system_page_alter() + * @see theme_system_run_cron_image() + * @see system_run_cron_image_access() + */ +function system_run_cron_image() { + drupal_page_is_cacheable(FALSE); + + // Output a transparent 1x1 image to the browser; required for clients not + // supporting JavaScript. + drupal_set_header('Content-Type', 'image/gif'); + echo "\x47\x49\x46\x38\x39\x61\x1\x0\x1\x0\x80\xff\x0\xc0\xc0\xc0\x0\x0\x0\x21\xf9\x4\x1\x0\x0\x0\x0\x2c\x0\x0\x0\x0\x1\x0\x1\x0\x0\x2\x2\x44\x1\x0\x3b"; + + // Cron threshold semaphore is used to avoid errors every time the image + // callback is displayed when a previous cron is still running. + $threshold_semaphore = variable_get('cron_threshold_semaphore', FALSE); + if ($threshold_semaphore) { + if (REQUEST_TIME - $threshold_semaphore > 3600) { + // Either cron has been running for more than an hour or the semaphore + // was not reset due to a database error. + watchdog('cron', 'Cron has been running for more than an hour and is most likely stuck.', array(), WATCHDOG_ERROR); + + // Release the cron threshold semaphore. + variable_del('cron_threshold_semaphore'); + } + } + else { + // Run cron automatically if it has never run or threshold was crossed. + $cron_last = variable_get('cron_last', NULL); + $cron_threshold = variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD); + if (!isset($cron_last) || (REQUEST_TIME - $cron_last > $cron_threshold)) { + // Lock cron threshold semaphore. + variable_set('cron_threshold_semaphore', REQUEST_TIME); + drupal_cron_run(); + // Release the cron threshold semaphore. + variable_del('cron_threshold_semaphore'); + } + } + + exit; +} + +/** + * Checks if the feature to automatically run cron is enabled. + * + * Also used as a menu access callback for this feature. + * + * @return + * TRUE if cron threshold is enabled, FALSE otherwise. + * + * @see system_run_cron_image() + * @see system_page_alter() + */ +function system_run_cron_image_access() { + return variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD) > 0; +} + +/** + * Display image used to run cron automatically. + * + * Renders an image pointing to the automatic cron run menu callback for + * graceful degradation when Javascript is not available. The wrapping NOSCRIPT + * tag ensures that only browsers not supporting JavaScript render the image. + * + * @see system_page_alter() + * @see system_run_cron_image() + * @ingroup themeable + */ +function theme_system_run_cron_image($image_path) { + return '<noscript><div id="system-cron-image">' . theme('image', $image_path, '', '', array(), FALSE) . '</div></noscript>'; +} + diff --git a/modules/system/system.test b/modules/system/system.test index 2c642adff..92d0e32dc 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -372,6 +372,7 @@ class CronRunTestCase extends DrupalWebTestCase { */ function testCronRun() { global $base_url; + // Run cron anonymously without any cron key. $this->drupalGet($base_url . '/cron.php', array('external' => TRUE)); $this->assertResponse(403); @@ -391,13 +392,57 @@ class CronRunTestCase extends DrupalWebTestCase { } /** + * Follow every image paths in the previously retrieved content. + */ + function drupalGetAllImages() { + foreach ($this->xpath('//img') as $image) { + $this->drupalGet($this->getAbsoluteUrl($image['src'])); + } + } + + /** + * Ensure that the cron image callback to run it automatically is working. + * + * In these tests we do not use REQUEST_TIME to track start time, because we + * need the exact time when cron is triggered. + */ + function testCronThreshold() { + // Ensure cron does not run when the cron threshold is enabled and was + // not passed. + $start_cron_last = time(); + variable_set('cron_last', $start_cron_last); + variable_set('cron_safe_threshold', 10); + $this->drupalGet(''); + // Follow every image path on the page. + $this->drupalGetAllImages(); + $this->assertTrue($start_cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is not passed.')); + + // Test if cron runs when the cron threshold was passed. + $start_cron_last = time() - 15; + variable_set('cron_last', $start_cron_last); + $this->drupalGet(''); + // Follow every image path on the page. + $this->drupalGetAllImages(); + $this->assertTrue(variable_get('cron_last', NULL) > $start_cron_last, t('Cron runs when the cron threshold is passed.')); + + // Test if cron does not run when the cron threshold was is disabled. + $start_cron_last = time() - 15; + variable_set('cron_safe_threshold', 0); + variable_set('cron_last', $start_cron_last); + $this->drupalGet(''); + // Follow every image path on the page. + $this->drupalGetAllImages(); + $this->assertTrue($start_cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is disabled.')); + } + + /** * Ensure that temporary files are removed. + * + * Create files for all the possible combinations of age and status. We are + * using UPDATE statments rather than file_save() because it would set the + * timestamp. */ function testTempFileCleanup() { - // Create files for all the possible combinations of age and status. We're - // using UPDATE statments rather than file_save() because it would set the - // timestamp. - // Temporary file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE. $temp_old = file_save_data(''); db_update('file') |