diff --git a/admin/tool/dbtransfer/locallib.php b/admin/tool/dbtransfer/locallib.php index 3357365165d..c7e622ff6bc 100644 --- a/admin/tool/dbtransfer/locallib.php +++ b/admin/tool/dbtransfer/locallib.php @@ -165,7 +165,7 @@ function tool_dbtransfer_get_drivers() { function tool_dbtransfer_create_maintenance_file() { global $CFG; - register_shutdown_function('tool_dbtransfer_maintenance_callback'); + core_shutdown_manager::register_function('tool_dbtransfer_maintenance_callback'); $options = new stdClass(); $options->trusted = false; diff --git a/backup/cc/cc_lib/gral_lib/cssparser.php b/backup/cc/cc_lib/gral_lib/cssparser.php index 1dcbee8858d..3d5653f33e5 100644 --- a/backup/cc/cc_lib/gral_lib/cssparser.php +++ b/backup/cc/cc_lib/gral_lib/cssparser.php @@ -20,7 +20,7 @@ class cssparser { function cssparser($html = true) { // Register "destructor" - register_shutdown_function(array(&$this, "finalize")); + core_shutdown_manager::register_function(array(&$this, "finalize")); $this->html = ($html != false); $this->Clear(); } diff --git a/lib/classes/session/database.php b/lib/classes/session/database.php index 4216980dff2..dfa2e1ea504 100644 --- a/lib/classes/session/database.php +++ b/lib/classes/session/database.php @@ -79,8 +79,6 @@ class database extends handler { if (!$result) { throw new exception('dbsessionhandlerproblem', 'error'); } - - register_shutdown_function(array($this, 'handler_shutdown')); } /** @@ -305,11 +303,4 @@ class database extends handler { $this->database->delete_records_select('sessions', 'userid = 0 AND timemodified < :purgebefore', $params); return true; } - - /** - * This makes sure the session is written to disk at the end of request. - */ - public function handler_shutdown() { - $this->database->dispose(); - } } diff --git a/lib/classes/shutdown_manager.php b/lib/classes/shutdown_manager.php new file mode 100644 index 00000000000..8b01ba0d759 --- /dev/null +++ b/lib/classes/shutdown_manager.php @@ -0,0 +1,160 @@ +. + +/** + * Shutdown management class. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Shutdown management class. + * + * @package core + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_shutdown_manager { + /** @var array list of custom callbacks */ + protected static $callbacks = array(); + /** @var bool is this manager already registered? */ + protected static $registered = false; + + /** + * Register self as main shutdown handler. + * + * @private to be called from lib/setup.php only! + */ + public static function initialize() { + if (self::$registered) { + debugging('Shutdown manager is already initialised!'); + } + self::$registered = true; + register_shutdown_function(array('core_shutdown_manager', 'shutdown_handler')); + } + + /** + * Register custom shutdown function. + * + * @param callable $callback + * @param array $params + */ + public static function register_function($callback, array $params = null) { + self::$callbacks[] = array($callback, $params); + } + + /** + * @private - do NOT call directly. + */ + public static function shutdown_handler() { + global $DB; + + // Custom stuff first. + foreach (self::$callbacks as $data) { + list($callback, $params) = $data; + try { + if (!is_callable($callback)) { + error_log('Invalid custom shutdown function detected '.var_export($callback, true)); + continue; + } + if ($params === null) { + call_user_func($callback); + } else { + call_user_func_array($callback, $params); + } + } catch (Exception $e) { + error_log('Exception ignored in shutdown function '.var_export($callback, true).':'.$e->getMessage()); + } + } + + // Handle DB transactions, session need to be written afterwards + // in order to maintain consistency in all session handlers. + if ($DB->is_transaction_started()) { + if (!defined('PHPUNIT_TEST') or !PHPUNIT_TEST) { + // This should not happen, it usually indicates wrong catching of exceptions, + // because all transactions should be finished manually or in default exception handler. + $backtrace = $DB->get_transaction_start_backtrace(); + error_log('Potential coding error - active database transaction detected during request shutdown:'."\n".format_backtrace($backtrace, true)); + } + $DB->force_transaction_rollback(); + } + + // Close sessions - do it here to make it consistent for all session handlers. + \core\session\manager::write_close(); + + // Other cleanup. + self::request_shutdown(); + + // Stop profiling. + if (function_exists('profiling_is_running')) { + if (profiling_is_running()) { + profiling_stop(); + } + } + + // NOTE: do not dispose $DB and MUC here, they might be used from legacy shutdown functions. + } + + /** + * Standard shutdown sequence. + */ + protected static function request_shutdown() { + global $CFG; + + // Help apache server if possible. + $apachereleasemem = false; + if (function_exists('apache_child_terminate') && function_exists('memory_get_usage') && ini_get_bool('child_terminate')) { + $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); // 64MB default. + if (memory_get_usage() > get_real_size($limit)) { + $apachereleasemem = $limit; + @apache_child_terminate(); + } + } + + // Deal with perf logging. + if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) { + if ($apachereleasemem) { + error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.'); + } + if (defined('MDL_PERFTOLOG')) { + $perf = get_performance_info(); + error_log("PERF: " . $perf['txt']); + } + if (defined('MDL_PERFINC')) { + $inc = get_included_files(); + $ts = 0; + foreach ($inc as $f) { + if (preg_match(':^/:', $f)) { + $fs = filesize($f); + $ts += $fs; + $hfs = display_size($fs); + error_log(substr($f, strlen($CFG->dirroot)) . " size: $fs ($hfs)", null, null, 0); + } else { + error_log($f , null, null, 0); + } + } + if ($ts > 0 ) { + $hts = display_size($ts); + error_log("Total size of files included: $ts ($hts)"); + } + } + } + } +} diff --git a/lib/dml/moodle_database.php b/lib/dml/moodle_database.php index 815248758b1..e8a5aae7ffb 100644 --- a/lib/dml/moodle_database.php +++ b/lib/dml/moodle_database.php @@ -336,6 +336,19 @@ abstract class moodle_database { return false; } + /** + * Returns transaction trace for debugging purposes. + * @private to be used by core only + * @return array or null if not in transaction. + */ + public function get_transaction_start_backtrace() { + if (!$this->transactions) { + return null; + } + $lowesttransaction = end($this->transactions); + return $lowesttransaction->get_backtrace(); + } + /** * Closes the database connection and releases all resources * and memory (especially circular memory references). @@ -348,24 +361,8 @@ abstract class moodle_database { } $this->disposed = true; if ($this->transactions) { - // this should not happen, it usually indicates wrong catching of exceptions, - // because all transactions should be finished manually or in default exception handler. - // unfortunately we can not access global $CFG any more and can not print debug, - // the diagnostic info should be printed in footer instead - $lowesttransaction = end($this->transactions); - $backtrace = $lowesttransaction->get_backtrace(); - - if (defined('PHPUNIT_TEST') and PHPUNIT_TEST) { - //no need to log sudden exits in our PHPUnit test cases - } else { - error_log('Potential coding error - active database transaction detected when disposing database:'."\n".format_backtrace($backtrace, true)); - } $this->force_transaction_rollback(); } - // Always terminate sessions here to make it consistent, - // this is needed because we need to save session to db before closing it. - \core\session\manager::write_close(); - $this->used_for_db_sessions = false; if ($this->temptables) { $this->temptables->dispose(); @@ -376,9 +373,6 @@ abstract class moodle_database { $this->database_manager = null; } $this->tables = null; - - // We do not need the MUC cache any more, - // if we did not keep it as property it might be already gone before we saved the session. $this->metacache = null; } @@ -2330,7 +2324,7 @@ abstract class moodle_database { } // now enable transactions again - $this->transactions = array(); // unfortunately all unfinished exceptions are kept in memory + $this->transactions = array(); $this->force_rollback = false; } diff --git a/lib/filelib.php b/lib/filelib.php index 8127f408497..9e031ba4726 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -2179,7 +2179,7 @@ function send_temp_file($path, $filename, $pathisstring=false) { print_error('filenotfound', 'error', $CFG->wwwroot.'/'); } // executed after normal finish or abort - @register_shutdown_function('send_temp_file_finished', $path); + core_shutdown_manager::register_function('send_temp_file_finished', array($path)); } // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 5e714a42f67..69151fdccb6 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -8542,58 +8542,6 @@ function fullclone($thing) { return unserialize(serialize($thing)); } - -/** - * This function expects to called during shutdown should be set via register_shutdown_function() in lib/setup.php . - * - * @return void - */ -function moodle_request_shutdown() { - global $CFG; - - // Help apache server if possible. - $apachereleasemem = false; - if (function_exists('apache_child_terminate') && function_exists('memory_get_usage') - && ini_get_bool('child_terminate')) { - - $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); // 64MB default. - if (memory_get_usage() > get_real_size($limit)) { - $apachereleasemem = $limit; - @apache_child_terminate(); - } - } - - // Deal with perf logging. - if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) { - if ($apachereleasemem) { - error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.'); - } - if (defined('MDL_PERFTOLOG')) { - $perf = get_performance_info(); - error_log("PERF: " . $perf['txt']); - } - if (defined('MDL_PERFINC')) { - $inc = get_included_files(); - $ts = 0; - foreach ($inc as $f) { - if (preg_match(':^/:', $f)) { - $fs = filesize($f); - $ts += $fs; - $hfs = display_size($fs); - error_log(substr($f, strlen($CFG->dirroot)) . " size: $fs ($hfs)" - , null, null, 0); - } else { - error_log($f , null, null, 0); - } - } - if ($ts > 0 ) { - $hts = display_size($ts); - error_log("Total size of files included: $ts ($hts)"); - } - } - } -} - /** * If new messages are waiting for the current user, then insert * JavaScript to pop up the messaging window into the page diff --git a/lib/navigationlib.php b/lib/navigationlib.php index 63ad2529aa6..46c9284118b 100644 --- a/lib/navigationlib.php +++ b/lib/navigationlib.php @@ -4668,7 +4668,7 @@ class navigation_cache { public function volatile($setting = true) { if (self::$volatilecaches===null) { self::$volatilecaches = array(); - register_shutdown_function(array('navigation_cache','destroy_volatile_caches')); + core_shutdown_manager::register_function(array('navigation_cache','destroy_volatile_caches')); } if ($setting) { diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 1ca922ad352..1d77f489a9d 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -883,9 +883,6 @@ class core_renderer extends renderer_base { $performanceinfo = ''; if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) { $perf = get_performance_info(); - if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) { - error_log("PERF: " . $perf['txt']); - } if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) { $performanceinfo = $perf['html']; } diff --git a/lib/setup.php b/lib/setup.php index 586f10f0b68..fe4d55c8d80 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -372,9 +372,7 @@ if (defined('ABORT_AFTER_CONFIG')) { // Early profiling start, based exclusively on config.php $CFG settings if (!empty($CFG->earlyprofilingenabled)) { require_once($CFG->libdir . '/xhprof/xhprof_moodle.php'); - if (profiling_start()) { - register_shutdown_function('profiling_stop'); - } + profiling_start(); } /** @@ -622,6 +620,9 @@ if (!isset($CFG->debugdisplay)) { ini_set('display_errors', '1'); } +// Register our shutdown manager, do NOT use register_shutdown_function(). +core_shutdown_manager::initialize(); + // Verify upgrade is not running unless we are in a script that needs to execute in any case if (!defined('NO_UPGRADE_CHECK') and isset($CFG->upgraderunning)) { if ($CFG->upgraderunning < time()) { @@ -642,11 +643,6 @@ if (function_exists('gc_enable')) { gc_enable(); } -// Register default shutdown tasks - such as Apache memory release helper, perf logging, etc. -if (function_exists('register_shutdown_function')) { - register_shutdown_function('moodle_request_shutdown'); -} - // detect unsupported upgrade jump as soon as possible - do not change anything, do not use system functions if (!empty($CFG->version) and $CFG->version < 2007101509) { print_error('upgraderequires19', 'error'); @@ -773,9 +769,7 @@ if (!PHPUNIT_TEST and !defined('BEHAT_TEST')) { // Late profiling, only happening if early one wasn't started if (!empty($CFG->profilingenabled)) { require_once($CFG->libdir . '/xhprof/xhprof_moodle.php'); - if (profiling_start()) { - register_shutdown_function('profiling_stop'); - } + profiling_start(); } // Process theme change in the URL. diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 4643fc1ca43..d1c9ce72540 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -41,6 +41,7 @@ information provided here is intended especially for developers. * Each plugin should include version information in version.php. * Module and block tables do not contain version column any more, use get_config('xx_yy', 'version') instead. * $USER->password field is intentionally unset so that session data does not contain password hashes. +* Use core_shutdown_manager::register_function() instead of register_shutdown_function(). DEPRECATIONS: Various previously deprecated functions have now been altered to throw DEBUG_DEVELOPER debugging notices diff --git a/lib/upgradelib.php b/lib/upgradelib.php index 12ed9b9a825..f9eda590c1e 100644 --- a/lib/upgradelib.php +++ b/lib/upgradelib.php @@ -1257,7 +1257,7 @@ function upgrade_started($preinstall=false) { } ignore_user_abort(true); - register_shutdown_function('upgrade_finished_handler'); + core_shutdown_manager::register_function('upgrade_finished_handler'); upgrade_setup_debug(true); set_config('upgraderunning', time()+300); $started = true; diff --git a/lib/weblib.php b/lib/weblib.php index 844fd5c7ed2..4bfabeb6b51 100644 --- a/lib/weblib.php +++ b/lib/weblib.php @@ -2615,13 +2615,6 @@ function redirect($url, $message='', $delay=-1) { $delay = 0; } - if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) { - if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) { - $perf = get_performance_info(); - error_log("PERF: " . $perf['txt']); - } - } - // Make sure the session is closed properly, this prevents problems in IIS // and also some potential PHP shutdown issues. \core\session\manager::write_close();