diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -810,7 +810,7 @@ class SizeofTest(unittest.TestCase): # memoryview check(memoryview(b''), size('Pnin 2P2n2i5P 3cPn')) # module - check(unittest, size('PnP')) + check(unittest, size('PnPP')) # None check(None, size('')) # NotImplementedType diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -11,6 +11,7 @@ typedef struct { PyObject *md_dict; struct PyModuleDef *md_def; void *md_state; + PyObject *md_weaklist; } PyModuleObject; static PyMemberDef module_members[] = { @@ -56,6 +57,7 @@ PyModule_NewObject(PyObject *name) return NULL; m->md_def = NULL; m->md_state = NULL; + m->md_weaklist = NULL; m->md_dict = PyDict_New(); if (module_init_dict(m->md_dict, name, NULL) != 0) goto fail; @@ -371,12 +373,11 @@ static void module_dealloc(PyModuleObject *m) { PyObject_GC_UnTrack(m); + if (m->md_weaklist != NULL) + PyObject_ClearWeakRefs((PyObject *) m); if (m->md_def && m->md_def->m_free) m->md_def->m_free(m); - if (m->md_dict != NULL) { - _PyModule_Clear((PyObject *)m); - Py_DECREF(m->md_dict); - } + Py_XDECREF(m->md_dict); if (m->md_state != NULL) PyMem_FREE(m->md_state); Py_TYPE(m)->tp_free((PyObject *)m); @@ -522,7 +523,7 @@ PyTypeObject PyModule_Type = { (traverseproc)module_traverse, /* tp_traverse */ (inquiry)module_clear, /* tp_clear */ 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ + offsetof(PyModuleObject, md_weaklist), /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ module_methods, /* tp_methods */ diff --git a/Python/import.c b/Python/import.c --- a/Python/import.c +++ b/Python/import.c @@ -323,6 +323,7 @@ PyImport_Cleanup(void) PyObject *key, *value, *dict; PyInterpreterState *interp = PyThreadState_GET()->interp; PyObject *modules = interp->modules; + PyObject *weaklist = NULL; if (modules == NULL) return; /* Already done */ @@ -360,12 +361,33 @@ PyImport_Cleanup(void) } } + /* We prepare a list which will receive (name, weakref) tuples of + modules when they are removed from sys.modules. The name is used + for diagnosis messages (in verbose mode), while the weakref helps + detect those modules which have been held alive. */ + weaklist = PyList_New(0); + +#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(); \ + } + /* First, delete __main__ */ value = PyDict_GetItemString(modules, "__main__"); if (value != NULL && PyModule_Check(value)) { if (Py_VerboseFlag) - PySys_WriteStderr("# cleanup __main__\n"); - _PyModule_Clear(value); + PySys_WriteStderr("# removing __main__\n"); + STORE_MODULE_WEAKREF(value); PyDict_SetItemString(modules, "__main__", Py_None); } @@ -396,8 +418,8 @@ PyImport_Cleanup(void) continue; if (Py_VerboseFlag) PySys_FormatStderr( - "# cleanup[1] %U\n", key); - _PyModule_Clear(value); + "# cleanup[1] removing %U\n", key); + STORE_MODULE_WEAKREF(value); PyDict_SetItem(modules, key, Py_None); ndone++; } @@ -411,8 +433,8 @@ PyImport_Cleanup(void) if (is_essential_module(key)) continue; if (Py_VerboseFlag) - PySys_FormatStderr("# cleanup[2] %U\n", key); - _PyModule_Clear(value); + PySys_FormatStderr("# cleanup[2] removing %U\n", key); + STORE_MODULE_WEAKREF(value); PyDict_SetItem(modules, key, Py_None); } } @@ -430,17 +452,40 @@ PyImport_Cleanup(void) while (PyDict_Next(modules, &pos, &key, &value)) { if (PyUnicode_Check(key) && PyModule_Check(value)) { if (Py_VerboseFlag) - PySys_FormatStderr("# cleanup[3] %U\n", key); - _PyModule_Clear(value); + PySys_FormatStderr("# cleanup[3] removing %U\n", key); + STORE_MODULE_WEAKREF(value); PyDict_SetItem(modules, key, Py_None); } } /* Finally, clear and delete the modules directory */ PyDict_Clear(modules); - _PyGC_CollectNoFail(); interp->modules = NULL; Py_DECREF(modules); + + /* If there are any modules left alive, clear their globals to minimize + potential leaks. */ + 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) + PySys_FormatStderr("# cleanup[4] wiping %U\n", + PyTuple_GET_ITEM(tup, 0)); + _PyModule_Clear(mod); + Py_DECREF(mod); + } + Py_DECREF(weaklist); + } + _PyGC_CollectNoFail(); + +#undef STORE_MODULE_WEAKREF }