From 582162e42ba4c944fa96c15a99de29ea96ea9ca3 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Tue, 12 Oct 2010 23:06:22 +0000 Subject: Merged revisions 85392 via svnmerge from svn+ssh://pythondev@svn.python.org/python/branches/py3k ........ r85392 | benjamin.peterson | 2010-10-12 17:57:59 -0500 (Tue, 12 Oct 2010) | 1 line prefer clearing global objects to obscure module.__dict__ bugs #10068 ........ --- Doc/reference/datamodel.rst | 7 +++++++ Lib/test/test_module.py | 17 ++++++++++++++++- Misc/NEWS | 3 +++ Objects/moduleobject.c | 5 +---- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 3d1d26d..9643f2b 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -654,6 +654,13 @@ Modules Special read-only attribute: :attr:`__dict__` is the module's namespace as a dictionary object. + .. impl-detail:: + + Because of the way CPython clears module dictionaries, the module + dictionary will be cleared when the module falls out of scope even if the + dictionary still has live references. To avoid this, copy the dictionary + or keep the module around while using its dictionary directly. + .. index:: single: __name__ (module attribute) single: __doc__ (module attribute) diff --git a/Lib/test/test_module.py b/Lib/test/test_module.py index 21ddc9a..7734fb0 100644 --- a/Lib/test/test_module.py +++ b/Lib/test/test_module.py @@ -1,6 +1,6 @@ # Test the module type import unittest -from test.support import run_unittest +from test.support import run_unittest, gc_collect import sys ModuleType = type(sys) @@ -55,14 +55,29 @@ class ModuleTests(unittest.TestCase): {"__name__": "foo", "__doc__": "foodoc", "bar": 42}) self.assertTrue(foo.__dict__ is d) + @unittest.expectedFailure def test_dont_clear_dict(self): # See issue 7140. def f(): foo = ModuleType("foo") foo.bar = 4 return foo + gc_collect() self.assertEqual(f().__dict__["bar"], 4) + def test_clear_dict_in_ref_cycle(self): + destroyed = [] + m = ModuleType("foo") + m.destroyed = destroyed + s = """class A: + def __del__(self): + destroyed.append(1) +a = A()""" + exec(s, m.__dict__) + del m + gc_collect() + self.assertEqual(destroyed, [1]) + def test_main(): run_unittest(ModuleTests) diff --git a/Misc/NEWS b/Misc/NEWS index 4f45059..f4ba9db 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -46,6 +46,9 @@ Core and Builtins - Issue #83755: Implicit set-to-frozenset conversion was not thread-safe. +- Issue #10068: Global objects which have reference cycles with their module's + dict are now cleared again. This causes issue #7140 to appear again. + - Issue #9416: Fix some issues with complex formatting where the output with no type specifier failed to match the str output: diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index def3781..5dd9c01 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -312,10 +312,7 @@ module_dealloc(PyModuleObject *m) if (m->md_def && m->md_def->m_free) m->md_def->m_free(m); if (m->md_dict != NULL) { - /* If we are the only ones holding a reference, we can clear - the dictionary. */ - if (Py_REFCNT(m->md_dict) == 1) - _PyModule_Clear((PyObject *)m); + _PyModule_Clear((PyObject *)m); Py_DECREF(m->md_dict); } if (m->md_state != NULL) -- cgit v0.12