Introduce importlib.util.ModuleManager which is a context manager to
handle providing (and cleaning up if needed) the module to be loaded.

A future commit will use the context manager in
Lib/importlib/_bootstrap.py and thus why the code is placed there
instead of in Lib/importlib/util.py.
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index 857f308..a4eab47 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -9,7 +9,7 @@
 #
 # IMPORTANT: Whenever making changes to this module, be sure to run
 # a top-level make in order to get the frozen version of the module
-# update. Not doing so, will result in the Makefile to fail for
+# update. Not doing so will result in the Makefile to fail for
 # all others who don't have a ./python around to freeze the module
 # in the early stages of compilation.
 #
@@ -20,10 +20,6 @@
 # reference any injected objects! This includes not only global code but also
 # anything specified at the class level.
 
-# XXX Make sure all public names have no single leading underscore and all
-#     others do.
-
-
 # Bootstrap-related code ######################################################
 
 _CASE_INSENSITIVE_PLATFORMS = 'win', 'cygwin', 'darwin'
@@ -498,6 +494,38 @@
         print(message.format(*args), file=sys.stderr)
 
 
+class ModuleManager:
+
+    """Context manager which returns the module to be loaded.
+
+    Does the proper unloading from sys.modules upon failure.
+
+    """
+
+    def __init__(self, name):
+        self._name = name
+
+    def __enter__(self):
+        self._module = sys.modules.get(self._name)
+        self._is_reload = self._module is not None
+        if not self._is_reload:
+            # This must be done before open() is called as the 'io' module
+            # implicitly imports 'locale' and would otherwise trigger an
+            # infinite loop.
+            self._module = new_module(self._name)
+            # This must be done before putting the module in sys.modules
+            # (otherwise an optimization shortcut in import.c becomes wrong)
+            self._module.__initializing__ = True
+            sys.modules[self._name] = self._module
+        return self._module
+
+    def __exit__(self, *args):
+        self._module.__initializing__ = False
+        del self._module
+        if any(arg is not None for arg in args) and not self._is_reload:
+            del sys.modules[self._name]
+
+
 def set_package(fxn):
     """Set __package__ on the returned module."""
     def set_package_wrapper(*args, **kwargs):
diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py
index 1316437..f817a40 100644
--- a/Lib/importlib/util.py
+++ b/Lib/importlib/util.py
@@ -1,5 +1,6 @@
 """Utility code for constructing importers, etc."""
 
+from ._bootstrap import ModuleManager
 from ._bootstrap import module_for_loader
 from ._bootstrap import set_loader
 from ._bootstrap import set_package
diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
index 5f08719..b986efd 100644
--- a/Lib/test/test_importlib/test_util.py
+++ b/Lib/test/test_importlib/test_util.py
@@ -2,10 +2,60 @@
 from . import util as test_util
 import imp
 import sys
+from test import support
 import types
 import unittest
 
 
+class ModuleManagerTests(unittest.TestCase):
+
+    module_name = 'ModuleManagerTest_module'
+
+    def setUp(self):
+        support.unload(self.module_name)
+        self.addCleanup(support.unload, self.module_name)
+
+    def test_new_module(self):
+        # Test a new module is created, inserted into sys.modules, has
+        # __initializing__ set to True after entering the context manager,
+        # and __initializing__ set to False after exiting.
+        with util.ModuleManager(self.module_name) as module:
+            self.assertIn(self.module_name, sys.modules)
+            self.assertIs(sys.modules[self.module_name], module)
+            self.assertTrue(module.__initializing__)
+        self.assertFalse(module.__initializing__)
+
+    def test_new_module_failed(self):
+        # Test the module is removed from sys.modules.
+        try:
+            with util.ModuleManager(self.module_name) as module:
+                self.assertIn(self.module_name, sys.modules)
+                raise exception
+        except Exception:
+            self.assertNotIn(self.module_name, sys.modules)
+        else:
+            self.fail('importlib.util.ModuleManager swallowed an exception')
+
+    def test_reload(self):
+        # Test that the same module is in sys.modules.
+        created_module = imp.new_module(self.module_name)
+        sys.modules[self.module_name] = created_module
+        with util.ModuleManager(self.module_name) as module:
+            self.assertIs(module, created_module)
+
+    def test_reload_failed(self):
+        # Test that the module was left in sys.modules.
+        created_module = imp.new_module(self.module_name)
+        sys.modules[self.module_name] = created_module
+        try:
+            with util.ModuleManager(self.module_name) as module:
+                raise Exception
+        except Exception:
+            self.assertIn(self.module_name, sys.modules)
+        else:
+            self.fail('importlib.util.ModuleManager swallowed an exception')
+
+
 class ModuleForLoaderTests(unittest.TestCase):
 
     """Tests for importlib.util.module_for_loader."""