- Patch by chx, Damien Tournoud, justinrandell et al: improved the cache registry lookups.

merge-requests/26/head
Dries Buytaert 2008-11-11 22:39:59 +00:00
parent 9d631d22f9
commit 59ece2e39d
10 changed files with 117 additions and 89 deletions

View File

@ -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;
}
/**

View File

@ -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);
}
/**

View File

@ -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']);

View File

@ -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;
}

View File

@ -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;
}
/**

View File

@ -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) {

View File

@ -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"));

View File

@ -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);

View File

@ -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,

View File

@ -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);
}