Merge branch 'w41_MDL-42040_m26_shutdown' of https://github.com/skodak/moodle
authorSam Hemelryk <sam@moodle.com>
Mon, 7 Oct 2013 04:38:25 +0000 (17:38 +1300)
committerSam Hemelryk <sam@moodle.com>
Mon, 7 Oct 2013 04:38:25 +0000 (17:38 +1300)
13 files changed:
admin/tool/dbtransfer/locallib.php
backup/cc/cc_lib/gral_lib/cssparser.php
lib/classes/session/database.php
lib/classes/shutdown_manager.php [new file with mode: 0644]
lib/dml/moodle_database.php
lib/filelib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/setup.php
lib/upgrade.txt
lib/upgradelib.php
lib/weblib.php

index 3357365..c7e622f 100644 (file)
@@ -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;
index 1dcbee8..3d5653f 100644 (file)
@@ -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();
   }
index 4216980..dfa2e1e 100644 (file)
@@ -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 (file)
index 0000000..8b01ba0
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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)");
+                }
+            }
+        }
+    }
+}
index 8152487..e8a5aae 100644 (file)
@@ -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;
     }
 
index 8127f40..9e031ba 100644 (file)
@@ -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
index 5e714a4..69151fd 100644 (file)
@@ -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
index 63ad252..46c9284 100644 (file)
@@ -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) {
index 1ca922a..1d77f48 100644 (file)
@@ -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'];
             }
index 586f10f..fe4d55c 100644 (file)
@@ -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.
index 4643fc1..d1c9ce7 100644 (file)
@@ -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
index 12ed9b9..f9eda59 100644 (file)
@@ -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;
index 844fd5c..4bfabeb 100644 (file)
@@ -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();