diff --git a/core/includes/update.inc b/core/includes/update.inc
index 5582bb4..6794ca7 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -578,6 +578,11 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $
  * @see update_batch()
  */
 function update_finished($success, $results, $operations) {
+  // Unlock the module system to flush all caches.
+  module_list_reset();
+  // Load all modules, so data structures can be properly rebuilt.
+  module_load_all(FALSE, TRUE);
+
   // Clear the caches in case the data has been updated.
   drupal_flush_all_caches();
 
diff --git a/core/modules/field/modules/field_sql_storage/field_sql_storage.install b/core/modules/field/modules/field_sql_storage/field_sql_storage.install
index 2229ef4..0bbdc5a 100644
--- a/core/modules/field/modules/field_sql_storage/field_sql_storage.install
+++ b/core/modules/field/modules/field_sql_storage/field_sql_storage.install
@@ -111,6 +111,9 @@ function field_sql_storage_update_8000(&$sandbox) {
   // Retrieve field data.
   $fields = _update_7000_field_read_fields(array('storage_type' => 'field_sql_storage'));
 
+  // Load private table name helper functions in field_sql_storage.module.
+  drupal_load('module', 'field_sql_storage');
+
   // Update schema.
   foreach ($fields as $field) {
     $data_table = _field_sql_storage_tablename($field);
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 a0f6c59..86456e5 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -191,13 +191,13 @@ protected function performUpgrade($register_errors = TRUE) {
     $update_url = $GLOBALS['base_url'] . '/core/update.php';
     $this->drupalGet($update_url, array('external' => TRUE));
     if (!$this->assertResponse(200)) {
-      return FALSE;
+      throw new Exception('Initial GET to update.php did not return HTTP 200 status.');
     }
 
     // Continue.
     $this->drupalPost(NULL, array(), t('Continue'));
     if (!$this->assertResponse(200)) {
-      return FALSE;
+      throw new Exception('POST to continue update.php did not return HTTP 200 status.');
     }
 
     // The test should pass if there are no pending updates.
@@ -211,7 +211,7 @@ protected function performUpgrade($register_errors = TRUE) {
     // Go!
     $this->drupalPost(NULL, array(), t('Apply pending updates'));
     if (!$this->assertResponse(200)) {
-      return FALSE;
+      throw new Exception('POST to update.php to apply pending updates did not return HTTP 200 status.');
     }
 
     // Check for errors during the update process.
@@ -222,36 +222,30 @@ protected function performUpgrade($register_errors = TRUE) {
         $this->fail($message);
       }
     }
-
     if (!empty($this->upgradeErrors)) {
       // Upgrade failed, the installation might be in an inconsistent state,
       // don't process.
-      return FALSE;
+      throw new Exception('Errors during update process.');
     }
 
     // Check if there still are pending updates.
     $this->drupalGet($update_url, array('external' => TRUE));
     $this->drupalPost(NULL, array(), t('Continue'));
     if (!$this->assertText(t('No pending updates.'), t('No pending updates at the end of the update process.'))) {
-      return FALSE;
+      throw new Exception('update.php still shows pending updates after execution.');
     }
 
     // Upgrade succeed, rebuild the environment so that we can call the API
     // of the child site directly from this request.
     $this->upgradedSite = TRUE;
 
-    // Reload module list. For modules that are enabled in the test database,
-    // but not on the test client, we need to load the code here.
+    // Reload module list for modules that are enabled in the test database
+    // but not on the test client.
     system_list_reset();
-    foreach (module_list() as $module) {
-      drupal_load('module', $module);
-    }
-
-    // Reload hook implementations
     module_implements_reset();
+    module_load_all(FALSE, TRUE);
 
     // Rebuild caches.
-    drupal_static_reset();
     drupal_flush_all_caches();
 
     // Reload global $conf array and permissions.
diff --git a/core/modules/user/user.install b/core/modules/user/user.install
index 67957da..a22b2d8 100644
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -351,11 +351,11 @@ function user_install() {
  * The 'Member for' extra field has moved one level up in the array.
  */
 function user_update_8000() {
-  $settings = field_bundle_settings('user', 'user');
+  $settings = update_variable_get('field_bundle_settings_user__user', array());
   if (isset($settings['extra_fields']['display']['summary'])) {
     $settings['extra_fields']['display']['member_for'] = $settings['extra_fields']['display']['summary'];
     unset($settings['extra_fields']['display']['summary']);
-    field_bundle_settings('user', 'user', $settings);
+    update_variable_set('field_bundle_settings_user__user', $settings);
   }
 }
 
diff --git a/core/update.php b/core/update.php
index b8a726d..d1dc65f 100644
--- a/core/update.php
+++ b/core/update.php
@@ -129,6 +129,11 @@ function update_script_selection_form($form, &$form_state) {
       '#links' => update_helpful_links(),
     );
 
+    // Unlock the module system to flush all caches.
+    module_list_reset();
+    // Load all modules, so data structures can be properly rebuilt.
+    module_load_all(FALSE, TRUE);
+
     // No updates to run, so caches won't get flushed later.  Clear them now.
     drupal_flush_all_caches();
   }
@@ -366,10 +371,6 @@ function update_check_requirements($skip_warnings = FALSE) {
   }
 }
 
-// Some unavoidable errors happen because the database is not yet up-to-date.
-// Our custom error handler is not yet installed, so we just suppress them.
-ini_set('display_errors', FALSE);
-
 // We prepare a minimal bootstrap for the update requirements check to avoid
 // reaching the PHP memory limit.
 require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc';
@@ -378,6 +379,23 @@ function update_check_requirements($skip_warnings = FALSE) {
 require_once DRUPAL_ROOT . '/core/includes/file.inc';
 require_once DRUPAL_ROOT . '/core/includes/unicode.inc';
 require_once DRUPAL_ROOT . '/core/includes/schema.inc';
+
+// The module list must be limited to System module for almost all parts of the
+// update process to prevent module APIs and hooks from being invoked too early
+// (trying to act on data structures that are not updated yet).
+// This especially applies to early bootstrap preparations when upgrading to a
+// new major version of Drupal core. Unfortunately, however, the initial
+// information and update selection pages (and forms) need to be able to invoke
+// hook_requirements() in all modules and also access other APIs. Therefore, the
+// effective module list has to be switched back and forth multiple times during
+// the update.php process.
+include_once DRUPAL_ROOT . '/core/includes/module.inc';
+$module_list['system']['filename'] = 'core/modules/system/system.module';
+
+// Lock the module list for the update bootstrap and performing initial system
+// changes.
+module_list(NULL, $module_list);
+
 update_prepare_d8_bootstrap();
 
 // Determine if the current user has access to run update.php.
@@ -404,9 +422,6 @@ function update_check_requirements($skip_warnings = FALSE) {
   require_once DRUPAL_ROOT . '/core/modules/system/system.install';
 
   // Load module basics.
-  include_once DRUPAL_ROOT . '/core/includes/module.inc';
-  $module_list['system']['filename'] = 'core/modules/system/system.module';
-  module_list(NULL, $module_list);
   drupal_load('module', 'system');
 
   // Reset the module_implements() cache so that any new hook implementations
@@ -419,6 +434,9 @@ function update_check_requirements($skip_warnings = FALSE) {
   // Set up theme system for the maintenance page.
   drupal_maintenance_theme();
 
+  // Unlock the module system to perform update requirements checks.
+  module_list_reset();
+
   // Check the update requirements for Drupal. Only report on errors at this
   // stage, since the real requirements check happens further down.
   update_check_requirements(TRUE);
@@ -434,10 +452,6 @@ function update_check_requirements($skip_warnings = FALSE) {
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 drupal_maintenance_theme();
 
-// Turn error reporting back on. From now on, only fatal errors (which are
-// not passed through the error handler) will cause a message to be printed.
-ini_set('display_errors', TRUE);
-
 
 // Only proceed with updates if the user is allowed to run them.
 if (update_access_allowed()) {
@@ -448,6 +462,9 @@ function update_check_requirements($skip_warnings = FALSE) {
 
   update_fix_compatibility();
 
+  // Unlock the module system to perform update requirements checks.
+  module_list_reset();
+
   // Check the update requirements for all modules. If there are warnings, but
   // no errors, skip reporting them if the user has provided a URL parameter
   // acknowledging the warnings and indicating a desire to continue anyway. See
@@ -456,6 +473,9 @@ function update_check_requirements($skip_warnings = FALSE) {
   $skip_warnings = !empty($continue);
   update_check_requirements($skip_warnings);
 
+  // Re-lock the module system.
+  module_list(NULL, $module_list);
+
   switch ($op) {
     // update.php ops.
 
@@ -483,6 +503,11 @@ function update_check_requirements($skip_warnings = FALSE) {
       break;
 
     case 'results':
+      // Unlock the module system after performing updates.
+      module_list_reset();
+      // Load all modules to flush all caches and rebuild data structures.
+      module_load_all(FALSE, TRUE);
+
       $output = update_results_page();
       break;
 
