Issue #18214: Improve finalization of Python modules to avoid setting their globals to None, in most cases.

This commit is contained in:
Antoine Pitrou 2013-07-31 23:14:08 +02:00
parent c27cd71cd7
commit dcedaf6e53
9 changed files with 178 additions and 109 deletions

View file

@ -29,6 +29,7 @@ Notes:
""" """
import atexit
import builtins import builtins
import __main__ import __main__
@ -158,3 +159,8 @@ except ImportError:
pass pass
else: else:
readline.set_completer(Completer().complete) readline.set_completer(Completer().complete)
# Release references early at shutdown (the readline module's
# contents are quasi-immortal, and the completer function holds a
# reference to globals).
atexit.register(lambda: readline.set_completer(None))

View file

@ -68,6 +68,7 @@ site-specific customizations. If this import fails with an
ImportError exception, it is silently ignored. ImportError exception, it is silently ignored.
""" """
import atexit
import sys import sys
import os import os
import re import re
@ -86,6 +87,25 @@ USER_SITE = None
USER_BASE = None USER_BASE = None
_no_builtin = object()
def _patch_builtins(**items):
# When patching builtins, we make some objects almost immortal
# (builtins are only reclaimed at the very end of the interpreter
# shutdown sequence). To avoid keeping to many references alive,
# we register callbacks to undo our builtins additions.
old_items = {k: getattr(builtins, k, _no_builtin) for k in items}
def unpatch(old_items=old_items):
for k, v in old_items.items():
if v is _no_builtin:
delattr(builtins, k)
else:
setattr(builtins, k, v)
for k, v in items.items():
setattr(builtins, k, v)
atexit.register(unpatch)
def makepath(*paths): def makepath(*paths):
dir = os.path.join(*paths) dir = os.path.join(*paths)
try: try:
@ -357,8 +377,7 @@ def setquit():
except: except:
pass pass
raise SystemExit(code) raise SystemExit(code)
builtins.quit = Quitter('quit') _patch_builtins(quit=Quitter('quit'), exit=Quitter('exit'))
builtins.exit = Quitter('exit')
class _Printer(object): class _Printer(object):
@ -423,20 +442,20 @@ class _Printer(object):
def setcopyright(): def setcopyright():
"""Set 'copyright' and 'credits' in builtins""" """Set 'copyright' and 'credits' in builtins"""
builtins.copyright = _Printer("copyright", sys.copyright) _patch_builtins(copyright=_Printer("copyright", sys.copyright))
if sys.platform[:4] == 'java': if sys.platform[:4] == 'java':
builtins.credits = _Printer( _patch_builtins(credits=_Printer(
"credits", "credits",
"Jython is maintained by the Jython developers (www.jython.org).") "Jython is maintained by the Jython developers (www.jython.org)."))
else: else:
builtins.credits = _Printer("credits", """\ _patch_builtins(credits=_Printer("credits", """\
Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
for supporting Python development. See www.python.org for more information.""") for supporting Python development. See www.python.org for more information."""))
here = os.path.dirname(os.__file__) here = os.path.dirname(os.__file__)
builtins.license = _Printer( _patch_builtins(license=_Printer(
"license", "See http://www.python.org/%.3s/license.html" % sys.version, "license", "See http://www.python.org/%.3s/license.html" % sys.version,
["LICENSE.txt", "LICENSE"], ["LICENSE.txt", "LICENSE"],
[os.path.join(here, os.pardir), here, os.curdir]) [os.path.join(here, os.pardir), here, os.curdir]))
class _Helper(object): class _Helper(object):
@ -453,7 +472,7 @@ class _Helper(object):
return pydoc.help(*args, **kwds) return pydoc.help(*args, **kwds)
def sethelper(): def sethelper():
builtins.help = _Helper() _patch_builtins(help=_Helper())
def enablerlcompleter(): def enablerlcompleter():
"""Enable default readline configuration on interactive prompts, by """Enable default readline configuration on interactive prompts, by

19
Lib/test/final_a.py Normal file
View file

@ -0,0 +1,19 @@
"""
Fodder for module finalization tests in test_module.
"""
import shutil
import test.final_b
x = 'a'
class C:
def __del__(self):
# Inspect module globals and builtins
print("x =", x)
print("final_b.x =", test.final_b.x)
print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None))
print("len =", getattr(len, '__name__', None))
c = C()
_underscored = C()

19
Lib/test/final_b.py Normal file
View file

@ -0,0 +1,19 @@
"""
Fodder for module finalization tests in test_module.
"""
import shutil
import test.final_a
x = 'b'
class C:
def __del__(self):
# Inspect module globals and builtins
print("x =", x)
print("final_a.x =", test.final_a.x)
print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None))
print("len =", getattr(len, '__name__', None))
c = C()
_underscored = C()

View file

@ -1,6 +1,7 @@
# Test the module type # Test the module type
import unittest import unittest
from test.support import run_unittest, gc_collect from test.support import run_unittest, gc_collect
from test.script_helper import assert_python_ok
import sys import sys
ModuleType = type(sys) ModuleType = type(sys)
@ -70,7 +71,6 @@ class ModuleTests(unittest.TestCase):
"__loader__": None, "__package__": None}) "__loader__": None, "__package__": None})
self.assertTrue(foo.__dict__ is d) self.assertTrue(foo.__dict__ is d)
@unittest.expectedFailure
def test_dont_clear_dict(self): def test_dont_clear_dict(self):
# See issue 7140. # See issue 7140.
def f(): def f():
@ -181,6 +181,19 @@ a = A(destroyed)"""
self.assertEqual(r[:25], "<module 'unittest' from '") self.assertEqual(r[:25], "<module 'unittest' from '")
self.assertEqual(r[-13:], "__init__.py'>") self.assertEqual(r[-13:], "__init__.py'>")
def test_module_finalization_at_shutdown(self):
# Module globals and builtins should still be available during shutdown
rc, out, err = assert_python_ok("-c", "from test import final_a")
self.assertFalse(err)
lines = out.splitlines()
self.assertEqual(set(lines), {
b"x = a",
b"x = b",
b"final_a.x = a",
b"final_b.x = b",
b"len = len",
b"shutil.rmtree = rmtree"})
# frozen and namespace module reprs are tested in importlib. # frozen and namespace module reprs are tested in importlib.

View file

@ -810,7 +810,7 @@ class SizeofTest(unittest.TestCase):
# memoryview # memoryview
check(memoryview(b''), size('Pnin 2P2n2i5P 3cPn')) check(memoryview(b''), size('Pnin 2P2n2i5P 3cPn'))
# module # module
check(unittest, size('PnP')) check(unittest, size('PnPPP'))
# None # None
check(None, size('')) check(None, size(''))
# NotImplementedType # NotImplementedType

View file

@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #18214: Improve finalization of Python modules to avoid setting
their globals to None, in most cases.
- Issue #18112: PEP 442 implementation (safe object finalization). - Issue #18112: PEP 442 implementation (safe object finalization).
- Issue #18552: Check return value of PyArena_AddPyObject() in - Issue #18552: Check return value of PyArena_AddPyObject() in

View file

@ -11,6 +11,8 @@ typedef struct {
PyObject *md_dict; PyObject *md_dict;
struct PyModuleDef *md_def; struct PyModuleDef *md_def;
void *md_state; void *md_state;
PyObject *md_weaklist;
PyObject *md_name; /* for logging purposes after md_dict is cleared */
} PyModuleObject; } PyModuleObject;
static PyMemberDef module_members[] = { static PyMemberDef module_members[] = {
@ -27,7 +29,8 @@ static PyTypeObject moduledef_type = {
static int static int
module_init_dict(PyObject *md_dict, PyObject *name, PyObject *doc) module_init_dict(PyModuleObject *mod, PyObject *md_dict,
PyObject *name, PyObject *doc)
{ {
if (md_dict == NULL) if (md_dict == NULL)
return -1; return -1;
@ -42,6 +45,11 @@ module_init_dict(PyObject *md_dict, PyObject *name, PyObject *doc)
return -1; return -1;
if (PyDict_SetItemString(md_dict, "__loader__", Py_None) != 0) if (PyDict_SetItemString(md_dict, "__loader__", Py_None) != 0)
return -1; return -1;
if (PyUnicode_CheckExact(name)) {
Py_INCREF(name);
Py_XDECREF(mod->md_name);
mod->md_name = name;
}
return 0; return 0;
} }
@ -56,8 +64,10 @@ PyModule_NewObject(PyObject *name)
return NULL; return NULL;
m->md_def = NULL; m->md_def = NULL;
m->md_state = NULL; m->md_state = NULL;
m->md_weaklist = NULL;
m->md_name = NULL;
m->md_dict = PyDict_New(); m->md_dict = PyDict_New();
if (module_init_dict(m->md_dict, name, NULL) != 0) if (module_init_dict(m, m->md_dict, name, NULL) != 0)
goto fail; goto fail;
PyObject_GC_Track(m); PyObject_GC_Track(m);
return (PyObject *)m; return (PyObject *)m;
@ -362,7 +372,7 @@ module_init(PyModuleObject *m, PyObject *args, PyObject *kwds)
return -1; return -1;
m->md_dict = dict; m->md_dict = dict;
} }
if (module_init_dict(dict, name, doc) < 0) if (module_init_dict(m, dict, name, doc) < 0)
return -1; return -1;
return 0; return 0;
} }
@ -371,12 +381,15 @@ static void
module_dealloc(PyModuleObject *m) module_dealloc(PyModuleObject *m)
{ {
PyObject_GC_UnTrack(m); PyObject_GC_UnTrack(m);
if (Py_VerboseFlag && m->md_name) {
PySys_FormatStderr("# destroy %S\n", m->md_name);
}
if (m->md_weaklist != NULL)
PyObject_ClearWeakRefs((PyObject *) m);
if (m->md_def && m->md_def->m_free) if (m->md_def && m->md_def->m_free)
m->md_def->m_free(m); m->md_def->m_free(m);
if (m->md_dict != NULL) { Py_XDECREF(m->md_dict);
_PyModule_Clear((PyObject *)m); Py_XDECREF(m->md_name);
Py_DECREF(m->md_dict);
}
if (m->md_state != NULL) if (m->md_state != NULL)
PyMem_FREE(m->md_state); PyMem_FREE(m->md_state);
Py_TYPE(m)->tp_free((PyObject *)m); Py_TYPE(m)->tp_free((PyObject *)m);
@ -522,7 +535,7 @@ PyTypeObject PyModule_Type = {
(traverseproc)module_traverse, /* tp_traverse */ (traverseproc)module_traverse, /* tp_traverse */
(inquiry)module_clear, /* tp_clear */ (inquiry)module_clear, /* tp_clear */
0, /* tp_richcompare */ 0, /* tp_richcompare */
0, /* tp_weaklistoffset */ offsetof(PyModuleObject, md_weaklist), /* tp_weaklistoffset */
0, /* tp_iter */ 0, /* tp_iter */
0, /* tp_iternext */ 0, /* tp_iternext */
module_methods, /* tp_methods */ module_methods, /* tp_methods */

View file

@ -277,6 +277,7 @@ static char* sys_deletes[] = {
"path", "argv", "ps1", "ps2", "path", "argv", "ps1", "ps2",
"last_type", "last_value", "last_traceback", "last_type", "last_value", "last_traceback",
"path_hooks", "path_importer_cache", "meta_path", "path_hooks", "path_importer_cache", "meta_path",
"__interactivehook__",
/* misc stuff */ /* misc stuff */
"flags", "float_info", "flags", "float_info",
NULL NULL
@ -289,40 +290,17 @@ static char* sys_files[] = {
NULL NULL
}; };
static int
is_essential_module(PyObject *name)
{
Py_ssize_t name_len;
char *name_str = PyUnicode_AsUTF8AndSize(name, &name_len);
if (name_str == NULL) {
PyErr_Clear();
return 0;
}
if (strcmp(name_str, "builtins") == 0)
return 1;
if (strcmp(name_str, "sys") == 0)
return 1;
/* These are all needed for stderr to still function */
if (strcmp(name_str, "codecs") == 0)
return 1;
if (strcmp(name_str, "_codecs") == 0)
return 1;
if (strncmp(name_str, "encodings.", 10) == 0)
return 1;
return 0;
}
/* Un-initialize things, as good as we can */ /* Un-initialize things, as good as we can */
void void
PyImport_Cleanup(void) PyImport_Cleanup(void)
{ {
Py_ssize_t pos, ndone; Py_ssize_t pos;
PyObject *key, *value, *dict; PyObject *key, *value, *dict;
PyInterpreterState *interp = PyThreadState_GET()->interp; PyInterpreterState *interp = PyThreadState_GET()->interp;
PyObject *modules = interp->modules; PyObject *modules = interp->modules;
PyObject *builtins = interp->builtins;
PyObject *weaklist = NULL;
if (modules == NULL) if (modules == NULL)
return; /* Already done */ return; /* Already done */
@ -333,6 +311,8 @@ PyImport_Cleanup(void)
deleted *last* of all, they would come too late in the normal deleted *last* of all, they would come too late in the normal
destruction order. Sigh. */ destruction order. Sigh. */
/* XXX Perhaps these precautions are obsolete. Who knows? */
value = PyDict_GetItemString(modules, "builtins"); value = PyDict_GetItemString(modules, "builtins");
if (value != NULL && PyModule_Check(value)) { if (value != NULL && PyModule_Check(value)) {
dict = PyModule_GetDict(value); dict = PyModule_GetDict(value);
@ -360,87 +340,84 @@ PyImport_Cleanup(void)
} }
} }
/* First, delete __main__ */ /* We prepare a list which will receive (name, weakref) tuples of
value = PyDict_GetItemString(modules, "__main__"); modules when they are removed from sys.modules. The name is used
if (value != NULL && PyModule_Check(value)) { for diagnosis messages (in verbose mode), while the weakref helps
if (Py_VerboseFlag) detect those modules which have been held alive. */
PySys_WriteStderr("# cleanup __main__\n"); weaklist = PyList_New(0);
_PyModule_Clear(value);
PyDict_SetItemString(modules, "__main__", Py_None); #define STORE_MODULE_WEAKREF(mod) \
if (weaklist != NULL) { \
PyObject *name = PyModule_GetNameObject(mod); \
PyObject *wr = PyWeakref_NewRef(mod, NULL); \
if (name && wr) { \
PyObject *tup = PyTuple_Pack(2, name, wr); \
PyList_Append(weaklist, tup); \
Py_XDECREF(tup); \
} \
Py_XDECREF(name); \
Py_XDECREF(wr); \
if (PyErr_Occurred()) \
PyErr_Clear(); \
} }
/* The special treatment of "builtins" here is because even /* Remove all modules from sys.modules, hoping that garbage collection
when it's not referenced as a module, its dictionary is can reclaim most of them. */
referenced by almost every module's __builtins__. Since
deleting a module clears its dictionary (even if there are
references left to it), we need to delete the "builtins"
module last. Likewise, we don't delete sys until the very
end because it is implicitly referenced (e.g. by print).
Also note that we 'delete' modules by replacing their entry
in the modules dict with None, rather than really deleting
them; this avoids a rehash of the modules dictionary and
also marks them as "non existent" so they won't be
re-imported. */
/* Next, repeatedly delete modules with a reference count of
one (skipping builtins and sys) and delete them */
do {
ndone = 0;
pos = 0;
while (PyDict_Next(modules, &pos, &key, &value)) {
if (value->ob_refcnt != 1)
continue;
if (PyUnicode_Check(key) && PyModule_Check(value)) {
if (is_essential_module(key))
continue;
if (Py_VerboseFlag)
PySys_FormatStderr(
"# cleanup[1] %U\n", key);
_PyModule_Clear(value);
PyDict_SetItem(modules, key, Py_None);
ndone++;
}
}
} while (ndone > 0);
/* Next, delete all modules (still skipping builtins and sys) */
pos = 0; pos = 0;
while (PyDict_Next(modules, &pos, &key, &value)) { while (PyDict_Next(modules, &pos, &key, &value)) {
if (PyUnicode_Check(key) && PyModule_Check(value)) { if (PyModule_Check(value)) {
if (is_essential_module(key)) if (Py_VerboseFlag && PyUnicode_Check(key))
continue; PySys_FormatStderr("# cleanup[2] removing %U\n", key, value);
if (Py_VerboseFlag) STORE_MODULE_WEAKREF(value);
PySys_FormatStderr("# cleanup[2] %U\n", key);
_PyModule_Clear(value);
PyDict_SetItem(modules, key, Py_None); PyDict_SetItem(modules, key, Py_None);
} }
} }
/* Collect garbage remaining after deleting the modules. Mostly /* Clear the modules dict. */
reference cycles created by classes. */ PyDict_Clear(modules);
PyGC_Collect(); /* Replace the interpreter's reference to builtins with an empty dict
(module globals still have a reference to the original builtins). */
builtins = interp->builtins;
interp->builtins = PyDict_New();
Py_DECREF(builtins);
/* Collect references */
_PyGC_CollectNoFail();
/* Dump GC stats before it's too late, since it uses the warnings /* Dump GC stats before it's too late, since it uses the warnings
machinery. */ machinery. */
_PyGC_DumpShutdownStats(); _PyGC_DumpShutdownStats();
/* Next, delete all remaining modules */ /* Now, if there are any modules left alive, clear their globals to
pos = 0; minimize potential leaks. All C extension modules actually end
while (PyDict_Next(modules, &pos, &key, &value)) { up here, since they are kept alive in the interpreter state. */
if (PyUnicode_Check(key) && PyModule_Check(value)) { if (weaklist != NULL) {
Py_ssize_t i, n;
n = PyList_GET_SIZE(weaklist);
for (i = 0; i < n; i++) {
PyObject *tup = PyList_GET_ITEM(weaklist, i);
PyObject *mod = PyWeakref_GET_OBJECT(PyTuple_GET_ITEM(tup, 1));
if (mod == Py_None)
continue;
Py_INCREF(mod);
assert(PyModule_Check(mod));
if (Py_VerboseFlag) if (Py_VerboseFlag)
PySys_FormatStderr("# cleanup[3] %U\n", key); PySys_FormatStderr("# cleanup[3] wiping %U\n",
_PyModule_Clear(value); PyTuple_GET_ITEM(tup, 0), mod);
PyDict_SetItem(modules, key, Py_None); _PyModule_Clear(mod);
Py_DECREF(mod);
} }
Py_DECREF(weaklist);
} }
/* Finally, clear and delete the modules directory */ /* Clear and delete the modules directory. Actual modules will
PyDict_Clear(modules); still be there only if imported during the execution of some
_PyGC_CollectNoFail(); destructor. */
interp->modules = NULL; interp->modules = NULL;
Py_DECREF(modules); Py_DECREF(modules);
/* Once more */
_PyGC_CollectNoFail();
#undef STORE_MODULE_WEAKREF
} }