- Patch #325665 by chx, Damien Tournoud, justinrandell et al: improved the cache registry lookups.
parent
9d631d22f9
commit
59ece2e39d
modules
|
@ -206,6 +206,16 @@ define('CHECK_PLAIN', 0);
|
|||
*/
|
||||
define('PASS_THROUGH', -1);
|
||||
|
||||
/**
|
||||
* Signals that the registry lookup cache should be reset.
|
||||
*/
|
||||
define('REGISTRY_RESET_LOOKUP_CACHE', 1);
|
||||
|
||||
/**
|
||||
* Signals that the registry lookup cache should be written to storage.
|
||||
*/
|
||||
define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
|
||||
|
||||
/**
|
||||
* @} End of "Title text filtering flags".
|
||||
*/
|
||||
|
@ -655,7 +665,7 @@ function page_get_cache() {
|
|||
* The name of the bootstrap hook we wish to invoke.
|
||||
*/
|
||||
function bootstrap_invoke_all($hook) {
|
||||
foreach (module_list(TRUE, TRUE) as $module) {
|
||||
foreach (module_implements($hook) as $module) {
|
||||
module_invoke($module, $hook);
|
||||
}
|
||||
}
|
||||
|
@ -1412,7 +1422,6 @@ function drupal_function_exists($function) {
|
|||
$checked[$function] = FALSE;
|
||||
|
||||
if (function_exists($function)) {
|
||||
registry_mark_code('function', $function);
|
||||
$checked[$function] = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
@ -1456,8 +1465,52 @@ function drupal_autoload_class($class) {
|
|||
|
||||
/**
|
||||
* Helper to check for a resource in the registry.
|
||||
*
|
||||
* @param $type
|
||||
* The type of resource we are looking up, or one of the constants
|
||||
* REGISTRY_RESET_LOOKUP_CACHE or REGISTRY_WRITE_LOOKUP_CACHE, which
|
||||
* signal that we should reset or write the cache, respectively.
|
||||
* @param $name
|
||||
* The name of the resource, or NULL if either of the REGISTRY_* constants
|
||||
* is passed in.
|
||||
* @return
|
||||
* TRUE if the resource was found, FALSE if not.
|
||||
* NULL if either of the REGISTRY_* constants is passed in as $type.
|
||||
*/
|
||||
function _registry_check_code($type, $name) {
|
||||
function _registry_check_code($type, $name = NULL) {
|
||||
static $lookup_cache, $cache_update_needed;
|
||||
|
||||
if (!isset($lookup_cache)) {
|
||||
$lookup_cache = _registry_get_lookup_cache();
|
||||
}
|
||||
|
||||
// When we rebuild the registry, we need to reset this cache so
|
||||
// we don't keep lookups for resources that changed during the rebuild.
|
||||
if ($type == REGISTRY_RESET_LOOKUP_CACHE) {
|
||||
$cache_update_needed = TRUE;
|
||||
$lookup_cache = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// Called from drupal_page_footer, we write to permanent storage if there
|
||||
// changes to the lookup cache for this request.
|
||||
if ($type == REGISTRY_WRITE_LOOKUP_CACHE) {
|
||||
if ($cache_update_needed) {
|
||||
_registry_set_lookup_cache($lookup_cache);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// $type can be one of 'function', 'interface' or 'class', so we only need the
|
||||
// first letter to keep the cache key unique.
|
||||
$cache_key = $type[0] . $name;
|
||||
if (isset($lookup_cache[$cache_key])) {
|
||||
if ($lookup_cache[$cache_key]) {
|
||||
require_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key];
|
||||
}
|
||||
return $lookup_cache[$cache_key];
|
||||
}
|
||||
|
||||
// This function may get called when the default database is not active, but
|
||||
// there is no reason we'd ever want to not use the default database for
|
||||
// this query.
|
||||
|
@ -1466,38 +1519,20 @@ function _registry_check_code($type, $name) {
|
|||
':type' => $type,
|
||||
))
|
||||
->fetchField();
|
||||
|
||||
// Flag that we've run a lookup query and need to update the cache.
|
||||
$cache_update_needed = TRUE;
|
||||
|
||||
// Misses are valuable information worth caching, so cache even if
|
||||
// $file is FALSE.
|
||||
$lookup_cache[$cache_key] = $file;
|
||||
|
||||
if ($file) {
|
||||
require_once DRUPAL_ROOT . '/' . $file;
|
||||
registry_mark_code($type, $name);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the resources used for this request.
|
||||
*
|
||||
* @param $type
|
||||
* The type of resource.
|
||||
* @param $name
|
||||
* The name of the resource.
|
||||
* @param $return
|
||||
* Boolean flag to indicate whether to return the resources.
|
||||
*/
|
||||
function registry_mark_code($type, $name, $return = FALSE) {
|
||||
static $resources = array();
|
||||
|
||||
if ($type && $name) {
|
||||
if (!isset($resources[$type])) {
|
||||
$resources[$type] = array();
|
||||
}
|
||||
if (!in_array($name, $resources[$type])) {
|
||||
$resources[$type][] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
if ($return) {
|
||||
return $resources;
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1513,60 +1548,35 @@ function registry_rebuild() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Save the files required by the registry for this path.
|
||||
* Wrapper function to perform array to string conversion of lookup cache.
|
||||
*/
|
||||
function registry_cache_path_files() {
|
||||
if ($used_code = registry_mark_code(NULL, NULL, TRUE)) {
|
||||
$files = array();
|
||||
$type_sql = array();
|
||||
$params = array();
|
||||
|
||||
// This function may get called when the default database is not active, but
|
||||
// there is no reason we'd ever want to not use the default database for
|
||||
// this query.
|
||||
$select = Database::getConnection('default')->select('registry')->distinct();
|
||||
$select->addField('registry', 'filename');
|
||||
|
||||
// This creates a series of 2-clause AND conditions that are then ORed together.
|
||||
$ors = db_or();
|
||||
foreach ($used_code as $type => $names) {
|
||||
$and = db_and()
|
||||
->condition('name', $names, 'IN')
|
||||
->condition('type', $type);
|
||||
$ors->condition($and);
|
||||
}
|
||||
$select->condition($ors);
|
||||
$files = $select->execute()->fetchCol();
|
||||
|
||||
if ($files) {
|
||||
sort($files);
|
||||
// Only write this to cache if the file list we are going to cache
|
||||
// is different to what we loaded earlier in the request.
|
||||
if ($files != registry_load_path_files(TRUE)) {
|
||||
$menu = menu_get_item();
|
||||
cache_set('registry:' . $menu['path'], implode(';', $files), 'cache_registry');
|
||||
}
|
||||
}
|
||||
function _registry_set_lookup_cache(array $lookup_cache) {
|
||||
// Cache a string, not an array, so we can avoid the memory usage hit
|
||||
// from serialize() in the cache system.
|
||||
$key_value_pairs = array();
|
||||
foreach ($lookup_cache as $key => $value) {
|
||||
$key_value_pairs[] = "$key|" . ($value ? $value : '');
|
||||
}
|
||||
return cache_set('lookup_cache', implode(';', $key_value_pairs), 'cache_registry');
|
||||
}
|
||||
|
||||
/**
|
||||
* registry_load_path_files
|
||||
* Wrapper function to perform string to array conversion of lookup cache.
|
||||
*/
|
||||
function registry_load_path_files($return = FALSE) {
|
||||
static $file_cache_data = array();
|
||||
if ($return) {
|
||||
sort($file_cache_data);
|
||||
return $file_cache_data;
|
||||
}
|
||||
$menu = menu_get_item();
|
||||
$cache = cache_get('registry:' . $menu['path'], 'cache_registry');
|
||||
if (!empty($cache->data)) {
|
||||
foreach(explode(';', $cache->data) as $file) {
|
||||
require_once DRUPAL_ROOT . '/' . $file;
|
||||
$file_cache_data[] = $file;
|
||||
function _registry_get_lookup_cache() {
|
||||
// In _registry_set_lookup_cache, we cache a string, not an array, to avoid
|
||||
// serialize() in the cache system. serialize() makes a copy, and thus uses
|
||||
// extra memory, which we are trying to avoid.
|
||||
$lookup_cache = array();
|
||||
if ($cache = cache_get('lookup_cache', 'cache_registry')) {
|
||||
// Each item is separated by ';'.
|
||||
foreach (explode(';', $cache->data) as $lookup) {
|
||||
// Key value pairs are separated by '|'.
|
||||
list($resource, $result) = explode('|', $lookup);
|
||||
$lookup_cache[$resource] = $result;
|
||||
}
|
||||
}
|
||||
return $lookup_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1641,7 +1641,7 @@ function drupal_page_footer() {
|
|||
module_invoke_all('exit');
|
||||
|
||||
module_implements(MODULE_IMPLEMENTS_WRITE_CACHE);
|
||||
registry_cache_path_files();
|
||||
_registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -393,7 +393,6 @@ function menu_execute_active_handler($path = NULL) {
|
|||
menu_rebuild();
|
||||
}
|
||||
if ($router_item = menu_get_item($path)) {
|
||||
registry_load_path_files();
|
||||
if ($router_item['access']) {
|
||||
if (drupal_function_exists($router_item['page_callback'])) {
|
||||
return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
|
||||
|
|
|
@ -90,7 +90,7 @@ function module_list($refresh = FALSE, $bootstrap = TRUE, $sort = FALSE, $fixed_
|
|||
* The array of filesystem objects used to rebuild the cache.
|
||||
*/
|
||||
function module_rebuild_cache() {
|
||||
// Get current list of modules
|
||||
// Get current list of modules, including uninstalled modules.
|
||||
$files = drupal_system_listing('/\.module$/', 'modules', 'name', 0);
|
||||
|
||||
// Extract current files from database.
|
||||
|
@ -128,7 +128,8 @@ function module_rebuild_cache() {
|
|||
// Log the critical hooks implemented by this module.
|
||||
$bootstrap = 0;
|
||||
foreach (bootstrap_hooks() as $hook) {
|
||||
if (module_hook($file->name, $hook)) {
|
||||
// Only look for hooks in installed modules.
|
||||
if (!empty($file->status) && in_array($file->name, module_implements($hook))) {
|
||||
$bootstrap = 1;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -69,10 +69,26 @@ function _registry_rebuild() {
|
|||
->execute();
|
||||
}
|
||||
}
|
||||
_registry_parse_files($files);
|
||||
$parsed_files = _registry_parse_files($files);
|
||||
|
||||
$unchanged_resources = array();
|
||||
foreach (_registry_get_lookup_cache() as $key => $file) {
|
||||
// If the file for this cached resource is carried over unchanged from
|
||||
// the last registry build, then we can safely re-cache it.
|
||||
if ($file && in_array($file, array_keys($files)) && !in_array($file, $parsed_files)) {
|
||||
$unchanged_resources[$key] = $file;
|
||||
}
|
||||
}
|
||||
_registry_check_code(REGISTRY_RESET_LOOKUP_CACHE);
|
||||
|
||||
module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE);
|
||||
cache_clear_all('*', 'cache_registry', TRUE);
|
||||
|
||||
// We have some unchanged resources, warm up the cache - no need to pay
|
||||
// for looking them up again.
|
||||
if (count($unchanged_resources) > 0) {
|
||||
_registry_set_lookup_cache($unchanged_resources);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,12 +108,13 @@ function registry_get_parsed_files() {
|
|||
* The list of files to check and parse.
|
||||
*/
|
||||
function _registry_parse_files($files) {
|
||||
$changed_files = array();
|
||||
$parsed_files = array();
|
||||
foreach ($files as $filename => $file) {
|
||||
$contents = file_get_contents($filename);
|
||||
$md5 = md5($contents);
|
||||
$new_file = !isset($file['md5']);
|
||||
if ($new_file || $md5 != $file['md5']) {
|
||||
$parsed_files[] = $filename;
|
||||
// We update the md5 after we've saved the files resources rather than here, so if we
|
||||
// don't make it through this rebuild, the next run will reparse the file.
|
||||
_registry_parse_file($filename, $contents, $file['module'], $file['weight']);
|
||||
|
@ -108,6 +125,7 @@ function _registry_parse_files($files) {
|
|||
->execute();
|
||||
}
|
||||
}
|
||||
return $parsed_files;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -239,7 +239,7 @@ function _block_rehash() {
|
|||
// Valid region names for the theme.
|
||||
$regions = system_region_list($theme_key);
|
||||
|
||||
foreach (module_list() as $module) {
|
||||
foreach (module_implements('block') as $module) {
|
||||
$module_blocks = module_invoke($module, 'block', 'list');
|
||||
if ($module_blocks) {
|
||||
foreach ($module_blocks as $delta => $block) {
|
||||
|
|
|
@ -657,7 +657,7 @@ function system_modules($form_state = array()) {
|
|||
}
|
||||
}
|
||||
// Generate link for module's help page, if there is one.
|
||||
if ($help_arg && module_hook($filename, 'help')) {
|
||||
if ($help_arg && $module->status && in_array($filename, module_implements('help'))) {
|
||||
if (module_invoke($filename, 'help', "admin/help#$filename", $help_arg)) {
|
||||
// Module has a help page.
|
||||
$extra['help'] = theme('more_help_link', url("admin/help/$filename"));
|
||||
|
|
|
@ -1398,12 +1398,12 @@ function system_get_module_admin_tasks($module) {
|
|||
$admin_tasks = array();
|
||||
$admin_task_count = 0;
|
||||
// Check for permissions.
|
||||
if (module_hook($module, 'perm') && $admin_access) {
|
||||
if (in_array($module, module_implements('perm')) && $admin_access) {
|
||||
$admin_tasks[-1] = l(t('Configure permissions'), 'admin/user/permissions', array('fragment' => 'module-' . $module));
|
||||
}
|
||||
|
||||
// Check for menu items that are admin links.
|
||||
if ($menu = module_invoke($module, 'menu')) {
|
||||
if (in_array($module, module_implements('menu')) && $menu = module_invoke($module, 'menu')) {
|
||||
foreach (array_keys($menu) as $path) {
|
||||
if (isset($items[$path])) {
|
||||
$admin_tasks[$items[$path]['title'] . $admin_task_count ++] = l($items[$path]['title'], $path);
|
||||
|
|
|
@ -510,7 +510,7 @@ function user_admin_perm($form_state, $rid = NULL) {
|
|||
// Render role/permission overview:
|
||||
$options = array();
|
||||
$hide_descriptions = !system_admin_compact_mode();
|
||||
foreach (module_list(FALSE, FALSE, TRUE) as $module) {
|
||||
foreach (module_implements('perm') as $module) {
|
||||
if ($permissions = module_invoke($module, 'perm')) {
|
||||
$form['permission'][] = array(
|
||||
'#markup' => $module,
|
||||
|
|
|
@ -1917,7 +1917,7 @@ function user_help($path, $arg) {
|
|||
function _user_categories($account) {
|
||||
$categories = array();
|
||||
|
||||
foreach (module_list() as $module) {
|
||||
foreach (module_implements('user_categories') as $module) {
|
||||
if ($data = module_invoke($module, 'user_categories', NULL, $account, '')) {
|
||||
$categories = array_merge($data, $categories);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue