From 449e36f6b55ccbde9e9d37ec32da8643c101b331 Mon Sep 17 00:00:00 2001
From: mmorris <mmorris@tombras.com>
Date: Mon, 18 May 2015 16:33:35 -0400
Subject: [PATCH] I suspect the out of memory issues are caused by repeated
 registry of the uninstall handler.

---
 core/lib/Drupal/Core/Extension/ModuleInstaller.php | 96 +++++++++++++---------
 1 file changed, 58 insertions(+), 38 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index c8bce9c..05284a8 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -52,18 +52,25 @@ class ModuleInstaller implements ModuleInstallerInterface {
   protected $uninstallValidators;
 
   /**
-   * The rollback flag.
+   * The module we are currently attempting to install.
    *
-   * @var boolean
+   * @var string
    */
-  protected $rollback = FALSE;
+  protected $module = '';
 
   /**
-   * The module we are currently attempting to install.
+   * The active module installer.
    *
-   * @var string
+   * @var $this
    */
-  protected $module = '';
+  protected static $activeInstaller = NULL;
+
+  /**
+   * Flag of whether we've registered our shutdown handler to do rollbacks.
+   *
+   * @var boolean
+   */
+  protected static $handlerRegistered = FALSE;
 
   /**
    * Constructs a new ModuleInstaller instance.
@@ -82,6 +89,13 @@ public function __construct($root, ModuleHandlerInterface $module_handler, Drupa
     $this->root = $root;
     $this->moduleHandler = $module_handler;
     $this->kernel = $kernel;
+
+    // Register the handler only once no matter how many times this class or the
+    // install method gets called.
+    if (!static::$handlerRegistered) {
+      register_shutdown_function([get_class(), 'rollbackModuleInstallFailureHandler']);
+      static::$handlerRegistered = TRUE;
+    }
   }
 
   /**
@@ -155,12 +169,6 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
     }
     $modules_installed = array();
 
-    // Preliminary checks done - now we are going to start working with
-    // potentially untested code. Prepare a shutdown handler to undo any
-    // hanging installs resulting from a failure of module code during this
-    // install process.
-    register_shutdown_function([$this, 'rollbackModuleInstallFailure']);
-
     foreach ($module_list as $module) {
       $enabled = $extension_config->get("module.$module") !== NULL;
       if (!$enabled) {
@@ -176,8 +184,10 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         // exceptions if the configuration is not valid.
         $config_installer->checkConfigurationToInstall('module', $module);
 
-        // From here out we're making changes that may require rollback.
-        $this->rollback = TRUE;
+        // From here out we're making changes that may require rollback. Set
+        // the active installer to this object and mark which module we are
+        // working on.
+        static::$activeInstaller = $this;
         $this->module = $module;
 
         // Save this data without checking schema. This is a performance
@@ -304,8 +314,9 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         // Record the fact that it was installed.
         \Drupal::logger('system')->info('%module module installed.', array('%module' => $module));
 
-        // Release the rollback flag.
-        $this->rollback = FALSE;
+        // Release active installer - preventing the module from being
+        // uninstalled on script closure.
+        static::$activeInstaller = NULL;
         $this->module = '';
       }
     }
@@ -352,29 +363,38 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
    *
    * @see https://www.drupal.org/node/2474363
    */
-  public function rollbackModuleInstallFailure() {
-    // register_shutdown_function always fires, error or not, so only rollback
-    // when needed.
-    if ($this->rollback) {
-      $this->deleteEntityBundles();
-      module_load_install($this->module);
-
-      // Remove all configuration belonging to the module.
-      \Drupal::service('config.manager')->uninstall('module', $this->module);
-      $this->deleteEntityTypes();
-
-      // Remove the schema.
-      drupal_uninstall_schema($this->module);
-
-      // Remove the module's entry from the config. Don't check schema when
-      // uninstalling a module since we are only clearing a key.
-      \Drupal::configFactory()->getEditable('core.extension')->clear("module.{$this->module}")->save(TRUE);
+  protected function rollback() {
+    $this->deleteEntityBundles();
+    module_load_install($this->module);
+
+    // Remove all configuration belonging to the module.
+    \Drupal::service('config.manager')->uninstall('module', $this->module);
+    $this->deleteEntityTypes();
+
+    // Remove the schema.
+    drupal_uninstall_schema($this->module);
+
+    // Remove the module's entry from the config. Don't check schema when
+    // uninstalling a module since we are only clearing a key.
+    \Drupal::configFactory()->getEditable('core.extension')->clear("module.{$this->module}")->save(TRUE);
+
+    // Update the kernel to exclude the uninstalled modules.
+    $this->updateKernel($this->unregisterFromModuleHandler());
+    $this->uncacheModule();
+    $this->refreshThemes();
+    $this->removeModuleSchema();
+  }
 
-      // Update the kernel to exclude the uninstalled modules.
-      $this->updateKernel($this->unregisterFromModuleHandler());
-      $this->uncacheModule();
-      $this->refreshThemes();
-      $this->removeModuleSchema();
+  /**
+   * Handler called by register_shutdown_function
+   *
+   * This gets called whenever the script exits whether or not there was an
+   * error. Therefore we only keep the activeInstaller set during the time that
+   * the system is vulnerable to crashing and leave it NULL at all other times.
+   */
+  public static function rollbackModuleInstallFailureHandler() {
+    if (static::$activeInstaller) {
+      static::$activeInstaller->rollback();
     }
   }
 
-- 
1.8.4.2

