Index: includes/module.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/module.inc,v
retrieving revision 1.206
diff -u -p -r1.206 module.inc
--- includes/module.inc	21 Nov 2010 10:19:48 -0000	1.206
+++ includes/module.inc	24 Nov 2010 00:33:12 -0000
@@ -591,7 +591,40 @@ function module_disable($module_list, $d
  *   implemented in that module.
  */
 function module_hook($module, $hook) {
-  return function_exists($module . '_' . $hook);
+  // Use the advanced drupal_static() pattern, since this is called very often.
+  static $drupal_static_fast;
+  if (!isset($drupal_static_fast)) {
+    $drupal_static_fast['implementations'] = &drupal_static(__FUNCTION__);
+  }
+  $implementations = &$drupal_static_fast['implementations'];
+
+  if (empty($implementations)) {
+    // Fetch implementations from cache.
+    $implementations = cache_get('module_implements', 'cache_bootstrap');
+    if ($implementations === FALSE) {
+      $implementations = array();
+    }
+    else {
+      $implementations = $implementations->data;
+    }
+  }
+  if (isset($implementations[$hook][$module]) || function_exists($module . '_' . $hook) || module_load_hook($module, $hook)) {
+    $implementations[$hook][$module] = TRUE;
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+function module_load_hook($module, $hook) {
+  $function = $module . '_' . $hook;
+  $hook_info = module_hook_info();
+  if (isset($hook_info[$hook]['group'])) {
+    $group = $hook_info[$hook]['group'];
+    module_load_include('inc', $module, "$module.$group");
+  }
+
+  return function_exists($function) ? $function : FALSE;
 }
 
 /**
@@ -633,6 +666,7 @@ function module_implements($hook, $sort 
   if ($reset) {
     $implementations = array();
     cache_set('module_implements', array(), 'cache_bootstrap');
+    drupal_static_reset('module_hook');
     drupal_static_reset('module_hook_info');
     drupal_static_reset('drupal_alter');
     cache_clear_all('hook_info', 'cache_bootstrap');
@@ -658,9 +692,8 @@ function module_implements($hook, $sort 
     $implementations[$hook] = array();
     $list = module_list(FALSE, FALSE, $sort);
     foreach ($list as $module) {
-      $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
-      if (module_hook($module, $hook)) {
-        $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
+      if (module_load_hook($module, $hook)) {
+        $implementations[$hook][$module] = isset($hook_info[$hook]['group']) ? $hook_info[$hook]['group'] : FALSE;
       }
     }
     // Allow modules to change the weight of specific implementations but avoid
@@ -669,24 +702,6 @@ function module_implements($hook, $sort 
       drupal_alter('module_implements', $implementations[$hook], $hook);
     }
   }
-  else {
-    foreach ($implementations[$hook] as $module => $group) {
-      // If this hook implementation is stored in a lazy-loaded file, so include
-      // that file first.
-      if ($group) {
-        module_load_include('inc', $module, "$module.$group");
-      }
-      // It is possible that a module removed a hook implementation without the
-      // implementations cache being rebuilt yet, so we check module_hook() on
-      // each request to avoid undefined function errors.
-      if (!module_hook($module, $hook)) {
-        // Clear out the stale implementation from the cache and force a cache
-        // refresh to forget about no longer existing hook implementations.
-        unset($implementations[$hook][$module]);
-        $implementations['#write_cache'] = TRUE;
-      }
-    }
-  }
 
   return array_keys($implementations[$hook]);
 }
@@ -695,36 +710,42 @@ function module_implements($hook, $sort 
  * Retrieve a list of what hooks are explicitly declared.
  */
 function module_hook_info() {
-  $hook_info = &drupal_static(__FUNCTION__, array());
-
-  if (empty($hook_info)) {
-    $cache = cache_get('hook_info', 'cache_bootstrap');
-    if ($cache === FALSE) {
-      // Rebuild the cache and save it.
-      // We can't use module_invoke_all() here or it would cause an infinite
-      // loop.
-      foreach (module_list() as $module) {
-        $function = $module . '_hook_info';
-        if (function_exists($function)) {
-          $result = $function();
-          if (isset($result) && is_array($result)) {
-            $hook_info = array_merge_recursive($hook_info, $result);
-          }
+  $static_cache = &drupal_static(__FUNCTION__);
+  if (isset($static_cache)) {
+    return $static_cache;
+  }
+
+  $cache = cache_get('hook_info', 'cache_bootstrap');
+  if ($cache === FALSE) {
+    $hook_info = array();
+    // Rebuild the cache and save it.
+    // We can't use module_invoke_all() here or it would cause an infinite
+    // loop.
+    foreach (module_list() as $module) {
+      $function = $module . '_hook_info';
+      if (function_exists($function)) {
+        $result = $function();
+        if (isset($result) && is_array($result)) {
+          $hook_info = array_merge_recursive($hook_info, $result);
         }
       }
-      // We can't use drupal_alter() for the same reason as above.
-      foreach (module_list() as $module) {
-        $function = $module . '_hook_info_alter';
-        if (function_exists($function)) {
-          $function($hook_info);
-        }
+    }
+    // We can't use drupal_alter() for the same reason as above.
+    foreach (module_list() as $module) {
+      $function = $module . '_hook_info_alter';
+      if (function_exists($function)) {
+        $function($hook_info);
       }
-      cache_set('hook_info', $hook_info, 'cache_bootstrap');
     }
-    else {
-      $hook_info = $cache->data;
+    // Only cache the results of hook_hook_info() if all modules are loaded.
+    if (drupal_bootstrap(NULL, FALSE) >= DRUPAL_BOOTSTRAP_FULL) {
+      cache_set('hook_info', $hook_info, 'cache_bootstrap');
+      $static_cache = $hook_info;
     }
   }
+  else {
+    $hook_info = $static_cache = $cache->data;
+  }
 
   return $hook_info;
 }
@@ -739,7 +760,8 @@ function module_implements_write_cache()
   // Check whether we need to write the cache. We do not want to cache hooks
   // which are only invoked on HTTP POST requests since these do not need to be
   // optimized as tightly, and not doing so keeps the cache entry smaller.
-  if (isset($implementations['#write_cache']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')) {
+  // Also do not cache unless all modules were loaded on the request.
+  if (isset($implementations['#write_cache']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && drupal_bootstrap(NULL, FALSE) >= DRUPAL_BOOTSTRAP_FULL) {
     unset($implementations['#write_cache']);
     cache_set('module_implements', $implementations, 'cache_bootstrap');
   }
@@ -763,8 +785,8 @@ function module_invoke() {
   $module = $args[0];
   $hook = $args[1];
   unset($args[0], $args[1]);
-  if (module_hook($module, $hook)) {
-    return call_user_func_array($module . '_' . $hook, $args);
+  if ($function = module_load_hook($module, $hook)) {
+    return call_user_func_array($function, $args);
   }
 }
 /**
@@ -785,8 +807,7 @@ function module_invoke_all() {
   unset($args[0]);
   $return = array();
   foreach (module_implements($hook) as $module) {
-    $function = $module . '_' . $hook;
-    if (function_exists($function)) {
+    if ($function = module_load_hook($module, $hook)) {
       $result = call_user_func_array($function, $args);
       if (isset($result) && is_array($result)) {
         $return = array_merge_recursive($return, $result);
@@ -912,7 +933,9 @@ function drupal_alter($type, &$data, &$c
       // function_exists(), since module_implements() returns only modules with
       // implementations.
       foreach ($modules as $module) {
-        $functions[$cid][] = $module . '_' . $hook;
+        if ($function = module_load_hook($module, $hook)) {
+          $functions[$cid][] = $function;
+        }
       }
     }
     else {
@@ -933,13 +956,11 @@ function drupal_alter($type, &$data, &$c
         // Since $modules is a merged array, for any given module, we do not
         // know whether it has any particular implementation, so we need a
         // function_exists().
-        $function = $module . '_' . $hook;
-        if (function_exists($function)) {
+        if ($function = module_load_hook($module, $hook)) {
           $functions[$cid][] = $function;
         }
         foreach ($extra_types as $extra_type) {
-          $function = $module . '_' . $extra_type . '_alter';
-          if (function_exists($function)) {
+          if ($function = module_load_hook($module, $extra_type . '_alter')) {
             $functions[$cid][] = $function;
           }
         }
@@ -955,14 +976,12 @@ function drupal_alter($type, &$data, &$c
       }
       $theme_keys[] = $theme;
       foreach ($theme_keys as $theme_key) {
-        $function = $theme_key . '_' . $hook;
-        if (function_exists($function)) {
+        if ($function = module_load_hook($theme_key, $hook)) {
           $functions[$cid][] = $function;
         }
         if (isset($extra_types)) {
           foreach ($extra_types as $extra_type) {
-            $function = $theme_key . '_' . $extra_type . '_alter';
-            if (function_exists($function)) {
+            if ($function = module_load_hook($theme_key, $extra_type . '_alter')) {
               $functions[$cid][] = $function;
             }
           }
Index: modules/simpletest/tests/module.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/module.test,v
retrieving revision 1.24
diff -u -p -r1.24 module.test
--- modules/simpletest/tests/module.test	20 Nov 2010 03:34:30 -0000	1.24
+++ modules/simpletest/tests/module.test	24 Nov 2010 00:33:12 -0000
@@ -112,6 +112,26 @@ class ModuleUnitTest extends DrupalWebTe
   }
 
   /**
+   * Test that module_invoke() can load a hook defined in hook_hook_info().
+   */
+  function testModuleInvokeDynamicLoading() {
+    module_enable(array('module_test'), FALSE);
+    $this->resetAll();
+    $this->drupalGet('module-test/hook-dynamic-loading-invoke');
+    $this->assertText('success!', t('module_invoke() dynamically loads a hook defined in hook_hook_info().'));
+  }
+
+  /**
+   * Test that module_invoke_all() can load a hook defined in hook_hook_info().
+   */
+  function testModuleInvokeAllDynamicLoading() {
+    module_enable(array('module_test'), FALSE);
+    $this->resetAll();
+    $this->drupalGet('module-test/hook-dynamic-loading-invoke-all');
+    $this->assertText('success!', t('module_invoke_all() dynamically loads a hook defined in hook_hook_info().'));
+  }
+
+  /**
    * Test dependency resolution.
    */
   function testDependencyResolution() {
Index: modules/simpletest/tests/module_test.file.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/module_test.file.inc,v
retrieving revision 1.2
diff -u -p -r1.2 module_test.file.inc
--- modules/simpletest/tests/module_test.file.inc	26 May 2010 19:51:01 -0000	1.2
+++ modules/simpletest/tests/module_test.file.inc	24 Nov 2010 00:33:12 -0000
@@ -10,5 +10,5 @@
  * Implements hook_test_hook().
  */
 function module_test_test_hook() {
-
+  return array('module_test' => 'success!');
 }
Index: modules/simpletest/tests/module_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/module_test.module,v
retrieving revision 1.9
diff -u -p -r1.9 module_test.module
--- modules/simpletest/tests/module_test.module	20 Nov 2010 03:34:30 -0000	1.9
+++ modules/simpletest/tests/module_test.module	24 Nov 2010 00:33:12 -0000
@@ -52,6 +52,45 @@ function module_test_hook_info() {
 }
 
 /**
+ * Implements hook_menu().
+ */
+function module_test_menu() {
+  $items['module-test/hook-dynamic-loading-invoke'] = array(
+    'title' => 'Test hook dynamic loading (invoke)',
+    'page callback' => 'module_test_hook_dynamic_loading_invoke',
+    'access arguments' => array('access content'),
+  );
+  $items['module-test/hook-dynamic-loading-invoke-all'] = array(
+    'title' => 'Test hook dynamic loading (invoke_all)',
+    'page callback' => 'module_test_hook_dynamic_loading_invoke_all',
+    'access arguments' => array('access content'),
+  );
+  return $items;
+}
+
+/**
+ * Page callback for 'hook dynamic loading' test.
+ *
+ * If the hook is dynamically loaded correctly, the menu callback should
+ * return 'success!'.
+ */
+function module_test_hook_dynamic_loading_invoke() {
+  $result = module_invoke('module_test', 'test_hook');
+  return $result['module_test'];
+}
+
+/**
+ * Page callback for 'hook dynamic loading' test.
+ *
+ * If the hook is dynamically loaded correctly, the menu callback should
+ * return 'success!'.
+ */
+function module_test_hook_dynamic_loading_invoke_all() {
+  $result = module_invoke_all('test_hook');
+  return $result['module_test'];
+}
+
+/**
  * Implements hook_modules_enabled().
  */
 function module_test_modules_enabled($modules) {
