Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dcedaf6

Browse files
committed
Issue #18214: Improve finalization of Python modules to avoid setting their globals to None, in most cases.
1 parent c27cd71 commit dcedaf6

9 files changed

Lines changed: 180 additions & 111 deletions

File tree

Lib/rlcompleter.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
3030
"""
3131

32+
import atexit
3233
import builtins
3334
import __main__
3435

@@ -158,3 +159,8 @@ def get_class_members(klass):
158159
pass
159160
else:
160161
readline.set_completer(Completer().complete)
162+
# Release references early at shutdown (the readline module's
163+
# contents are quasi-immortal, and the completer function holds a
164+
# reference to globals).
165+
atexit.register(lambda: readline.set_completer(None))
166+

Lib/site.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
ImportError exception, it is silently ignored.
6969
"""
7070

71+
import atexit
7172
import sys
7273
import os
7374
import re
@@ -86,6 +87,25 @@
8687
USER_BASE = None
8788

8889

90+
_no_builtin = object()
91+
92+
def _patch_builtins(**items):
93+
# When patching builtins, we make some objects almost immortal
94+
# (builtins are only reclaimed at the very end of the interpreter
95+
# shutdown sequence). To avoid keeping to many references alive,
96+
# we register callbacks to undo our builtins additions.
97+
old_items = {k: getattr(builtins, k, _no_builtin) for k in items}
98+
def unpatch(old_items=old_items):
99+
for k, v in old_items.items():
100+
if v is _no_builtin:
101+
delattr(builtins, k)
102+
else:
103+
setattr(builtins, k, v)
104+
for k, v in items.items():
105+
setattr(builtins, k, v)
106+
atexit.register(unpatch)
107+
108+
89109
def makepath(*paths):
90110
dir = os.path.join(*paths)
91111
try:
@@ -357,8 +377,7 @@ def __call__(self, code=None):
357377
except:
358378
pass
359379
raise SystemExit(code)
360-
builtins.quit = Quitter('quit')
361-
builtins.exit = Quitter('exit')
380+
_patch_builtins(quit=Quitter('quit'), exit=Quitter('exit'))
362381

363382

364383
class _Printer(object):
@@ -423,20 +442,20 @@ def __call__(self):
423442

424443
def setcopyright():
425444
"""Set 'copyright' and 'credits' in builtins"""
426-
builtins.copyright = _Printer("copyright", sys.copyright)
445+
_patch_builtins(copyright=_Printer("copyright", sys.copyright))
427446
if sys.platform[:4] == 'java':
428-
builtins.credits = _Printer(
447+
_patch_builtins(credits=_Printer(
429448
"credits",
430-
"Jython is maintained by the Jython developers (www.jython.org).")
449+
"Jython is maintained by the Jython developers (www.jython.org)."))
431450
else:
432-
builtins.credits = _Printer("credits", """\
451+
_patch_builtins(credits=_Printer("credits", """\
433452
Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
434-
for supporting Python development. See www.python.org for more information.""")
453+
for supporting Python development. See www.python.org for more information."""))
435454
here = os.path.dirname(os.__file__)
436-
builtins.license = _Printer(
455+
_patch_builtins(license=_Printer(
437456
"license", "See http://www.python.org/%.3s/license.html" % sys.version,
438457
["LICENSE.txt", "LICENSE"],
439-
[os.path.join(here, os.pardir), here, os.curdir])
458+
[os.path.join(here, os.pardir), here, os.curdir]))
440459

441460

442461
class _Helper(object):
@@ -453,7 +472,7 @@ def __call__(self, *args, **kwds):
453472
return pydoc.help(*args, **kwds)
454473

455474
def sethelper():
456-
builtins.help = _Helper()
475+
_patch_builtins(help=_Helper())
457476

458477
def enablerlcompleter():
459478
"""Enable default readline configuration on interactive prompts, by

Lib/test/final_a.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""
2+
Fodder for module finalization tests in test_module.
3+
"""
4+
5+
import shutil
6+
import test.final_b
7+
8+
x = 'a'
9+
10+
class C:
11+
def __del__(self):
12+
# Inspect module globals and builtins
13+
print("x =", x)
14+
print("final_b.x =", test.final_b.x)
15+
print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None))
16+
print("len =", getattr(len, '__name__', None))
17+
18+
c = C()
19+
_underscored = C()

Lib/test/final_b.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""
2+
Fodder for module finalization tests in test_module.
3+
"""
4+
5+
import shutil
6+
import test.final_a
7+
8+
x = 'b'
9+
10+
class C:
11+
def __del__(self):
12+
# Inspect module globals and builtins
13+
print("x =", x)
14+
print("final_a.x =", test.final_a.x)
15+
print("shutil.rmtree =", getattr(shutil.rmtree, '__name__', None))
16+
print("len =", getattr(len, '__name__', None))
17+
18+
c = C()
19+
_underscored = C()

Lib/test/test_module.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Test the module type
22
import unittest
33
from test.support import run_unittest, gc_collect
4+
from test.script_helper import assert_python_ok
45

56
import sys
67
ModuleType = type(sys)
@@ -70,7 +71,6 @@ def test_reinit(self):
7071
"__loader__": None, "__package__": None})
7172
self.assertTrue(foo.__dict__ is d)
7273

73-
@unittest.expectedFailure
7474
def test_dont_clear_dict(self):
7575
# See issue 7140.
7676
def f():
@@ -181,6 +181,19 @@ def test_module_repr_source(self):
181181
self.assertEqual(r[:25], "<module 'unittest' from '")
182182
self.assertEqual(r[-13:], "__init__.py'>")
183183

184+
def test_module_finalization_at_shutdown(self):
185+
# Module globals and builtins should still be available during shutdown
186+
rc, out, err = assert_python_ok("-c", "from test import final_a")
187+
self.assertFalse(err)
188+
lines = out.splitlines()
189+
self.assertEqual(set(lines), {
190+
b"x = a",
191+
b"x = b",
192+
b"final_a.x = a",
193+
b"final_b.x = b",
194+
b"len = len",
195+
b"shutil.rmtree = rmtree"})
196+
184197
# frozen and namespace module reprs are tested in importlib.
185198

186199

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ def get_gen(): yield 1
810810
# memoryview
811811
check(memoryview(b''), size('Pnin 2P2n2i5P 3cPn'))
812812
# module
813-
check(unittest, size('PnP'))
813+
check(unittest, size('PnPPP'))
814814
# None
815815
check(None, size(''))
816816
# NotImplementedType

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #18214: Improve finalization of Python modules to avoid setting
14+
their globals to None, in most cases.
15+
1316
- Issue #18112: PEP 442 implementation (safe object finalization).
1417

1518
- Issue #18552: Check return value of PyArena_AddPyObject() in

Objects/moduleobject.c

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ typedef struct {
1111
PyObject *md_dict;
1212
struct PyModuleDef *md_def;
1313
void *md_state;
14+
PyObject *md_weaklist;
15+
PyObject *md_name; /* for logging purposes after md_dict is cleared */
1416
} PyModuleObject;
1517

1618
static PyMemberDef module_members[] = {
@@ -27,7 +29,8 @@ static PyTypeObject moduledef_type = {
2729

2830

2931
static int
30-
module_init_dict(PyObject *md_dict, PyObject *name, PyObject *doc)
32+
module_init_dict(PyModuleObject *mod, PyObject *md_dict,
33+
PyObject *name, PyObject *doc)
3134
{
3235
if (md_dict == NULL)
3336
return -1;
@@ -42,6 +45,11 @@ module_init_dict(PyObject *md_dict, PyObject *name, PyObject *doc)
4245
return -1;
4346
if (PyDict_SetItemString(md_dict, "__loader__", Py_None) != 0)
4447
return -1;
48+
if (PyUnicode_CheckExact(name)) {
49+
Py_INCREF(name);
50+
Py_XDECREF(mod->md_name);
51+
mod->md_name = name;
52+
}
4553

4654
return 0;
4755
}
@@ -56,8 +64,10 @@ PyModule_NewObject(PyObject *name)
5664
return NULL;
5765
m->md_def = NULL;
5866
m->md_state = NULL;
67+
m->md_weaklist = NULL;
68+
m->md_name = NULL;
5969
m->md_dict = PyDict_New();
60-
if (module_init_dict(m->md_dict, name, NULL) != 0)
70+
if (module_init_dict(m, m->md_dict, name, NULL) != 0)
6171
goto fail;
6272
PyObject_GC_Track(m);
6373
return (PyObject *)m;
@@ -362,7 +372,7 @@ module_init(PyModuleObject *m, PyObject *args, PyObject *kwds)
362372
return -1;
363373
m->md_dict = dict;
364374
}
365-
if (module_init_dict(dict, name, doc) < 0)
375+
if (module_init_dict(m, dict, name, doc) < 0)
366376
return -1;
367377
return 0;
368378
}
@@ -371,12 +381,15 @@ static void
371381
module_dealloc(PyModuleObject *m)
372382
{
373383
PyObject_GC_UnTrack(m);
384+
if (Py_VerboseFlag && m->md_name) {
385+
PySys_FormatStderr("# destroy %S\n", m->md_name);
386+
}
387+
if (m->md_weaklist != NULL)
388+
PyObject_ClearWeakRefs((PyObject *) m);
374389
if (m->md_def && m->md_def->m_free)
375390
m->md_def->m_free(m);
376-
if (m->md_dict != NULL) {
377-
_PyModule_Clear((PyObject *)m);
378-
Py_DECREF(m->md_dict);
379-
}
391+
Py_XDECREF(m->md_dict);
392+
Py_XDECREF(m->md_name);
380393
if (m->md_state != NULL)
381394
PyMem_FREE(m->md_state);
382395
Py_TYPE(m)->tp_free((PyObject *)m);
@@ -522,7 +535,7 @@ PyTypeObject PyModule_Type = {
522535
(traverseproc)module_traverse, /* tp_traverse */
523536
(inquiry)module_clear, /* tp_clear */
524537
0, /* tp_richcompare */
525-
0, /* tp_weaklistoffset */
538+
offsetof(PyModuleObject, md_weaklist), /* tp_weaklistoffset */
526539
0, /* tp_iter */
527540
0, /* tp_iternext */
528541
module_methods, /* tp_methods */

0 commit comments

Comments
 (0)