Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -3685,7 +3685,7 @@ def _to_memoryview(buf):

class CTextIOWrapperTest(TextIOWrapperTest):
io = io
shutdown_error = "RuntimeError: could not find io module state"
shutdown_error = "LookupError: unknown encoding: ascii"

def test_initialization(self):
r = self.BytesIO(b"\xc3\xa9\n\n")
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sys

from test import support
from test.support.script_helper import assert_python_ok

ISBIGENDIAN = sys.byteorder == "big"

Expand Down Expand Up @@ -652,6 +653,23 @@ def test_format_attr(self):
s2 = struct.Struct(s.format.encode())
self.assertEqual(s2.format, s.format)

def test_struct_cleans_up_at_runtime_shutdown(self):
code = """if 1:
import struct

class C:
def __init__(self):
self.pack = struct.pack
def __del__(self):
self.pack('I', -42)

struct.x = C()
"""
rc, stdout, stderr = assert_python_ok("-c", code)
self.assertEqual(rc, 0)
self.assertEqual(stdout.rstrip(), b"")
self.assertIn(b"Exception ignored in:", stderr)
self.assertIn(b"C.__del__", stderr)

class UnpackIteratorTest(unittest.TestCase):
"""
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,23 @@ def __del__(self, sys=sys):
self.assertIn(b'sys.flags', out[0])
self.assertIn(b'sys.float_info', out[1])

def test_sys_ignores_cleaning_up_user_data(self):
code = """if 1:
import struct, sys

class C:
def __init__(self):
self.pack = struct.pack
def __del__(self):
self.pack('I', -42)

sys.x = C()
"""
rc, stdout, stderr = assert_python_ok('-c', code)
self.assertEqual(rc, 0)
self.assertEqual(stdout.rstrip(), b"")
self.assertEqual(stderr.rstrip(), b"")

@unittest.skipUnless(hasattr(sys, 'getandroidapilevel'),
'need sys.getandroidapilevel()')
def test_getandroidapilevel(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix to clear the interpreter state only after clearing module globals to
guarantee module state access from C Extensions during runtime destruction
5 changes: 3 additions & 2 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -566,8 +566,6 @@ _PyImport_Cleanup(PyThreadState *tstate)
_PyErr_Clear(tstate);
}
Py_XDECREF(dict);
/* Clear module dict copies stored in the interpreter state */
_PyInterpreterState_ClearModules(interp);
/* Collect references */
_PyGC_CollectNoFail();
/* Dump GC stats before it's too late, since it uses the warnings
Expand Down Expand Up @@ -619,6 +617,9 @@ _PyImport_Cleanup(PyThreadState *tstate)
}
_PyModule_ClearDict(interp->builtins);

/* Clear module dict copies stored in the interpreter state */
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm tempted to change this comment to: "Clear module dict copies stored in the interpreter state only after all the modules have been cleared" or something along those lines.

_PyInterpreterState_ClearModules(interp);

/* Clear and delete the modules directory. Actual modules will
still be there only if imported during the execution of some
destructor. */
Expand Down