diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 3a661aca4fa48ef2811b4e7b0489b72ca00ebd04..495dd0bbb34dc2cd747b43d95355fd29e09f0c4a 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -206,6 +206,16 @@ */ 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; } /** diff --git a/includes/common.inc b/includes/common.inc index 5c9cb056923f25b3da08ea799669667a7ef79f6f..8e78606190f8041abfec7a05e61e6378ecf4c3fb 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -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); } /** diff --git a/includes/menu.inc b/includes/menu.inc index f4f8ecd29403bbee144a3e1dd0535048181f8791..fdcc1bf4efee553245b9bb875aaeedcbdc3ed1d0 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -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']); diff --git a/includes/module.inc b/includes/module.inc index c5c3686fc7baceb119f2c93654330ddf767297d1..9eb2eeeae500d9a69107a2285a145dd7e5dcd313 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -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; } diff --git a/includes/registry.inc b/includes/registry.inc index c02811182e11a6070f1bf51e197da489f7169ec1..ec968dd2ad6feab6b62b461ef268a9e20a1cba7b 100644 --- a/includes/registry.inc +++ b/includes/registry.inc @@ -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; } /** diff --git a/modules/block/block.module b/modules/block/block.module index 9c482e1de10f1bd470146f2a07920c81c6110292..95756a94380898590db8f5414b6240a44b58ef20 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -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) { diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 03db7b6832e1200b2edf67640c273cd531dd734d..fe94ba1914f984ca11beecea4d3117933a8df1b9 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -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")); diff --git a/modules/system/system.module b/modules/system/system.module index 48ac89a63b122e2bab2427ffd29d464b674c55a6..b4f796986ee2f76f2f242d1c25048e4c6a530020 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -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); diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc index c58cbb6484a4207c40794066ad404b3cbf1b3687..370b7e05c36e47ecfa1c4d6ea58aad3db28d5564 100644 --- a/modules/user/user.admin.inc +++ b/modules/user/user.admin.inc @@ -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, diff --git a/modules/user/user.module b/modules/user/user.module index 84ba50441fd009015b82842b7bfa535a86a8040f..224d44d419eb785ef82079794c582311daa6c70a 100644 --- a/modules/user/user.module +++ b/modules/user/user.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); }