mirror of https://github.com/python/cpython
bpo-38076 Clear the interpreter state only after clearing module globals (GH-18039)
Currently, during runtime destruction, `_PyImport_Cleanup` is clearing the interpreter state before clearing out the modules themselves. This leads to a segfault on modules that rely on the module state to clear themselves up. For example, let's take the small snippet added in the issue by @DinoV : ``` import _struct class C: def __init__(self): self.pack = _struct.pack def __del__(self): self.pack('I', -42) _struct.x = C() ``` The module `_struct` uses the module state to run `pack`. Therefore, the module state has to be alive until after the module has been cleared out to successfully run `C.__del__`. This happens at line 606, when `_PyImport_Cleanup` calls `_PyModule_Clear`. In fact, the loop that calls `_PyModule_Clear` has in its comments: > Now, if there are any modules left alive, clear their globals to minimize potential leaks. All C extension modules actually end up here, since they are kept alive in the interpreter state. That means that we can't clear the module state (which is used by C Extensions) before we run that loop. Moving `_PyInterpreterState_ClearModules` until after it, fixes the segfault in the code snippet. Finally, this updates a test in `io` to correctly assert the error that it now throws (since it now finds the io module state). The test that uses this is: `test_create_at_shutdown_without_encoding`. Given this test is now working is a proof that the module state now stays alive even when `__del__` is called at module destruction time. Thus, I didn't add a new tests for this. https://bugs.python.org/issue38076
This commit is contained in:
parent
b6999e5690
commit
4590f72259
|
@ -3683,7 +3683,7 @@ def _to_memoryview(buf):
|
||||||
|
|
||||||
class CTextIOWrapperTest(TextIOWrapperTest):
|
class CTextIOWrapperTest(TextIOWrapperTest):
|
||||||
io = io
|
io = io
|
||||||
shutdown_error = "RuntimeError: could not find io module state"
|
shutdown_error = "LookupError: unknown encoding: ascii"
|
||||||
|
|
||||||
def test_initialization(self):
|
def test_initialization(self):
|
||||||
r = self.BytesIO(b"\xc3\xa9\n\n")
|
r = self.BytesIO(b"\xc3\xa9\n\n")
|
||||||
|
|
|
@ -7,6 +7,7 @@ import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from test import support
|
from test import support
|
||||||
|
from test.support.script_helper import assert_python_ok
|
||||||
|
|
||||||
ISBIGENDIAN = sys.byteorder == "big"
|
ISBIGENDIAN = sys.byteorder == "big"
|
||||||
|
|
||||||
|
@ -652,6 +653,23 @@ class StructTest(unittest.TestCase):
|
||||||
s2 = struct.Struct(s.format.encode())
|
s2 = struct.Struct(s.format.encode())
|
||||||
self.assertEqual(s2.format, s.format)
|
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):
|
class UnpackIteratorTest(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -855,6 +855,23 @@ class SysModuleTest(unittest.TestCase):
|
||||||
self.assertIn(b'sys.flags', out[0])
|
self.assertIn(b'sys.flags', out[0])
|
||||||
self.assertIn(b'sys.float_info', out[1])
|
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'),
|
@unittest.skipUnless(hasattr(sys, 'getandroidapilevel'),
|
||||||
'need sys.getandroidapilevel()')
|
'need sys.getandroidapilevel()')
|
||||||
def test_getandroidapilevel(self):
|
def test_getandroidapilevel(self):
|
||||||
|
|
|
@ -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
|
|
@ -568,8 +568,6 @@ _PyImport_Cleanup(PyThreadState *tstate)
|
||||||
_PyErr_Clear(tstate);
|
_PyErr_Clear(tstate);
|
||||||
}
|
}
|
||||||
Py_XDECREF(dict);
|
Py_XDECREF(dict);
|
||||||
/* Clear module dict copies stored in the interpreter state */
|
|
||||||
_PyInterpreterState_ClearModules(interp);
|
|
||||||
/* Collect references */
|
/* Collect references */
|
||||||
_PyGC_CollectNoFail();
|
_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
|
||||||
|
@ -621,6 +619,9 @@ _PyImport_Cleanup(PyThreadState *tstate)
|
||||||
}
|
}
|
||||||
_PyModule_ClearDict(interp->builtins);
|
_PyModule_ClearDict(interp->builtins);
|
||||||
|
|
||||||
|
/* Clear module dict copies stored in the interpreter state */
|
||||||
|
_PyInterpreterState_ClearModules(interp);
|
||||||
|
|
||||||
/* Clear and delete the modules directory. Actual modules will
|
/* Clear and delete the modules directory. Actual modules will
|
||||||
still be there only if imported during the execution of some
|
still be there only if imported during the execution of some
|
||||||
destructor. */
|
destructor. */
|
||||||
|
|
Loading…
Reference in New Issue