Skip to content

Commit c93c58b

Browse files
bpo-33331: Clean modules in the reversed order in PyImport_Cleanup(). (GH-6565)
Modules imported last are now cleared first at interpreter shutdown. A newly imported module is moved to the end of sys.modules, behind modules on which it depends.
1 parent 542497a commit c93c58b

File tree

4 files changed

+1813
-1845
lines changed

4 files changed

+1813
-1845
lines changed

Lib/importlib/_bootstrap.py

Lines changed: 62 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -302,33 +302,6 @@ def _module_repr(module):
302302
return '<module {!r} from {!r}>'.format(name, filename)
303303

304304

305-
class _installed_safely:
306-
307-
def __init__(self, module):
308-
self._module = module
309-
self._spec = module.__spec__
310-
311-
def __enter__(self):
312-
# This must be done before putting the module in sys.modules
313-
# (otherwise an optimization shortcut in import.c becomes
314-
# wrong)
315-
self._spec._initializing = True
316-
sys.modules[self._spec.name] = self._module
317-
318-
def __exit__(self, *args):
319-
try:
320-
spec = self._spec
321-
if any(arg is not None for arg in args):
322-
try:
323-
del sys.modules[spec.name]
324-
except KeyError:
325-
pass
326-
else:
327-
_verbose_message('import {!r} # {!r}', spec.name, spec.loader)
328-
finally:
329-
self._spec._initializing = False
330-
331-
332305
class ModuleSpec:
333306
"""The specification for a module, used for loading.
334307
@@ -614,30 +587,44 @@ def _exec(spec, module):
614587
if sys.modules.get(name) is not module:
615588
msg = 'module {!r} not in sys.modules'.format(name)
616589
raise ImportError(msg, name=name)
617-
if spec.loader is None:
618-
if spec.submodule_search_locations is None:
619-
raise ImportError('missing loader', name=spec.name)
620-
# namespace package
621-
_init_module_attrs(spec, module, override=True)
622-
return module
623-
_init_module_attrs(spec, module, override=True)
624-
if not hasattr(spec.loader, 'exec_module'):
625-
# (issue19713) Once BuiltinImporter and ExtensionFileLoader
626-
# have exec_module() implemented, we can add a deprecation
627-
# warning here.
628-
spec.loader.load_module(name)
629-
else:
630-
spec.loader.exec_module(module)
631-
return sys.modules[name]
590+
try:
591+
if spec.loader is None:
592+
if spec.submodule_search_locations is None:
593+
raise ImportError('missing loader', name=spec.name)
594+
# Namespace package.
595+
_init_module_attrs(spec, module, override=True)
596+
else:
597+
_init_module_attrs(spec, module, override=True)
598+
if not hasattr(spec.loader, 'exec_module'):
599+
# (issue19713) Once BuiltinImporter and ExtensionFileLoader
600+
# have exec_module() implemented, we can add a deprecation
601+
# warning here.
602+
spec.loader.load_module(name)
603+
else:
604+
spec.loader.exec_module(module)
605+
finally:
606+
# Update the order of insertion into sys.modules for module
607+
# clean-up at shutdown.
608+
module = sys.modules.pop(spec.name)
609+
sys.modules[spec.name] = module
610+
return module
632611

633612

634613
def _load_backward_compatible(spec):
635614
# (issue19713) Once BuiltinImporter and ExtensionFileLoader
636615
# have exec_module() implemented, we can add a deprecation
637616
# warning here.
638-
spec.loader.load_module(spec.name)
617+
try:
618+
spec.loader.load_module(spec.name)
619+
except:
620+
if spec.name in sys.modules:
621+
module = sys.modules.pop(spec.name)
622+
sys.modules[spec.name] = module
623+
raise
639624
# The module must be in sys.modules at this point!
640-
module = sys.modules[spec.name]
625+
# Move it to the end of sys.modules.
626+
module = sys.modules.pop(spec.name)
627+
sys.modules[spec.name] = module
641628
if getattr(module, '__loader__', None) is None:
642629
try:
643630
module.__loader__ = spec.loader
@@ -663,23 +650,42 @@ def _load_backward_compatible(spec):
663650
def _load_unlocked(spec):
664651
# A helper for direct use by the import system.
665652
if spec.loader is not None:
666-
# not a namespace package
653+
# Not a namespace package.
667654
if not hasattr(spec.loader, 'exec_module'):
668655
return _load_backward_compatible(spec)
669656

670657
module = module_from_spec(spec)
671-
with _installed_safely(module):
672-
if spec.loader is None:
673-
if spec.submodule_search_locations is None:
674-
raise ImportError('missing loader', name=spec.name)
675-
# A namespace package so do nothing.
676-
else:
677-
spec.loader.exec_module(module)
678658

679-
# We don't ensure that the import-related module attributes get
680-
# set in the sys.modules replacement case. Such modules are on
681-
# their own.
682-
return sys.modules[spec.name]
659+
# This must be done before putting the module in sys.modules
660+
# (otherwise an optimization shortcut in import.c becomes
661+
# wrong).
662+
spec._initializing = True
663+
try:
664+
sys.modules[spec.name] = module
665+
try:
666+
if spec.loader is None:
667+
if spec.submodule_search_locations is None:
668+
raise ImportError('missing loader', name=spec.name)
669+
# A namespace package so do nothing.
670+
else:
671+
spec.loader.exec_module(module)
672+
except:
673+
try:
674+
del sys.modules[spec.name]
675+
except KeyError:
676+
pass
677+
raise
678+
# Move the module to the end of sys.modules.
679+
# We don't ensure that the import-related module attributes get
680+
# set in the sys.modules replacement case. Such modules are on
681+
# their own.
682+
module = sys.modules.pop(spec.name)
683+
sys.modules[spec.name] = module
684+
_verbose_message('import {!r} # {!r}', spec.name, spec.loader)
685+
finally:
686+
spec._initializing = False
687+
688+
return module
683689

684690
# A method used during testing of _load_unlocked() and by
685691
# _load_module_shim().
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Modules imported last are now cleared first at interpreter shutdown.

Python/import.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -543,9 +543,10 @@ PyImport_Cleanup(void)
543543
module last. Likewise, we don't delete sys until the very
544544
end because it is implicitly referenced (e.g. by print). */
545545
if (weaklist != NULL) {
546-
Py_ssize_t i, n;
547-
n = PyList_GET_SIZE(weaklist);
548-
for (i = 0; i < n; i++) {
546+
Py_ssize_t i;
547+
/* Since dict is ordered in CPython 3.6+, modules are saved in
548+
importing order. First clear modules imported later. */
549+
for (i = PyList_GET_SIZE(weaklist) - 1; i >= 0; i--) {
549550
PyObject *tup = PyList_GET_ITEM(weaklist, i);
550551
PyObject *name = PyTuple_GET_ITEM(tup, 0);
551552
PyObject *mod = PyWeakref_GET_OBJECT(PyTuple_GET_ITEM(tup, 1));

0 commit comments

Comments
 (0)