From f72f814f7b077e12bfb436f8a092c19e81763dca Mon Sep 17 00:00:00 2001
From: Dries <dries@buytaert.net>
Date: Thu, 3 May 2012 11:09:39 -0400
Subject: [PATCH] - Patch #1404198 by sun, beejeebus, fago, chx: Separate
 database cache clearing from static cache clearing and data structure
 rebuilding.

---
 core/includes/common.inc                      | 132 +++++++++++++-----
 core/includes/gettext.inc                     |   2 +-
 core/includes/install.core.inc                |  24 ++--
 core/includes/lock.inc                        |   7 +-
 core/includes/menu.inc                        |  12 +-
 core/includes/module.inc                      |  21 ++-
 core/includes/theme.inc                       |   4 +-
 core/modules/aggregator/aggregator.test       |   2 +-
 core/modules/block/block.module               |  13 +-
 core/modules/block/block.test                 |   4 +-
 core/modules/comment/comment.test             |   1 -
 core/modules/entity/tests/entity.test         |   2 +-
 core/modules/field/field.module               |  15 +-
 core/modules/field_ui/field_ui.module         |   2 +-
 core/modules/filter/filter.module             |   7 +
 core/modules/menu/menu.install                |   2 +-
 core/modules/menu/menu.module                 |   2 +-
 core/modules/node/content_types.inc           |   4 +-
 core/modules/node/node.module                 |  10 +-
 core/modules/search/search.test               |   4 +-
 core/modules/shortcut/shortcut.install        |   2 +-
 .../simpletest/drupal_web_test_case.php       |  20 +--
 core/modules/system/system.admin.inc          |  13 +-
 core/modules/system/system.api.php            |  55 +++++++-
 core/modules/system/system.module             |  19 ++-
 core/modules/system/system.test               |  10 +-
 core/modules/system/tests/cache.test          |   5 +-
 core/modules/system/tests/menu.test           |   2 +-
 .../update_script_test.module                 |   6 +-
 core/modules/update/update.module             |   4 +-
 profiles/standard/standard.install            |   2 +-
 31 files changed, 275 insertions(+), 133 deletions(-)

diff --git a/core/includes/common.inc b/core/includes/common.inc
index 673ac9cfdcc1..f3cdd6982319 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -7267,50 +7267,116 @@ function drupal_implode_tags($tags) {
 }
 
 /**
- * Flushes all cached data on the site.
- *
- * Empties cache tables, rebuilds the menu cache and theme registries, and
- * invokes a hook so that other modules' cache data can be cleared as well.
+ * Flushes all persistent caches, resets all variables, and rebuilds all data structures.
+ *
+ * At times, it is necessary to re-initialize the entire system to account for
+ * changed or new code. This function:
+ * - Clears all persistent caches (invoking hook_cache_flush()), which always
+ *   includes:
+ *   - The bootstrap cache bin containing base system, module system, and theme
+ *     system information.
+ *   - The common 'cache' cache bin containing arbitrary caches.
+ *   - The page cache.
+ *   - The URL alias path cache.
+ * - Resets all static variables that have been defined via drupal_static().
+ * - Clears asset (JS/CSS) file caches.
+ * - Updates the system with latest information about extensions (modules and
+ *   themes).
+ * - Updates the bootstrap flag for modules implementing bootstrap_hooks().
+ * - Rebuilds the full database schema information (invoking hook_schema()).
+ * - Rebuilds data structures of all modules (invoking hook_rebuild()). In
+ *   core this means
+ *   - blocks, node types, date formats and actions are synchronized with the
+ *     database
+ *   - The 'active' status of fields is refreshed.
+ * - Rebuilds the menu router.
+ *
+ * This means the entire system is reset so all caches and static variables are
+ * effectively empty. After that is guaranteed, information about the currently
+ * active code is updated, and rebuild operations are successively called in
+ * order to synchronize the active system according to the current information
+ * defined in code.
+ *
+ * All modules need to ensure that all of their caches are flushed when
+ * hook_cache_flush() is invoked; any previously known information must no
+ * longer exist. All following hook_rebuild() operations must be based on fresh
+ * and current system data. All modules must be able to rely on this contract.
+ *
+ * @see hook_cache_flush()
+ * @see hook_rebuild()
+ *
+ * This function also resets the theme, which means it is not initialized
+ * anymore and all previously added JavaScript and CSS is gone. Normally, this
+ * function is called as an end-of-POST-request operation that is followed by a
+ * redirect, so this effect is not visible. Since the full reset is the whole
+ * point of this function, callers need to take care for backing up all needed
+ * variables and properly restoring or re-initializing them on their own. For
+ * convenience, this function automatically re-initializes the maintenance theme
+ * if it was initialized before.
+ *
+ * @todo Try to clear page/JS/CSS caches last, so cached pages can still be
+ *   served during this possibly long-running operation. (Conflict on bootstrap
+ *   cache though.)
+ * @todo Add a global lock to ensure that caches are not primed in concurrent
+ *   requests.
  */
 function drupal_flush_all_caches() {
-  // Change query-strings on css/js files to enforce reload for all users.
-  _drupal_flush_css_js();
+  // Flush all persistent caches.
+  // This is executed based on old/previously known information, which is
+  // sufficient, since new extensions cannot have any primed caches yet.
+  foreach (module_invoke_all('cache_flush') as $bin) {
+    cache($bin)->flush();
+  }
 
-  registry_rebuild();
+  // Flush asset file caches.
   drupal_clear_css_cache();
   drupal_clear_js_cache();
+  _drupal_flush_css_js();
 
-  // Rebuild the theme data. Note that the module data is rebuilt above, as
-  // part of registry_rebuild().
-  system_rebuild_theme_data();
-  drupal_theme_rebuild();
-
-  entity_info_cache_clear();
+  // Reset all static caches.
+  drupal_static_reset();
 
-  // @todo D8: Split cache flushing from rebuilding.
-  // @see http://drupal.org/node/996236
-  if (module_exists('node')) {
-    node_types_rebuild();
-  }
-  // node_menu() defines menu items based on node types so it needs to come
-  // after node types are rebuilt.
-  menu_rebuild();
+  // Clear all non-drupal_static() static caches.
+  // None currently; kept if any static caches need to be reset in the future.
 
-  // Synchronize to catch any actions that were added or removed.
-  actions_synchronize();
+  // Update and synchronize the class registry and extension information based
+  // on current/actual code.
+  // Module data is rebuilt as part of registry_rebuild().
+  registry_rebuild();
+  system_rebuild_theme_data();
 
-  // Don't clear cache_form - in-progress form submissions may break.
-  // Ordered so clearing the page cache will always be the last action.
-  $core = array('cache', 'path', 'filter', 'bootstrap', 'page');
-  $cache_bins = array_merge(module_invoke_all('flush_caches'), $core);
-  foreach ($cache_bins as $bin) {
-    cache($bin)->flush();
-  }
+  // Ensure that all modules that are currently supposed to be enabled are
+  // actually loaded.
+  module_load_all();
 
-  // Rebuild the bootstrap module list. We do this here so that developers
-  // can get new hook_boot() implementations registered without having to
-  // write a hook_update_N() function.
+  // Update the list of bootstrap modules.
+  // Allows developers to get new hook_boot() implementations registered without
+  // having to write a hook_update_N() function.
   _system_update_bootstrap_status();
+
+  // Rebuild the schema and cache a fully-built schema based on new module data.
+  // This is necessary for any invocation of index.php, because setting cache
+  // table entries requires schema information and that occurs during bootstrap
+  // before any modules are loaded, so if there is no cached schema,
+  // drupal_get_schema() will try to generate one, but with no loaded modules,
+  // it will return nothing.
+  drupal_get_schema(NULL, TRUE);
+
+  // Rebuild all information based on new module data.
+  module_invoke_all('rebuild');
+
+  // Rebuild the menu router based on all rebuilt data.
+  // Important: This rebuild must happen last, so the menu router is guaranteed
+  // to be based on up to date information.
+  menu_router_rebuild();
+
+  // Re-initialize the maintenance theme, if the current request attempted to
+  // use it. Unlike regular usages of this function, the installer and update
+  // scripts need to flush all caches during GET requests/page building.
+  if (function_exists('_drupal_maintenance_theme')) {
+    unset($GLOBALS['theme']);
+    drupal_maintenance_theme();
+  }
 }
 
 /**
diff --git a/core/includes/gettext.inc b/core/includes/gettext.inc
index a8498dc22bc8..2355157cb889 100644
--- a/core/includes/gettext.inc
+++ b/core/includes/gettext.inc
@@ -61,7 +61,7 @@ function _locale_import_po($file, $langcode, $overwrite_options, $customized = L
   cache()->deletePrefix('locale:');
 
   // Rebuild the menu, strings may have changed.
-  menu_rebuild();
+  menu_router_rebuild();
 
   drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
   watchdog('locale', 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.', array('%file' => $file->filename, '%locale' => $langcode, '%number' => $additions, '%update' => $updates, '%delete' => $deletes));
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 5cb3399a094a..76ce3e679680 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -1567,28 +1567,26 @@ function install_import_translations_remaining(&$install_state) {
  *   A message informing the user that the installation is complete.
  */
 function install_finished(&$install_state) {
-  drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_distribution_name())), PASS_THROUGH);
-  $messages = drupal_set_message();
-  $output = '<p>' . st('Congratulations, you installed @drupal!', array('@drupal' => drupal_install_profile_distribution_name())) . '</p>';
-  $output .= '<p>' . (isset($messages['error']) ? st('Review the messages above before visiting <a href="@url">your new site</a>.', array('@url' => url(''))) : st('<a href="@url">Visit your new site</a>.', array('@url' => url('')))) . '</p>';
-
-  // Flush all caches to ensure that any full bootstraps during the installer
-  // do not leave stale cached data, and that any content types or other items
-  // registered by the install profile are registered correctly.
-  drupal_flush_all_caches();
-
   // Remember the profile which was used.
   variable_set('install_profile', drupal_get_profile());
 
-  // Install profiles are always loaded last
+  // Install profiles are always loaded last.
   db_update('system')
     ->fields(array('weight' => 1000))
     ->condition('type', 'module')
     ->condition('name', drupal_get_profile())
     ->execute();
 
-  // Cache a fully-built schema.
-  drupal_get_schema(NULL, TRUE);
+  // Flush all caches to ensure that any full bootstraps during the installer
+  // do not leave stale cached data, and that any content types or other items
+  // registered by the install profile are registered correctly.
+  drupal_flush_all_caches();
+
+  drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_distribution_name())), PASS_THROUGH);
+
+  $messages = drupal_set_message();
+  $output = '<p>' . st('Congratulations, you installed @drupal!', array('@drupal' => drupal_install_profile_distribution_name())) . '</p>';
+  $output .= '<p>' . (isset($messages['error']) ? st('Review the messages above before visiting <a href="@url">your new site</a>.', array('@url' => url(''))) : st('<a href="@url">Visit your new site</a>.', array('@url' => url('')))) . '</p>';
 
   // Run cron to populate update status tables (if available) so that users
   // will be warned if they've installed an out of date Drupal version.
diff --git a/core/includes/lock.inc b/core/includes/lock.inc
index 7ef199e1d0ce..fd794aee8d1d 100644
--- a/core/includes/lock.inc
+++ b/core/includes/lock.inc
@@ -13,9 +13,10 @@
  * In most environments, multiple Drupal page requests (a.k.a. threads or
  * processes) will execute in parallel. This leads to potential conflicts or
  * race conditions when two requests execute the same code at the same time. A
- * common example of this is a rebuild like menu_rebuild() where we invoke many
- * hook implementations to get and process data from all active modules, and
- * then delete the current data in the database to insert the new afterwards.
+ * common example of this is a rebuild like menu_router_rebuild() where we
+ * invoke many hook implementations to get and process data from all active
+ * modules, and then delete the current data in the database to insert the new
+ * afterwards.
  *
  * This is a cooperative, advisory lock system. Any long-running operation
  * that could potentially be attempted in parallel by multiple requests should
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 2bfc39e6e89a..dc70185f4b91 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -301,7 +301,7 @@
  * then the 1011 bitstring represents node/%/edit/foo where % means that
  * any argument matches that part. We limit ourselves to using binary
  * numbers that correspond the patterns of wildcards of router items that
- * actually exists. This list of 'masks' is built in menu_rebuild().
+ * actually exists. This list of 'masks' is built in menu_router_rebuild().
  *
  * @param $parts
  *   An array of path parts, for the above example
@@ -450,7 +450,7 @@ function menu_get_item($path = NULL, $router_item = NULL) {
     // Rebuild if we know it's needed, or if the menu masks are missing which
     // occurs rarely, likely due to a race condition of multiple rebuilds.
     if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
-      menu_rebuild();
+      menu_router_rebuild();
     }
     $original_map = arg(NULL, $path);
 
@@ -2658,12 +2658,12 @@ function menu_reset_static_cache() {
  *   TRUE if the menu was rebuilt, FALSE if another thread was rebuilding
  *   in parallel and the current thread just waited for completion.
  */
-function menu_rebuild() {
-  if (!lock_acquire('menu_rebuild')) {
+function menu_router_rebuild() {
+  if (!lock_acquire(__FUNCTION__)) {
     // Wait for another request that is already doing this work.
     // We choose to block here since otherwise the router item may not
     // be available in menu_execute_active_handler() resulting in a 404.
-    lock_wait('menu_rebuild');
+    lock_wait(__FUNCTION__);
     return FALSE;
   }
 
@@ -2684,7 +2684,7 @@ function menu_rebuild() {
     watchdog_exception('menu', $e);
   }
 
-  lock_release('menu_rebuild');
+  lock_release(__FUNCTION__);
   return TRUE;
 }
 
diff --git a/core/includes/module.inc b/core/includes/module.inc
index 6192a38d1ae9..6b4604a7b21d 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -19,7 +19,15 @@
  *   have been loaded.
  */
 function module_load_all($bootstrap = FALSE) {
-  static $has_run = FALSE;
+  // Already loaded code cannot be unloaded, but new modules may be added within
+  // a request, which should be loaded as well.
+  // Use the advanced drupal_static() pattern, since this is called very often.
+  // @see theme()
+  static $drupal_static_fast;
+  if (!isset($drupal_static_fast)) {
+    $drupal_static_fast['has_run'] = &drupal_static(__FUNCTION__, FALSE);
+  }
+  $has_run = &$drupal_static_fast['has_run'];
 
   if (isset($bootstrap)) {
     foreach (module_list(TRUE, $bootstrap) as $module) {
@@ -66,7 +74,16 @@ function module_load_all($bootstrap = FALSE) {
  *   the list.
  */
 function module_list($refresh = FALSE, $bootstrap_refresh = FALSE, $sort = FALSE, $fixed_list = NULL) {
-  static $list = array(), $sorted_list;
+  // system_list() may be reset within a request, so the module list needs to be
+  // reset, too.
+  // Use the advanced drupal_static() pattern, since this is called very often.
+  static $drupal_static_fast;
+  if (!isset($drupal_static_fast)) {
+    $drupal_static_fast['list'] = &drupal_static(__FUNCTION__ . ':list', array());
+    $drupal_static_fast['sorted_list'] = &drupal_static(__FUNCTION__ . ':sorted_list');
+  }
+  $list = &$drupal_static_fast['list'];
+  $sorted_list = &$drupal_static_fast['sorted_list'];
 
   if (empty($list) || $refresh || $fixed_list) {
     $list = array();
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index b145979875ee..2a88969f8c80 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1489,7 +1489,7 @@ function theme_enable($theme_list) {
   }
 
   list_themes(TRUE);
-  menu_rebuild();
+  menu_router_rebuild();
   drupal_theme_rebuild();
 
   // Invoke hook_themes_enabled() after the themes have been enabled.
@@ -1522,7 +1522,7 @@ function theme_disable($theme_list) {
   }
 
   list_themes(TRUE);
-  menu_rebuild();
+  menu_router_rebuild();
   drupal_theme_rebuild();
 
   // Invoke hook_themes_disabled after the themes have been disabled.
diff --git a/core/modules/aggregator/aggregator.test b/core/modules/aggregator/aggregator.test
index 61ad16b2d817..ddfcc0775b5c 100644
--- a/core/modules/aggregator/aggregator.test
+++ b/core/modules/aggregator/aggregator.test
@@ -893,7 +893,7 @@ class AggregatorRenderingTestCase extends AggregatorTestCase {
     $feed->block = 0;
     aggregator_save_feed((array) $feed);
     // It is nescessary to flush the cache after saving the number of items.
-    drupal_flush_all_caches();
+    $this->resetAll();
     // Check that the block is no longer displayed.
     $this->drupalGet('node');
     $this->assertNoText(t($block['title']), 'Feed block is not displayed on the page when number of items is set to 0.');
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index 2bfd7653b9c1..f7d8dc0c5b44 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -935,9 +935,16 @@ function _block_get_renderable_block($element) {
 }
 
 /**
- * Implements hook_flush_caches().
+ * Implements hook_cache_flush().
  */
-function block_flush_caches() {
+function block_cache_flush() {
+  return array('block');
+}
+
+/**
+ * Implements hook_rebuild().
+ */
+function block_rebuild() {
   // Rehash blocks for active themes. We don't use list_themes() here,
   // because if MAINTENANCE_MODE is defined it skips reading the database,
   // and we can't tell which themes are active.
@@ -945,8 +952,6 @@ function block_flush_caches() {
   foreach ($themes as $theme) {
     _block_rehash($theme->name);
   }
-
-  return array('block');
 }
 
 /**
diff --git a/core/modules/block/block.test b/core/modules/block/block.test
index a5bde420ebcb..192e264531b3 100644
--- a/core/modules/block/block.test
+++ b/core/modules/block/block.test
@@ -391,7 +391,7 @@ class BlockTestCase extends DrupalWebTestCase {
     // Disable caching for this block.
     variable_set('block_test_caching', DRUPAL_NO_CACHE);
     // Flushing all caches should call _block_rehash().
-    drupal_flush_all_caches();
+    $this->resetAll();
     // Verify that the database is updated with the new caching mode.
     $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField();
     $this->assertEqual($current_caching, DRUPAL_NO_CACHE, t("Test block's database entry updated to DRUPAL_NO_CACHE."));
@@ -833,7 +833,7 @@ class BlockHiddenRegionTestCase extends DrupalWebTestCase {
     $theme = 'block_test_theme';
     theme_enable(array($theme));
     variable_set('theme_default', $theme);
-    menu_rebuild();
+    menu_router_rebuild();
 
     // Ensure that "block_test_theme" is set as the default theme.
     $this->drupalGet('admin/structure/block');
diff --git a/core/modules/comment/comment.test b/core/modules/comment/comment.test
index c41f9af7b8ed..3f5b0409f58f 100644
--- a/core/modules/comment/comment.test
+++ b/core/modules/comment/comment.test
@@ -2090,7 +2090,6 @@ class CommentFieldsTest extends CommentHelperCase {
     $edit['modules[Core][book][enable]'] = 'book';
     $edit['modules[Core][poll][enable]'] = 'poll';
     $this->drupalPost('admin/modules', $edit, t('Save configuration'));
-    $this->resetAll();
 
     // Now enable the comment module.
     $edit = array();
diff --git a/core/modules/entity/tests/entity.test b/core/modules/entity/tests/entity.test
index cd9b879f7693..abc02ef27026 100644
--- a/core/modules/entity/tests/entity.test
+++ b/core/modules/entity/tests/entity.test
@@ -222,7 +222,7 @@ class EntityAPIInfoTestCase extends DrupalWebTestCase  {
     // Change the label of the test entity type and make sure changes appear
     // after flushing caches.
     variable_set('entity_cache_test_label', 'New label.');
-    drupal_flush_all_caches();
+    $this->resetAll();
     $info = entity_get_info('entity_cache_test');
     $this->assertEqual($info['label'], 'New label.', 'New label appears in entity info.');
 
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 1e8b7bbf15e3..0220a6ec9547 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -380,14 +380,19 @@ function field_system_info_alter(&$info, $file, $type) {
 }
 
 /**
- * Implements hook_flush_caches().
+ * Implements hook_cache_flush().
  */
-function field_flush_caches() {
+function field_cache_flush() {
+  // Request a flush of our cache table.
+  return array('field');
+}
+
+/**
+ * Implements hook_rebuild().
+ */
+function field_rebuild() {
   // Refresh the 'active' status of fields.
   field_sync_field_status();
-
- // Request a flush of our cache table.
-  return array('field');
 }
 
 /**
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index c75005a2624b..b65cd3c5cf73 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -52,7 +52,7 @@ function field_ui_help($path, $arg) {
 function field_ui_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
   // The Field UI relies on entity_get_info() to build menu items for entity
   // field administration pages. Ensure that the menu is rebuilt.
-  menu_rebuild();
+  menu_router_rebuild();
 }
 
 /**
diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module
index 45f5170ce744..9f492fe3f16b 100644
--- a/core/modules/filter/filter.module
+++ b/core/modules/filter/filter.module
@@ -5,6 +5,13 @@
  * Framework for handling filtering of content.
  */
 
+/**
+ * Implements hook_cache_flush().
+ */
+function filter_cache_flush() {
+  return array('filter');
+}
+
 /**
  * Implements hook_help().
  */
diff --git a/core/modules/menu/menu.install b/core/modules/menu/menu.install
index 05aed283fae4..d876017af653 100644
--- a/core/modules/menu/menu.install
+++ b/core/modules/menu/menu.install
@@ -66,6 +66,6 @@ function menu_install() {
  * Implements hook_uninstall().
  */
 function menu_uninstall() {
-  menu_rebuild();
+  menu_router_rebuild();
 }
 
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index 5d55d066e8c8..81094d07c534 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -180,7 +180,7 @@ function menu_theme() {
  * Add a link for each custom menu.
  */
 function menu_enable() {
-  menu_rebuild();
+  menu_router_rebuild();
   $base_link = db_query("SELECT mlid AS plid, menu_name FROM {menu_links} WHERE link_path = 'admin/structure/menu' AND module = 'system'")->fetchAssoc();
   $base_link['router_path'] = 'admin/structure/menu/manage/%';
   $base_link['module'] = 'menu';
diff --git a/core/modules/node/content_types.inc b/core/modules/node/content_types.inc
index 2e45c9a7c754..9ca1e15a7ad7 100644
--- a/core/modules/node/content_types.inc
+++ b/core/modules/node/content_types.inc
@@ -355,7 +355,7 @@ function node_type_form_submit($form, &$form_state) {
   $status = node_type_save($type);
 
   node_types_rebuild();
-  menu_rebuild();
+  menu_router_rebuild();
   $t_args = array('%name' => $type->name);
 
   if ($status == SAVED_UPDATED) {
@@ -458,7 +458,7 @@ function node_type_delete_confirm_submit($form, &$form_state) {
   watchdog('menu', 'Deleted content type %name.', $t_args, WATCHDOG_NOTICE);
 
   node_types_rebuild();
-  menu_rebuild();
+  menu_router_rebuild();
 
   $form_state['redirect'] = 'admin/structure/types';
   return;
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 5e1c3b31c91f..9e21bfaa0e76 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -76,6 +76,13 @@
  */
 const NODE_ACCESS_IGNORE = NULL;
 
+/**
+ * Implements hook_rebuild().
+ */
+function node_rebuild() {
+  node_types_rebuild();
+}
+
 /**
  * Implements hook_help().
  */
@@ -1890,9 +1897,6 @@ function node_menu() {
     'type' => MENU_CALLBACK,
   );
   // @todo Remove this loop when we have a 'description callback' property.
-  // Resets the internal static cache of _node_types_build() and forces a
-  // rebuild of the node type information.
-  node_type_cache_reset();
   foreach (node_type_get_types() as $type) {
     $type_url_str = str_replace('_', '-', $type->type);
     $items['node/add/' . $type_url_str] = array(
diff --git a/core/modules/search/search.test b/core/modules/search/search.test
index dd63ec488ca5..60854655c219 100644
--- a/core/modules/search/search.test
+++ b/core/modules/search/search.test
@@ -1225,7 +1225,7 @@ class SearchKeywordsConditions extends SearchWebTestCase {
     $this->drupalLogin($this->searching_user);
     // Test with all search modules enabled.
     variable_set('search_active_modules', array('node' => 'node', 'user' => 'user', 'search_extra_type' => 'search_extra_type'));
-    menu_rebuild();
+    menu_router_rebuild();
   }
 
   /**
@@ -1936,7 +1936,7 @@ class SearchPageOverride extends SearchWebTestCase {
 
     // Enable the extra type module for searching.
     variable_set('search_active_modules', array('node' => 'node', 'user' => 'user', 'search_extra_type' => 'search_extra_type'));
-    menu_rebuild();
+    menu_router_rebuild();
   }
 
   function testSearchPageHook() {
diff --git a/core/modules/shortcut/shortcut.install b/core/modules/shortcut/shortcut.install
index 60c113aea38a..9ee9fd365056 100644
--- a/core/modules/shortcut/shortcut.install
+++ b/core/modules/shortcut/shortcut.install
@@ -36,7 +36,7 @@ function shortcut_install() {
   // Drupal is already installed (i.e., we are not in the installer).
   // @see http://drupal.org/node/1376150
   if (variable_get('install_task', '') != 'done') {
-    menu_rebuild();
+    menu_router_rebuild();
   }
   shortcut_set_save($shortcut_set);
 }
diff --git a/core/modules/simpletest/drupal_web_test_case.php b/core/modules/simpletest/drupal_web_test_case.php
index 15c165630216..6a0073dde932 100644
--- a/core/modules/simpletest/drupal_web_test_case.php
+++ b/core/modules/simpletest/drupal_web_test_case.php
@@ -1014,7 +1014,7 @@ protected function drupalCreateContentType($settings = array()) {
 
     $saved_type = node_type_save($type);
     node_types_rebuild();
-    menu_rebuild();
+    menu_router_rebuild();
     node_add_body_field($type);
 
     $this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type)));
@@ -1415,6 +1415,9 @@ protected function setUp() {
     // Create the database prefix for this test.
     $this->prepareDatabasePrefix();
 
+    // Prepare the environment for running tests.
+    $this->prepareEnvironment();
+
     // Reset all statics and variables to perform tests in a clean environment.
     $conf = array();
     drupal_static_reset();
@@ -1425,9 +1428,6 @@ protected function setUp() {
     // write back to persistent caches when they are destructed.
     $this->changeDatabasePrefix();
 
-    // Prepare the environment for running tests.
-    $this->prepareEnvironment();
-
     // Preset the 'install_profile' system variable, so the first call into
     // system_rebuild_module_data() (in drupal_install_system()) will register
     // the test's profile as a module. Without this, the installation profile of
@@ -1553,17 +1553,7 @@ protected function preloadRegistry() {
    * are enabled later.
    */
   protected function resetAll() {
-    // Reset all static variables.
-    drupal_static_reset();
-    // Reset the list of enabled modules.
-    module_list(TRUE);
-
-    // Reset cached schema for new database prefix. This must be done before
-    // drupal_flush_all_caches() so rebuilds can make use of the schema of
-    // modules enabled on the cURL side.
-    drupal_get_schema(NULL, TRUE);
-
-    // Perform rebuilds and flush remaining caches.
+    // Clear all database and static caches and rebuild data structures.
     drupal_flush_all_caches();
 
     // Reload global $conf array and permissions.
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 9619461d3033..dcc289ac2877 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -327,12 +327,13 @@ function system_theme_default() {
       // Set the default theme.
       variable_set('theme_default', $theme);
 
-      // Rebuild the menu. This duplicates the menu_rebuild() in theme_enable().
-      // However, modules must know the current default theme in order to use
-      // this information in hook_menu() or hook_menu_alter() implementations,
-      // and doing the variable_set() before the theme_enable() could result
-      // in a race condition where the theme is default but not enabled.
-      menu_rebuild();
+      // Rebuild the menu. This duplicates the menu_router_rebuild() in
+      // theme_enable(). However, modules must know the current default theme in
+      // order to use this information in hook_menu() or hook_menu_alter()
+      // implementations, and doing the variable_set() before the theme_enable()
+      // could result in a race condition where the theme is default but not
+      // enabled.
+      menu_router_rebuild();
 
       // The status message depends on whether an admin theme is currently in use:
       // a value of 0 means the admin theme is set to be the default theme.
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 15ffa2175695..74240154476f 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -2087,21 +2087,62 @@ function hook_mail($key, &$message, $params) {
 }
 
 /**
- * Add a list of cache tables to be cleared.
+ * Flush all persistent and static caches.
  *
- * This hook allows your module to add cache bins to the list of cache bins
- * that will be cleared by the Clear button on the Performance page or
- * whenever drupal_flush_all_caches is invoked.
+ * This hook asks your module to clear all of its persistent (database) and
+ * static caches, in order to ensure a clean environment for subsequently
+ * invoked data rebuilds.
  *
- * @return
- *   An array of cache bins.
+ * Do NOT use this hook for rebuilding information. Only use it to flush custom
+ * caches and return the names of additional cache bins to flush.
+ *
+ * Static caches using drupal_static() do not need to be reset manually.
+ * However, all other static variables that do not use drupal_static() must be
+ * manually reset.
+ *
+ * This hook is invoked by drupal_flush_all_caches(). It runs before module data
+ * is updated and before hook_rebuild().
+ *
+ * @return array
+ *   An array of cache bins to be flushed.
  *
  * @see drupal_flush_all_caches()
+ * @see hook_rebuild()
  */
-function hook_flush_caches() {
+function hook_cache_flush() {
   return array('example');
 }
 
+/**
+ * Rebuild data based upon refreshed caches.
+ *
+ * This hook allows your module to rebuild its data based on the latest/current
+ * module data. It runs after hook_cache_flush() and after all module data has
+ * been updated.
+ *
+ * This hook is only invoked after the system has been completely cleared;
+ * i.e., all previously cached data is known to be gone and every API in the
+ * system is known to return current information, so your module can safely rely
+ * on all available data to rebuild its own.
+ *
+ * The menu router is the only exception regarding rebuilt data; it is only
+ * rebuilt after all hook_rebuild() implementations have been invoked. That
+ * ensures that hook_menu() implementations and the final router rebuild can
+ * rely on all data being returned by all modules.
+ *
+ * @see hook_cache_flush()
+ * @see drupal_flush_all_caches()
+ */
+function hook_rebuild() {
+  // Rehash blocks for active themes. We don't use list_themes() here,
+  // because if MAINTENANCE_MODE is defined it skips reading the database,
+  // and we can't tell which themes are active.
+  $themes = db_query("SELECT name FROM {system} WHERE type = 'theme' AND status = 1");
+  foreach ($themes as $theme) {
+    _block_rehash($theme->name);
+  }
+}
+
 /**
  * Perform necessary actions before modules are installed.
  *
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 80a84033fb9a..8af6cc3938f3 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -3054,8 +3054,7 @@ function system_cron() {
     }
   }
 
-  $core = array('cache', 'path', 'filter', 'page', 'form', 'menu');
-  $cache_bins = array_merge(module_invoke_all('flush_caches'), $core);
+  $cache_bins = array_merge(module_invoke_all('cache_flush'), array('form', 'menu'));
   foreach ($cache_bins as $bin) {
     cache($bin)->expire();
   }
@@ -3081,13 +3080,21 @@ function system_cron() {
 }
 
 /**
- * Implements hook_flush_caches().
+ * Implements hook_cache_flush().
  */
-function system_flush_caches() {
+function system_cache_flush() {
+  // Do NOT flush the 'form' cache bin to retain in-progress form submissions.
+  return array('bootstrap', 'cache', 'page', 'path');
+}
+
+/**
+ * Implements hook_rebuild().
+ */
+function system_rebuild() {
   // Rebuild list of date formats.
   system_date_formats_rebuild();
-  // Reset the menu static caches.
-  menu_reset_static_cache();
+  // Synchronize any actions that were added or removed.
+  actions_synchronize();
 }
 
 /**
diff --git a/core/modules/system/system.test b/core/modules/system/system.test
index 4fdad7172e1d..1458ca742dfb 100644
--- a/core/modules/system/system.test
+++ b/core/modules/system/system.test
@@ -2184,7 +2184,7 @@ class SystemInfoAlterTestCase extends DrupalWebTestCase {
     // Enable our test module. Flush all caches, which we assert is the only
     // thing necessary to use the rebuilt {system}.info.
     module_enable(array('module_test'), FALSE);
-    drupal_flush_all_caches();
+    $this->resetAll();
     $this->assertTrue(module_exists('module_test'), t('Test module is enabled.'));
 
     $info = $this->getSystemInfo('seven', 'theme');
@@ -2199,7 +2199,7 @@ class SystemInfoAlterTestCase extends DrupalWebTestCase {
 
     // Disable the module and verify that {system}.info is rebuilt without it.
     module_disable(array('module_test'), FALSE);
-    drupal_flush_all_caches();
+    $this->resetAll();
     $this->assertFalse(module_exists('module_test'), t('Test module is disabled.'));
 
     $info = $this->getSystemInfo('seven', 'theme');
@@ -2294,7 +2294,7 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase {
     $this->drupalPost(NULL, array(), t('Continue'));
     $this->assertText(t('No pending updates.'), t('End of update process was reached.'));
     // Confirm that all caches were cleared.
-    $this->assertText(t('hook_flush_caches() invoked for update_script_test.module.'), 'Caches were cleared when there were no requirements warnings or errors.');
+    $this->assertText(t('hook_cache_flush() invoked for update_script_test.module.'), 'Caches were cleared when there were no requirements warnings or errors.');
 
     // If there is a requirements warning, we expect it to be initially
     // displayed, but clicking the link to proceed should allow us to go
@@ -2312,7 +2312,7 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase {
     $this->drupalPost(NULL, array(), t('Apply pending updates'));
     $this->assertText(t('The update_script_test_update_8000() update was executed successfully.'), t('End of update process was reached.'));
     // Confirm that all caches were cleared.
-    $this->assertText(t('hook_flush_caches() invoked for update_script_test.module.'), 'Caches were cleared after resolving a requirements warning and applying updates.');
+    $this->assertText(t('hook_cache_flush() invoked for update_script_test.module.'), 'Caches were cleared after resolving a requirements warning and applying updates.');
 
     // Now try again without pending updates to make sure that works too.
     $this->drupalGet($this->update_url, array('external' => TRUE));
@@ -2322,7 +2322,7 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase {
     $this->drupalPost(NULL, array(), t('Continue'));
     $this->assertText(t('No pending updates.'), t('End of update process was reached.'));
     // Confirm that all caches were cleared.
-    $this->assertText(t('hook_flush_caches() invoked for update_script_test.module.'), 'Caches were cleared after applying updates and re-running the script.');
+    $this->assertText(t('hook_cache_flush() invoked for update_script_test.module.'), 'Caches were cleared after applying updates and re-running the script.');
 
     // If there is a requirements error, it should be displayed even after
     // clicking the link to proceed (since the problem that triggered the error
diff --git a/core/modules/system/tests/cache.test b/core/modules/system/tests/cache.test
index e5d4435b98e0..66778ae0112c 100644
--- a/core/modules/system/tests/cache.test
+++ b/core/modules/system/tests/cache.test
@@ -321,8 +321,9 @@ class CacheClearCase extends CacheTestCase {
    */
   function testFlushAllCaches() {
     // Create cache entries for each flushed cache bin.
-    $bins = array('cache', 'filter', 'page', 'bootstrap', 'path');
-    $bins = array_merge(module_invoke_all('flush_caches'), $bins);
+    $bins = module_invoke_all('cache_flush');
+    $this->assertTrue($bins, 'hook_cache_flush() returned bins to flush.');
+    $bins = array_merge($bins, array('menu'));
     foreach ($bins as $id => $bin) {
       $cid = 'test_cid_clear' . $id;
       cache($bin)->set($cid, $this->default_value);
diff --git a/core/modules/system/tests/menu.test b/core/modules/system/tests/menu.test
index b38e9a170f16..791cd5c8b550 100644
--- a/core/modules/system/tests/menu.test
+++ b/core/modules/system/tests/menu.test
@@ -394,7 +394,7 @@ class MenuRouterTestCase extends DrupalWebTestCase {
     // Change the menu_name parameter in menu_test.module, then force a menu
     // rebuild.
     menu_test_menu_name('changed');
-    menu_rebuild();
+    menu_router_rebuild();
 
     $sql = "SELECT menu_name FROM {menu_links} WHERE router_path = 'menu_name_test'";
     $name = db_query($sql)->fetchField();
diff --git a/core/modules/system/tests/modules/update_script_test/update_script_test.module b/core/modules/system/tests/modules/update_script_test/update_script_test.module
index beb5a71ece55..986dc31ab3f1 100644
--- a/core/modules/system/tests/modules/update_script_test/update_script_test.module
+++ b/core/modules/system/tests/modules/update_script_test/update_script_test.module
@@ -6,13 +6,13 @@
  */
 
 /**
- * Implements hook_flush_caches().
+ * Implements hook_cache_flush().
  *
  * This sets a message to confirm that all caches are cleared whenever
  * update.php completes.
  *
  * @see UpdateScriptFunctionalTest::testRequirements()
  */
-function update_script_test_flush_caches() {
-  drupal_set_message(t('hook_flush_caches() invoked for update_script_test.module.'));
+function update_script_test_cache_flush() {
+  drupal_set_message(t('hook_cache_flush() invoked for update_script_test.module.'));
 }
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index 44d86d6af619..4b3eab29c82f 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -836,7 +836,7 @@ function _update_cache_clear($cid = NULL, $wildcard = FALSE) {
 }
 
 /**
- * Implements hook_flush_caches().
+ * Implements hook_cache_flush().
  *
  * Called from update.php (among others) to flush the caches.
  * Since we're running update.php, we are likely to install a new version of
@@ -851,7 +851,7 @@ function _update_cache_clear($cid = NULL, $wildcard = FALSE) {
  * check if the site is in MAINTENANCE_MODE == 'update' (which indicates
  * update.php is running, not update module... alas for overloaded names).
  */
-function update_flush_caches() {
+function update_cache_flush() {
   if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
     _update_cache_clear();
   }
diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install
index 30a943bacc7d..ec1da2c61caf 100644
--- a/profiles/standard/standard.install
+++ b/profiles/standard/standard.install
@@ -430,7 +430,7 @@ function standard_install() {
   menu_link_save($item);
 
   // Update the menu router information.
-  menu_rebuild();
+  menu_router_rebuild();
 
   // Enable the admin theme.
   db_update('system')
-- 
GitLab