summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--includes/batch.inc2
-rw-r--r--includes/bootstrap.inc56
-rw-r--r--includes/common.inc4
-rw-r--r--includes/database/mysql/database.inc2
-rw-r--r--includes/lock.inc2
-rw-r--r--includes/menu.inc6
-rw-r--r--includes/session.inc2
-rw-r--r--modules/search/search.module2
-rw-r--r--modules/simpletest/tests/system_test.module33
-rw-r--r--modules/system/system.api.php2
-rw-r--r--modules/system/system.test28
11 files changed, 128 insertions, 11 deletions
diff --git a/includes/batch.inc b/includes/batch.inc
index 850eff710..7fcc91566 100644
--- a/includes/batch.inc
+++ b/includes/batch.inc
@@ -58,7 +58,7 @@ function _batch_page() {
}
// Register database update for the end of processing.
- register_shutdown_function('_batch_shutdown');
+ drupal_register_shutdown_function('_batch_shutdown');
// Add batch-specific CSS.
foreach ($batch['sets'] as $batch_set) {
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 13c912cf4..05a654bf1 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -2644,3 +2644,59 @@ function drupal_is_cli() {
function drupal_placeholder($variables) {
return '<em class="placeholder">' . check_plain($variables['text']) . '</em>';
}
+
+/**
+ *
+ * Register a function for execution on shutdown.
+ *
+ * Wrapper for register_shutdown_function() which catches thrown exceptions
+ * to avoid "Exception thrown without a stack frame in Unknown".
+ *
+ * @param $callback
+ * The shutdown function to register.
+ * @param $parameters
+ * It is possible to pass parameters to the shutdown function by passing
+ * additional parameters.
+ * @return
+ * Array of shutdown functions to be executed.
+ *
+ * @see register_shutdown_function()
+ */
+function &drupal_register_shutdown_function($callback = NULL, $parameters = NULL) {
+ // We cannot use drupal_static() here because the static cache is reset
+ // during batch processing, which breaks batch handling.
+ static $callbacks = array();
+
+ if (isset($callback)) {
+ // Only register the internal shutdown function once.
+ if (empty($callbacks)) {
+ register_shutdown_function('_drupal_shutdown_function');
+ }
+ $args = func_get_args();
+ array_shift($args);
+ // Save callback and arguments
+ $callbacks[] = array('callback' => $callback, 'arguments' => $args);
+ }
+ return $callbacks;
+}
+
+/**
+ * Internal function used to execute registered shutdown functions.
+ */
+function _drupal_shutdown_function() {
+ $callbacks = &drupal_register_shutdown_function();
+
+ try {
+ while (list($key, $callback) = each($callbacks)) {
+ call_user_func_array($callback['callback'], $callback['arguments']);
+ }
+ }
+ catch(Exception $exception) {
+ require_once DRUPAL_ROOT . '/includes/errors.inc';
+ // The theme has already been printed so it doesn't make much sense to use
+ // drupal_exception_handler() because that would display the maintenaince
+ // page below the usual page. For now, just print the error for debugging.
+ // @todo: Improve this.
+ print t('%type: %message in %function (line %line of %file).', _drupal_decode_exception($exception));
+ }
+}
diff --git a/includes/common.inc b/includes/common.inc
index f41982da9..82180255d 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -4017,7 +4017,7 @@ function _drupal_bootstrap_full() {
// Load all enabled modules
module_load_all();
// Register automated cron run handler.
- register_shutdown_function('system_run_automated_cron');
+ drupal_register_shutdown_function('system_run_automated_cron');
// Make sure all stream wrappers are registered.
file_get_stream_wrappers();
if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'simpletest') !== FALSE) {
@@ -4130,7 +4130,7 @@ function drupal_cron_run() {
DrupalQueue::get($queue_name)->createQueue();
}
// Register shutdown callback
- register_shutdown_function('drupal_cron_cleanup');
+ drupal_register_shutdown_function('drupal_cron_cleanup');
// Lock cron semaphore
variable_set('cron_semaphore', REQUEST_TIME);
diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc
index 39ba59e4b..46acb4dda 100644
--- a/includes/database/mysql/database.inc
+++ b/includes/database/mysql/database.inc
@@ -85,7 +85,7 @@ class DatabaseConnection_mysql extends DatabaseConnection {
$new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
}
if (!$shutdown_registered) {
- register_shutdown_function(array(get_class($this), 'nextIdDelete'));
+ drupal_register_shutdown_function(array(get_class($this), 'nextIdDelete'));
$shutdown_registered = TRUE;
}
return $new_id;
diff --git a/includes/lock.inc b/includes/lock.inc
index 5d03cdd99..3b649a050 100644
--- a/includes/lock.inc
+++ b/includes/lock.inc
@@ -84,7 +84,7 @@ function _lock_id() {
// Assign a unique id.
$lock_id = uniqid(mt_rand(), TRUE);
// We only register a shutdown function if a lock is used.
- register_shutdown_function('lock_release_all', $lock_id);
+ drupal_register_shutdown_function('lock_release_all', $lock_id);
}
return $lock_id;
}
diff --git a/includes/menu.inc b/includes/menu.inc
index 83d36aa7b..dedb04a10 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -2216,7 +2216,7 @@ function menu_cache_clear($menu_name = 'navigation') {
$cache_cleared[$menu_name] = 1;
}
elseif ($cache_cleared[$menu_name] == 1) {
- register_shutdown_function('cache_clear_all', 'links:' . $menu_name . ':', 'cache_menu', TRUE);
+ drupal_register_shutdown_function('cache_clear_all', 'links:' . $menu_name . ':', 'cache_menu', TRUE);
$cache_cleared[$menu_name] = 2;
}
@@ -2798,9 +2798,9 @@ function _menu_clear_page_cache() {
$cache_cleared = 1;
}
elseif ($cache_cleared == 1) {
- register_shutdown_function('cache_clear_all');
+ drupal_register_shutdown_function('cache_clear_all');
// Keep track of which menus have expanded items.
- register_shutdown_function('_menu_set_expanded_menus');
+ drupal_register_shutdown_function('_menu_set_expanded_menus');
$cache_cleared = 2;
}
}
diff --git a/includes/session.inc b/includes/session.inc
index 5295173e5..6f1d5a8f4 100644
--- a/includes/session.inc
+++ b/includes/session.inc
@@ -72,7 +72,7 @@ function _drupal_session_read($sid) {
// since PHP 5.0.5.
// Thus destructors can use sessions but session handler can't use objects.
// So we are moving session closure before destructing objects.
- register_shutdown_function('session_write_close');
+ drupal_register_shutdown_function('session_write_close');
// Handle the case of first time visitors and clients that don't store
// cookies (eg. web crawlers).
diff --git a/modules/search/search.module b/modules/search/search.module
index 4fefecc60..36a0c1820 100644
--- a/modules/search/search.module
+++ b/modules/search/search.module
@@ -341,7 +341,7 @@ function search_dirty($word = NULL) {
function search_cron() {
// We register a shutdown function to ensure that search_total is always up
// to date.
- register_shutdown_function('search_update_totals');
+ drupal_register_shutdown_function('search_update_totals');
foreach(variable_get('search_active_modules', array('node', 'user')) as $module) {
// Update word index
diff --git a/modules/simpletest/tests/system_test.module b/modules/simpletest/tests/system_test.module
index b37019f8c..a638b70f5 100644
--- a/modules/simpletest/tests/system_test.module
+++ b/modules/simpletest/tests/system_test.module
@@ -94,6 +94,13 @@ function system_test_menu() {
'type' => MENU_CALLBACK,
);
+ $items['system-test/shutdown-functions'] = array(
+ 'title' => 'Test main content duplication',
+ 'page callback' => 'system_test_page_shutdown_functions',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+
return $items;
}
@@ -280,3 +287,29 @@ function system_test_main_content_fallback() {
return t('Content to test main content fallback');
}
+/**
+ * A simple page callback which adds a register shutdown function.
+ */
+function system_test_page_shutdown_functions($arg1, $arg2) {
+ drupal_register_shutdown_function('_system_test_first_shutdown_function', $arg1, $arg2);
+}
+
+/**
+ * Dummy shutdown function which registers another shutdown function.
+ */
+function _system_test_first_shutdown_function($arg1, $arg2) {
+ // Output something, page has already been printed and the session stored
+ // so we can't use drupal_set_message.
+ print t('First shutdown function, arg1 : @arg1, arg2: @arg2', array('@arg1' => $arg1, '@arg2' => $arg2));
+ drupal_register_shutdown_function('_system_test_second_shutdown_function', $arg1, $arg2);
+}
+
+/**
+ * Dummy shutdown function.
+ */
+function _system_test_second_shutdown_function($arg1, $arg2) {
+ // Output something, page has already been printed and the session stored
+ // so we can't use drupal_set_message.
+ print t('Second shutdown function, arg1 : @arg1, arg2: @arg2', array('@arg1' => $arg1, '@arg2' => $arg2));
+}
+
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 5162b69c7..153ddca83 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -877,7 +877,7 @@ function hook_forms($form_id, $args) {
function hook_boot() {
// we need user_access() in the shutdown function. make sure it gets loaded
drupal_load('module', 'user');
- register_shutdown_function('devel_shutdown');
+ drupal_register_shutdown_function('devel_shutdown');
}
/**
diff --git a/modules/system/system.test b/modules/system/system.test
index e33ad11a8..79e141225 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -1598,3 +1598,31 @@ class FloodFunctionalTest extends DrupalWebTestCase {
$this->assertFalse(flood_is_allowed($name, $threshold));
}
}
+
+/**
+ * Functional tests shutdown functions.
+ */
+class ShutdownFunctionsTest extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Shutdown functions',
+ 'description' => 'Functional tests for shutdown functions',
+ 'group' => 'System',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('system_test');
+ }
+
+ /**
+ * Test flood control mechanism clean-up.
+ */
+ function testShutdownFunctions() {
+ $arg1 = $this->randomName();
+ $arg2 = $this->randomName();
+ $this->drupalGet('system-test/shutdown-functions/' . $arg1 . '/' . $arg2);
+ $this->assertText(t('First shutdown function, arg1 : @arg1, arg2: @arg2', array('@arg1' => $arg1, '@arg2' => $arg2)));
+ $this->assertText(t('Second shutdown function, arg1 : @arg1, arg2: @arg2', array('@arg1' => $arg1, '@arg2' => $arg2)));
+ }
+} \ No newline at end of file