diff --git a/admin/cli/alternative_component_cache.php b/admin/cli/alternative_component_cache.php new file mode 100644 index 00000000000..6b3d454c997 --- /dev/null +++ b/admin/cli/alternative_component_cache.php @@ -0,0 +1,111 @@ +. + +/** + * This hack is intended for clustered sites that do not want + * to use shared cachedir for component cache. + * + * This file needs to be called after any change in PHP files in dataroot, + * that is before upgrade and install. + * + * @package core + * @copyright 2013 Petr Skoda (skodak) {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +define('CLI_SCRIPT', true); +define('ABORT_AFTER_CONFIG', true); // We need just the values from config.php. +define('CACHE_DISABLE_ALL', true); // This prevents reading of existing caches. +define('IGNORE_COMPONENT_CACHE', true); + +require(__DIR__.'/../../config.php'); +require_once($CFG->libdir.'/clilib.php'); + +// Now get cli options. +list($options, $unrecognized) = cli_get_params( + array( + 'file' => false, + 'rebuild' => false, + 'print' => false, + 'help' => false + ), + array( + 'h' => 'help' + ) +); + +if ($unrecognized) { + $unrecognized = implode("\n ", $unrecognized); + cli_error(get_string('cliunknowoption', 'admin', $unrecognized), 2); +} + +if (!$options['rebuild'] and !$options['file'] and !$options['print']) { + $help = +"Create alternative component cache file + +Options: +-h, --help Print out this help +--rebuild Rebuild \$CFG->alternative_component_cache file +--file=filepath Save component cache to file +--print Print component cache file content + +Example: +\$ php admin/cli/rebuild_alternative_component_cache.php --rebuild +"; + + echo $help; + exit(0); +} + +error_reporting(E_ALL | E_STRICT); +ini_set('display_errors', 1); + +$content = core_component::get_cache_content(); + +if ($options['print']) { + echo $content; + exit(0); +} + +if ($options['rebuild']) { + if (empty($CFG->alternative_component_cache)) { + fwrite(STDERR, 'config.php does not contain $CFG->alternative_component_cache setting'); + fwrite(STDERR, "\n"); + exit(2); + } + $target = $CFG->alternative_component_cache; +} else { + $target = $options['file']; +} + +if (!$target) { + fwrite(STDERR, "Invalid target file $target"); + fwrite(STDERR, "\n"); + exit(1); +} + +$bytes = file_put_contents($target, $content); + +if (!$bytes) { + fwrite(STDERR, "Error writing to $target"); + fwrite(STDERR, "\n"); + exit(1); +} + +// Success. +echo "File $target was updated\n"; +exit(0); diff --git a/admin/cli/install.php b/admin/cli/install.php index f0bee315f6a..c2d08d129df 100644 --- a/admin/cli/install.php +++ b/admin/cli/install.php @@ -139,6 +139,8 @@ define('CACHE_DISABLE_ALL', true); define('PHPUNIT_TEST', false); +define('IGNORE_COMPONENT_CACHE', true); + // Check that PHP is of a sufficient version if (version_compare(phpversion(), "5.3.3") < 0) { $phpversion = phpversion(); diff --git a/admin/tool/behat/cli/init.php b/admin/tool/behat/cli/init.php index 9ac693183cf..5547b56eae3 100644 --- a/admin/tool/behat/cli/init.php +++ b/admin/tool/behat/cli/init.php @@ -34,6 +34,7 @@ if (function_exists('opcache_reset')) { // Is not really necessary but adding it as is a CLI_SCRIPT. define('CLI_SCRIPT', true); +define('CACHE_DISABLE_ALL', true); // Basic functions. require_once(__DIR__ . '/../../../../lib/clilib.php'); diff --git a/admin/tool/behat/cli/util.php b/admin/tool/behat/cli/util.php index b28f4fcb6b0..cef4ea7e126 100644 --- a/admin/tool/behat/cli/util.php +++ b/admin/tool/behat/cli/util.php @@ -84,6 +84,7 @@ define('BEHAT_UTIL', true); define('CLI_SCRIPT', true); define('ABORT_AFTER_CONFIG', true); define('NO_OUTPUT_BUFFERING', true); +define('IGNORE_COMPONENT_CACHE', true); error_reporting(E_ALL | E_STRICT); ini_set('display_errors', '1'); diff --git a/admin/tool/phpunit/cli/init.php b/admin/tool/phpunit/cli/init.php index a3d875dfc61..eb807ee4d14 100644 --- a/admin/tool/phpunit/cli/init.php +++ b/admin/tool/phpunit/cli/init.php @@ -32,6 +32,8 @@ if (function_exists('opcache_reset')) { opcache_reset(); } +define('IGNORE_COMPONENT_CACHE', true); + require_once(__DIR__.'/../../../../lib/clilib.php'); require_once(__DIR__.'/../../../../lib/phpunit/bootstraplib.php'); require_once(__DIR__.'/../../../../lib/testing/lib.php'); diff --git a/admin/tool/phpunit/cli/util.php b/admin/tool/phpunit/cli/util.php index 035ce7b9e26..be593a27e7e 100644 --- a/admin/tool/phpunit/cli/util.php +++ b/admin/tool/phpunit/cli/util.php @@ -28,6 +28,8 @@ if (isset($_SERVER['REMOTE_ADDR'])) { die; // no access from web! } +define('IGNORE_COMPONENT_CACHE', true); + require_once(__DIR__.'/../../../../lib/clilib.php'); require_once(__DIR__.'/../../../../lib/phpunit/bootstraplib.php'); require_once(__DIR__.'/../../../../lib/testing/lib.php'); diff --git a/install.php b/install.php index 950b39a4f7d..f591b00c523 100644 --- a/install.php +++ b/install.php @@ -47,6 +47,7 @@ define('CLI_SCRIPT', false); // prevents some warnings later define('AJAX_SCRIPT', false); // prevents some warnings later define('CACHE_DISABLE_ALL', true); // Disables caching.. just in case. define('PHPUNIT_TEST', false); +define('IGNORE_COMPONENT_CACHE', true); // Servers should define a default timezone in php.ini, but if they don't then make sure something is defined. // This is a quick hack. Ideally we should ask the admin for a value. See MDL-22625 for more on this. diff --git a/lib/classes/component.php b/lib/classes/component.php index 0c7a8fa7bc8..29391cd1eb0 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -79,20 +79,44 @@ class core_component { return; } - if (PHPUNIT_TEST or !empty($CFG->early_install_lang) or - (defined('BEHAT_UTIL') and BEHAT_UTIL) or - (defined('BEHAT_TEST') and BEHAT_TEST)) { - // 1/ Do not bother storing the file for unit tests, - // we need fresh copy for each execution and - // later we keep it in memory. - // 2/ We can not write to dataroot in installer yet. + if (defined('IGNORE_COMPONENT_CACHE') and IGNORE_COMPONENT_CACHE) { self::fill_all_caches(); return; } - // Note: cachedir MUST be shared by all servers in a cluster, sorry guys... - // MUC should use classloading, we can not depend on it here. - $cachefile = "$CFG->cachedir/core_component.php"; + if (!empty($CFG->alternative_component_cache)) { + // Hack for heavily clustered sites that want to manage component cache invalidation manually. + $cachefile = $CFG->alternative_component_cache; + + if (file_exists($cachefile)) { + if (CACHE_DISABLE_ALL) { + // Verify the cache state only on upgrade pages. + $content = self::get_cache_content(); + if (sha1_file($cachefile) !== sha1($content)) { + die('Outdated component cache file defined in $CFG->alternative_component_cache, can not continue'); + } + return; + } + $cache = array(); + include($cachefile); + self::$plugintypes = $cache['plugintypes']; + self::$plugins = $cache['plugins']; + self::$subsystems = $cache['subsystems']; + self::$classmap = $cache['classmap']; + return; + } + + if (!is_writable(dirname($cachefile))) { + die('Can not create alternative component cache file defined in $CFG->alternative_component_cache, can not continue'); + } + + // Lets try to create the file, it might be in some writable directory or a local cache dir. + + } else { + // Note: $CFG->cachedir MUST be shared by all servers in a cluster, + // use $CFG->alternative_component_cache if you do not like it. + $cachefile = "$CFG->cachedir/core_component.php"; + } if (!CACHE_DISABLE_ALL and !self::is_developer()) { // 1/ Use the cache only outside of install and upgrade. @@ -105,7 +129,7 @@ class core_component { } else if (!isset($cache['plugintypes']) or !isset($cache['plugins']) or !isset($cache['subsystems']) or !isset($cache['classmap'])) { // Something is very wrong. } else if ($cache['plugintypes']['mod'] !== "$CFG->dirroot/mod") { - // Dirroot was changed. + // $CFG->dirroot was changed. } else { // The cache looks ok, let's use it. self::$plugintypes = $cache['plugintypes']; @@ -114,17 +138,12 @@ class core_component { self::$classmap = $cache['classmap']; return; } + // Note: we do not verify $CFG->admin here intentionally, + // they must visit admin/index.php after any change. } } - $cachedir = dirname($cachefile); - if (!is_dir($cachedir)) { - mkdir($cachedir, $CFG->directorypermissions, true); - } - if (!isset(self::$plugintypes)) { - self::fill_all_caches(); - // This needs to be atomic and self-fixing as much as possible. $content = self::get_cache_content(); @@ -132,9 +151,15 @@ class core_component { if (sha1_file($cachefile) === sha1($content)) { return; } + // Stale cache detected! unlink($cachefile); } + $cachedir = dirname($cachefile); + if (!is_dir($cachedir)) { + mkdir($cachedir, $CFG->directorypermissions, true); + } + if ($fp = @fopen($cachefile.'.tmp', 'xb')) { fwrite($fp, $content); fclose($fp); @@ -157,11 +182,16 @@ class core_component { protected static function is_developer() { global $CFG; - if (!isset($CFG->config_php_settings['debug'])) { + if (isset($CFG->config_php_settings['debug'])) { + // Standard moodle script. + $debug = (int)$CFG->config_php_settings['debug']; + } else if (isset($CFG->debug)) { + // Usually script with ABORT_AFTER_CONFIG. + $debug = (int)$CFG->debug; + } else { return false; } - $debug = (int)$CFG->config_php_settings['debug']; if ($debug & E_ALL and $debug & E_STRICT) { return true; } @@ -172,9 +202,15 @@ class core_component { /** * Create cache file content. * + * @private this is intended for $CFG->alternative_component_cache only. + * * @return string */ - protected static function get_cache_content() { + public static function get_cache_content() { + if (!isset(self::$plugintypes)) { + self::fill_all_caches(); + } + $cache = array( 'subsystems' => self::$subsystems, 'plugintypes' => self::$plugintypes, diff --git a/lib/phpunit/bootstrap.php b/lib/phpunit/bootstrap.php index 0593812cad1..6dee6211041 100644 --- a/lib/phpunit/bootstrap.php +++ b/lib/phpunit/bootstrap.php @@ -40,6 +40,10 @@ if (ini_get('opcache.enable') and strtolower(ini_get('opcache.enable')) !== 'off } } +if (!defined('IGNORE_COMPONENT_CACHE')) { + define('IGNORE_COMPONENT_CACHE', true); +} + require_once(__DIR__.'/bootstraplib.php'); require_once(__DIR__.'/../testing/lib.php'); require_once(__DIR__.'/classes/autoloader.php');