Commit c93c58b5 authored by Serhiy Storchaka's avatar Serhiy Storchaka Committed by GitHub

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.
parent 542497aa
...@@ -302,33 +302,6 @@ def _module_repr(module): ...@@ -302,33 +302,6 @@ def _module_repr(module):
return '<module {!r} from {!r}>'.format(name, filename) return '<module {!r} from {!r}>'.format(name, filename)
class _installed_safely:
def __init__(self, module):
self._module = module
self._spec = module.__spec__
def __enter__(self):
# This must be done before putting the module in sys.modules
# (otherwise an optimization shortcut in import.c becomes
# wrong)
self._spec._initializing = True
sys.modules[self._spec.name] = self._module
def __exit__(self, *args):
try:
spec = self._spec
if any(arg is not None for arg in args):
try:
del sys.modules[spec.name]
except KeyError:
pass
else:
_verbose_message('import {!r} # {!r}', spec.name, spec.loader)
finally:
self._spec._initializing = False
class ModuleSpec: class ModuleSpec:
"""The specification for a module, used for loading. """The specification for a module, used for loading.
...@@ -614,30 +587,44 @@ def _exec(spec, module): ...@@ -614,30 +587,44 @@ def _exec(spec, module):
if sys.modules.get(name) is not module: if sys.modules.get(name) is not module:
msg = 'module {!r} not in sys.modules'.format(name) msg = 'module {!r} not in sys.modules'.format(name)
raise ImportError(msg, name=name) raise ImportError(msg, name=name)
if spec.loader is None: try:
if spec.submodule_search_locations is None: if spec.loader is None:
raise ImportError('missing loader', name=spec.name) if spec.submodule_search_locations is None:
# namespace package raise ImportError('missing loader', name=spec.name)
_init_module_attrs(spec, module, override=True) # Namespace package.
return module _init_module_attrs(spec, module, override=True)
_init_module_attrs(spec, module, override=True) else:
if not hasattr(spec.loader, 'exec_module'): _init_module_attrs(spec, module, override=True)
# (issue19713) Once BuiltinImporter and ExtensionFileLoader if not hasattr(spec.loader, 'exec_module'):
# have exec_module() implemented, we can add a deprecation # (issue19713) Once BuiltinImporter and ExtensionFileLoader
# warning here. # have exec_module() implemented, we can add a deprecation
spec.loader.load_module(name) # warning here.
else: spec.loader.load_module(name)
spec.loader.exec_module(module) else:
return sys.modules[name] spec.loader.exec_module(module)
finally:
# Update the order of insertion into sys.modules for module
# clean-up at shutdown.
module = sys.modules.pop(spec.name)
sys.modules[spec.name] = module
return module
def _load_backward_compatible(spec): def _load_backward_compatible(spec):
# (issue19713) Once BuiltinImporter and ExtensionFileLoader # (issue19713) Once BuiltinImporter and ExtensionFileLoader
# have exec_module() implemented, we can add a deprecation # have exec_module() implemented, we can add a deprecation
# warning here. # warning here.
spec.loader.load_module(spec.name) try:
spec.loader.load_module(spec.name)
except:
if spec.name in sys.modules:
module = sys.modules.pop(spec.name)
sys.modules[spec.name] = module
raise
# The module must be in sys.modules at this point! # The module must be in sys.modules at this point!
module = sys.modules[spec.name] # Move it to the end of sys.modules.
module = sys.modules.pop(spec.name)
sys.modules[spec.name] = module
if getattr(module, '__loader__', None) is None: if getattr(module, '__loader__', None) is None:
try: try:
module.__loader__ = spec.loader module.__loader__ = spec.loader
...@@ -663,23 +650,42 @@ def _load_backward_compatible(spec): ...@@ -663,23 +650,42 @@ def _load_backward_compatible(spec):
def _load_unlocked(spec): def _load_unlocked(spec):
# A helper for direct use by the import system. # A helper for direct use by the import system.
if spec.loader is not None: if spec.loader is not None:
# not a namespace package # Not a namespace package.
if not hasattr(spec.loader, 'exec_module'): if not hasattr(spec.loader, 'exec_module'):
return _load_backward_compatible(spec) return _load_backward_compatible(spec)
module = module_from_spec(spec) module = module_from_spec(spec)
with _installed_safely(module):
if spec.loader is None:
if spec.submodule_search_locations is None:
raise ImportError('missing loader', name=spec.name)
# A namespace package so do nothing.
else:
spec.loader.exec_module(module)
# We don't ensure that the import-related module attributes get # This must be done before putting the module in sys.modules
# set in the sys.modules replacement case. Such modules are on # (otherwise an optimization shortcut in import.c becomes
# their own. # wrong).
return sys.modules[spec.name] spec._initializing = True
try:
sys.modules[spec.name] = module
try:
if spec.loader is None:
if spec.submodule_search_locations is None:
raise ImportError('missing loader', name=spec.name)
# A namespace package so do nothing.
else:
spec.loader.exec_module(module)
except:
try:
del sys.modules[spec.name]
except KeyError:
pass
raise
# Move the module to the end of sys.modules.
# We don't ensure that the import-related module attributes get
# set in the sys.modules replacement case. Such modules are on
# their own.
module = sys.modules.pop(spec.name)
sys.modules[spec.name] = module
_verbose_message('import {!r} # {!r}', spec.name, spec.loader)
finally:
spec._initializing = False
return module
# A method used during testing of _load_unlocked() and by # A method used during testing of _load_unlocked() and by
# _load_module_shim(). # _load_module_shim().
......
Modules imported last are now cleared first at interpreter shutdown.
...@@ -543,9 +543,10 @@ PyImport_Cleanup(void) ...@@ -543,9 +543,10 @@ PyImport_Cleanup(void)
module last. Likewise, we don't delete sys until the very module last. Likewise, we don't delete sys until the very
end because it is implicitly referenced (e.g. by print). */ end because it is implicitly referenced (e.g. by print). */
if (weaklist != NULL) { if (weaklist != NULL) {
Py_ssize_t i, n; Py_ssize_t i;
n = PyList_GET_SIZE(weaklist); /* Since dict is ordered in CPython 3.6+, modules are saved in
for (i = 0; i < n; i++) { importing order. First clear modules imported later. */
for (i = PyList_GET_SIZE(weaklist) - 1; i >= 0; i--) {
PyObject *tup = PyList_GET_ITEM(weaklist, i); PyObject *tup = PyList_GET_ITEM(weaklist, i);
PyObject *name = PyTuple_GET_ITEM(tup, 0); PyObject *name = PyTuple_GET_ITEM(tup, 0);
PyObject *mod = PyWeakref_GET_OBJECT(PyTuple_GET_ITEM(tup, 1)); PyObject *mod = PyWeakref_GET_OBJECT(PyTuple_GET_ITEM(tup, 1));
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment