diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 60b086a..7faa97a 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -1,5 +1,6 @@
 <?php
 
+use Drupal\Component\PhpStorage\PhpStorageFactory;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\Settings;
 use Drupal\Core\DrupalKernel;
@@ -3615,3 +3616,105 @@ function lock() {
 /**
  * @} End of "defgroup lock".
  */
+
+/**
+ * Unfreezes Drupal from a corrupted/deadlock situation.
+ *
+ * Under various conditions, the registered and the available services may
+ * mismatch, preventing the system to boot a kernel and any subsystems,
+ * eventually leaving the system in a non-functional state, of which it cannot
+ * recover on its own due to circular dependencies.
+ *
+ * In such cases, put the following code into a new @code unfreeze.php @endcode
+ * file in your site's root folder:
+ * @code
+ * define('DRUPAL_ROOT', getcwd());
+ * require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc';
+ * drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+ * drupal_rebuild();
+ * @endcode
+ * Invoke the script either by accessing it through the browser or via the
+ * command line. Make sure to disable or delete the script file afterwards.
+ *
+ * This function is essentially a wrapper around drupal_flush_all_caches(),
+ * which performs all required tasks already, but which requires a functional
+ * kernel, base system, and module system to operate. Use the drupal_rebuild()
+ * function if these base systems are corrupted.
+ *
+ * Do not use this function to flush all caches and rebuild the system at
+ * regular runtime. Use drupal_flush_all_caches() directly instead.
+ */
+function drupal_rebuild() {
+  // First, there has to be a functional DrupalKernel, since all following
+  // operations depend on services being available, and the services itself
+  // partially require the module system to be available (which in turn requires
+  // the kernel).
+
+  // Ensure the base system has been booted. Booting to
+  // DRUPAL_BOOTSTRAP_CONFIGURATION is always possible and should never fail,
+  // since it does not involve any complex operations.
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+
+  // Load the code that would normally be loaded between
+  // DRUPAL_BOOTSTRAP_CONFIGURATION and DRUPAL_BOOTSTRAP_VARIABLES.
+  // @see _drupal_bootstrap_page_cache()
+  require_once DRUPAL_ROOT . '/core/includes/cache.inc';
+  if (!empty($conf['cache_backends'])) {
+    foreach ($conf['cache_backends'] as $include) {
+      require_once DRUPAL_ROOT . '/' . $include;
+    }
+  }
+  // @see _drupal_bootstrap_database()
+  require_once DRUPAL_ROOT . '/core/includes/database.inc';
+  // @see _drupal_bootstrap_variables()
+  require_once DRUPAL_ROOT . '/core/includes/module.inc';
+
+  // In a disaster recovery situation, it is not possible to boot to
+  // DRUPAL_BOOTSTRAP_VARIABLES, since the second bootstrap phase tries to boot
+  // a kernel already. The PhpStorageFactory calls below require the public
+  // files path though, so ensure it is known.
+  // @todo Once file system settings are converted to configuration, this means
+  //   a massive circular dependency, since the configuration system requires a
+  //   working kernel.
+  // We cannot use variable_initialize(), since it requires the cache and lock
+  // services already.
+  // @see _drupal_bootstrap_variables()
+  // @see variable_initialize()
+  global $conf;
+  $variables = array_map('unserialize', Database::getConnection()->query('SELECT name, value FROM {variable}')->fetchAllKeyed());
+  $conf += $variables;
+
+  // The current kernel may contain outdated/bogus definitions, so ensure that
+  // all dumped containers no longer exist.
+  // Dependencies:
+  // - global $conf « drupal_settings_initialize(), DRUPAL_BOOTSTRAP_CONFIGURATION
+  // - $conf['file_public_path'] « variable_initialize(), DRUPAL_BOOTSTRAP_VARIABLES
+  PhpStorageFactory::get('service_container')->deleteAll();
+  PhpStorageFactory::get('twig')->deleteAll();
+
+  // Build and boot a new kernel.
+  // Do not write it to disk, since drupal_flush_all_caches() will do that.
+  // Dependencies:
+  // - $conf['drupal_bootstrap_config_storage'] « drupal_settings_initialize()
+  // - conf_path()
+  // - global $config_directories « drupal_settings_initialize()
+  $kernel = new DrupalKernel('prod', FALSE, drupal_classloader(), FALSE);
+  $kernel->boot();
+
+  // Second, now that the kernel has been rebuilt and rebooted from scratch and
+  // services should be available, ensure that base system services do not
+  // operate on stale/outdated data. This should allow us to boot the module
+  // system and eventually to DRUPAL_BOOTSTRAP_CODE.
+
+  // Flush the primary bootstrap cache bins to ensure that data is not loaded
+  // from outdated caches.
+  cache('bootstrap')->deleteAll();
+  cache('config')->deleteAll();
+
+  // Boot all remaining subsystems.
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
+
+  // Third, flush all caches and rebuild data structures.
+  drupal_flush_all_caches();
+}
+
diff --git a/core/includes/common.inc b/core/includes/common.inc
index f3d8f2c..d0f8512 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -8,6 +8,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Database\Database;
+use Drupal\Core\DrupalKernel;
 use Drupal\Core\SystemListingInfo;
 use Drupal\Core\Template\Attribute;
 
@@ -6432,14 +6433,29 @@ function drupal_flush_all_caches() {
   // Clear all non-drupal_static() static caches.
   // None currently; kept if any static caches need to be reset in the future.
 
+  // Wipe the PHP Storage caches.
+  PhpStorageFactory::get('service_container')->deleteAll();
+  PhpStorageFactory::get('twig')->deleteAll();
+
   // Rebuild module and theme data.
-  system_rebuild_module_data();
+  $module_data = system_rebuild_module_data();
   system_rebuild_theme_data();
 
   // Ensure that all modules that are currently supposed to be enabled are
   // actually loaded.
   drupal_container()->get('module_handler')->loadAll();
 
+  // Rebuild and reboot a new kernel.
+  // A simple DrupalKernel::reboot() is not sufficient, since the list of
+  // enabled modules might have been adjusted above due to changed code.
+  $files = array();
+  foreach ($module_data as $module => $data) {
+    if (isset($data->uri)) {
+      $files[$module] = $data->uri;
+    }
+  }
+  drupal_container()->get('kernel')->updateModules(module_list(), $files);
+
   // Update the list of bootstrap modules.
   // Allows developers to get new hook_boot() implementations registered without
   // having to write a hook_update_N() function.
@@ -6462,10 +6478,6 @@ function drupal_flush_all_caches() {
   drupal_container()->get('router.builder')->rebuild();
   menu_router_rebuild();
 
-  // Wipe the PHP Storage caches.
-  PhpStorageFactory::get('service_container')->deleteAll();
-  PhpStorageFactory::get('twig')->deleteAll();
-
   // 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.
diff --git a/core/includes/module.inc b/core/includes/module.inc
index d99b39d..2c515bb 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -284,11 +284,13 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
 
   $modules_installed = array();
   $modules_enabled = array();
-  $schema_store = drupal_container()->get('keyvalue')->get('system.schema');
-  $module_config = config('system.module');
-  $disabled_config = config('system.module.disabled');
-  $module_handler = drupal_container()->get('module_handler');
   foreach ($module_list as $module) {
+    // Each iteration through this loop, there's potentially a new
+    // drupal_container() so refetch these objects.
+    $module_config = config('system.module');
+    $disabled_config = config('system.module.disabled');
+    $module_handler = drupal_container()->get('module_handler');
+
     // Only process modules that are not already enabled.
     // A module is only enabled if it is configured as enabled. Custom or
     // overridden module handlers might contain the module already, which means
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 624c716..9906e2e 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -52,8 +52,7 @@ public function build(ContainerBuilder $container) {
 
     $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
       ->addArgument(new Reference('config.storage'))
-      ->addArgument(new Reference('event_dispatcher'))
-      ->addTag('persist');
+      ->addArgument(new Reference('event_dispatcher'));
 
     // Register staging configuration storage.
     $container
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index d56f503..91f8e1b 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -150,6 +150,14 @@ public function boot() {
   }
 
   /**
+   * Implements DrupalKernelInterface::reboot().
+   */
+  public function reboot() {
+    $this->booted = FALSE;
+    $this->boot();
+  }
+
+  /**
    * Returns an array of available bundles.
    *
    * @return array
@@ -236,8 +244,7 @@ public function updateModules(array $module_list, array $module_filenames = arra
     // list will take effect when boot() is called. If we have already booted,
     // then reboot in order to refresh the bundle list and container.
     if ($this->booted) {
-      $this->booted = FALSE;
-      $this->boot();
+      $this->reboot();
     }
   }
 
@@ -375,7 +382,19 @@ protected function buildContainer() {
     foreach ($this->bundles as $bundle) {
       $bundle->build($container);
     }
-    $container->setParameter('persistIds', array_keys($container->findTaggedServiceIds('persist')));
+
+    // Identify all services whose instances should be persisted when rebuilding
+    // the container during the lifetime of the kernel (e.g., during a kernel
+    // reboot). Include synthetic services, because by definition, they cannot
+    // be automatically reinstantiated. Also include services tagged to persist.
+    $persist_ids = array();
+    foreach ($container->getDefinitions() as $id => $definition) {
+      if ($definition->isSynthetic() || $definition->getTag('persist')) {
+        $persist_ids[] = $id;
+      }
+    }
+    $container->setParameter('persistIds', $persist_ids);
+
     $container->compile();
     return $container;
   }
diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php
index 60d0403..34c2df5 100644
--- a/core/lib/Drupal/Core/DrupalKernelInterface.php
+++ b/core/lib/Drupal/Core/DrupalKernelInterface.php
@@ -18,6 +18,16 @@
 interface DrupalKernelInterface extends KernelInterface {
 
   /**
+   * Reboots an already booted kernel.
+   *
+   * Rebooting ensures that all instances of services being tagged with
+   * 'persist' are moved over to the new kernel instance.
+   *
+   * @see DrupalKernelInterface::updateModules()
+   */
+  public function reboot();
+
+  /**
    * Updates the kernel's list of modules to the new list.
    *
    * The kernel needs to update its bundle list and container to match the new
diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php
index f3b834a..e4fb87e 100644
--- a/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php
+++ b/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php
@@ -67,7 +67,6 @@ function testWithoutEditorAvailable() {
 
     // Make a text editor available.
     module_enable(array('editor_test'));
-    $this->rebuildContainer();
     $this->resetAll();
     $this->drupalGet('admin/config/content/formats/filtered_html');
 
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
index b17e371..89ed34f 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -87,10 +87,8 @@ protected function setUp() {
     state()->set('system.theme.files', $this->themeFiles);
     state()->set('system.theme.data', $this->themeData);
 
-    // Bootstrap the kernel.
-    // No need to dump it; this test runs in-memory.
-    $this->kernel = new DrupalKernel('testing', TRUE, drupal_classloader(), FALSE);
-    $this->kernel->boot();
+    // Prepare the kernel.
+    $this->prepareKernel();
 
     // Collect and set a fixed module list.
     $class = get_class($this);
@@ -137,7 +135,7 @@ public function containerBuild($container) {
       // away with a simple container holding the absolute bare minimum. When
       // a kernel is overridden then there's no need to re-register the keyvalue
       // service but when a test is happy with the superminimal container put
-      // together here, it still might a keyvalue storage for anything (for 
+      // together here, it still might a keyvalue storage for anything (for
       // eg. module_enable) using state() -- that's why a memory service was
       // added in the first place.
       $container
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index b337e4c..9d71a59 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -930,17 +930,13 @@ protected function prepareConfigDirectories() {
   }
 
   /**
-   * Rebuild drupal_container().
+   * Prepares the DrupalKernel to use for the test.
    *
-   * Use this to build a new kernel and service container. For example, when the
-   * list of enabled modules is changed via the internal browser, in which case
-   * the test process still contains an old kernel and service container with an
-   * old module list.
-   *
-   * @todo Fix http://drupal.org/node/1708692 so that module enable/disable
-   *   changes are immediately reflected in drupal_container(). Until then,
-   *   tests can invoke this workaround when requiring services from newly
-   *   enabled modules to be immediately available in the same request.
+   * The kernel's primary responsibility is to initialize the dependency
+   * injection container, which it stores in drupal_container(). Tests can
+   * access the container via $this->container, so we copy it to there as well.
+   * TestBase::tearDown() restores drupal_container() to the original container
+   * that existed prior to TestBase::prepareEnvironment().
    *
    * @see TestBase::prepareEnvironment()
    * @see TestBase::tearDown()
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index e6cc158..9af1f09 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -794,10 +794,16 @@ protected function setUp() {
     foreach ($variables as $name => $value) {
       $GLOBALS['conf'][$name] = $value;
     }
+
     // Execute the non-interactive installer.
     require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
     install_drupal($settings);
-    $this->rebuildContainer();
+
+    // The installer uses a 'install' kernel. Now that the installation is
+    // finished, prepare a 'testing' kernel.
+    $this->prepareKernel();
+
+    // Save overridden $conf variables to the database.
     foreach ($variables as $name => $value) {
       variable_set($name, $value);
     }
@@ -828,7 +834,6 @@ protected function setUp() {
     if ($modules) {
       $success = module_enable($modules, TRUE);
       $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
-      $this->rebuildContainer();
     }
 
     // Reset/rebuild all data structures after enabling the modules.
@@ -850,14 +855,19 @@ protected function setUp() {
   /**
    * Reset all data structures after having enabled new modules.
    *
-   * This method is called by Drupal\simpletest\WebTestBase::setUp() after enabling
-   * the requested modules. It must be called again when additional modules
-   * are enabled later.
+   * This method is called by Drupal\simpletest\WebTestBase::setUp() after
+   * enabling the requested modules. It must be called again when additional
+   * modules are enabled later.
    */
   protected function resetAll() {
     // Clear all database and static caches and rebuild data structures.
     drupal_flush_all_caches();
 
+    // When modules are enabled or caches flushed, the container in
+    // drupal_container() is replaced with a rebuilt object. Update the
+    // container used by tests to match.
+    $this->container = drupal_container();
+
     // Reload global $conf array and permissions.
     $this->refreshVariables();
     $this->checkPermissions(array(), TRUE);
@@ -3157,4 +3167,5 @@ protected function verboseEmail($count = 1) {
       $this->verbose(t('Email:') . '<pre>' . print_r($mail, TRUE) . '</pre>');
     }
   }
+
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
index c4dda65..de007cd 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -213,10 +213,11 @@ protected function performUpgrade($register_errors = TRUE) {
       throw new Exception('An error was encountered during the first access to update.php.');
     }
 
-    // Initialize config directories and rebuild the service container after
-    // creating them in the first step.
+    // Initialize config directories.
     parent::prepareConfigDirectories();
-    $this->rebuildContainer();
+
+    // Prepare the kernel.
+    $this->prepareKernel();
 
     // Continue.
     $this->drupalPost(NULL, array(), t('Continue'));
@@ -270,9 +271,10 @@ protected function performUpgrade($register_errors = TRUE) {
     // but not on the test client.
     drupal_container()->get('module_handler')->resetImplementations();
     drupal_container()->get('module_handler')->reload();
+    $module_list = $this->container->get('module_handler')->getModuleList();
+    $this->kernel->updateModules($module_list);
 
-    // Rebuild the container and all caches.
-    $this->rebuildContainer();
+    // Reset all caches.
     $this->resetAll();
 
     return TRUE;
