Commit 8d01aeb4 authored by webchick's avatar webchick

#710142 by Berdir, moshe weitzman, chx: Handle exceptions in shutdown...

#710142 by Berdir, moshe weitzman, chx: Handle exceptions in shutdown functions (with tests). Hopefully the last of these weird 'Stack frame in Unknown line 0' errors.
parent 8847f4c9
......@@ -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) {
......
......@@ -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));
}
}
......@@ -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);
......
......@@ -85,7 +85,7 @@ public function nextId($existing_id = 0) {
$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;
......
......@@ -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;
}
......
......@@ -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;
}
}
......
......@@ -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).
......
......@@ -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
......
......@@ -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));
}
......@@ -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');
}
/**
......
......@@ -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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment