Skip to content

Commit d0d2965

Browse files
bpo-44050: Extension modules can share state when they don't support sub-interpreters. (GH-27794) (GH-28738)
Automerge-Triggered-By: GH:encukou (cherry picked from commit b9bb748) Co-authored-by: Hai Shi <shihai1992@gmail.com>
1 parent d0d0909 commit d0d2965

File tree

4 files changed

+59
-1
lines changed

4 files changed

+59
-1
lines changed

Lib/test/test_capi.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,37 @@ def test_mutate_exception(self):
762762

763763
self.assertFalse(hasattr(binascii.Error, "foobar"))
764764

765+
def test_module_state_shared_in_global(self):
766+
"""
767+
bpo-44050: Extension module state should be shared between interpreters
768+
when it doesn't support sub-interpreters.
769+
"""
770+
r, w = os.pipe()
771+
self.addCleanup(os.close, r)
772+
self.addCleanup(os.close, w)
773+
774+
script = textwrap.dedent(f"""
775+
import importlib.machinery
776+
import importlib.util
777+
import os
778+
779+
fullname = '_test_module_state_shared'
780+
origin = importlib.util.find_spec('_testmultiphase').origin
781+
loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
782+
spec = importlib.util.spec_from_loader(fullname, loader)
783+
module = importlib.util.module_from_spec(spec)
784+
attr_id = str(id(module.Error)).encode()
785+
786+
os.write({w}, attr_id)
787+
""")
788+
exec(script)
789+
main_attr_id = os.read(r, 100)
790+
791+
ret = support.run_in_subinterp(script)
792+
self.assertEqual(ret, 0)
793+
subinterp_attr_id = os.read(r, 100)
794+
self.assertEqual(main_attr_id, subinterp_attr_id)
795+
765796

766797
class TestThreadState(unittest.TestCase):
767798

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Extensions that indicate they use global state (by setting ``m_size`` to -1)
2+
can again be used in multiple interpreters. This reverts to behavior of
3+
Python 3.8.

Modules/_testmultiphase.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,28 @@ PyInit__testmultiphase_meth_state_access(PyObject *spec)
844844
return PyModuleDef_Init(&def_meth_state_access);
845845
}
846846

847+
static PyModuleDef def_module_state_shared = {
848+
PyModuleDef_HEAD_INIT,
849+
.m_name = "_test_module_state_shared",
850+
.m_doc = PyDoc_STR("Regression Test module for single-phase init."),
851+
.m_size = -1,
852+
};
853+
854+
PyMODINIT_FUNC
855+
PyInit__test_module_state_shared(PyObject *spec)
856+
{
857+
PyObject *module = PyModule_Create(&def_module_state_shared);
858+
if (module == NULL) {
859+
return NULL;
860+
}
861+
862+
if (PyModule_AddObjectRef(module, "Error", PyExc_Exception) < 0) {
863+
Py_DECREF(module);
864+
return NULL;
865+
}
866+
return module;
867+
}
868+
847869

848870
/*** Helper for imp test ***/
849871

Python/import.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,9 @@ _PyImport_FixupExtensionObject(PyObject *mod, PyObject *name,
441441
return -1;
442442
}
443443

444-
if (_Py_IsMainInterpreter(tstate->interp)) {
444+
// bpo-44050: Extensions and def->m_base.m_copy can be updated
445+
// when the extension module doesn't support sub-interpreters.
446+
if (_Py_IsMainInterpreter(tstate->interp) || def->m_size == -1) {
445447
if (def->m_size == -1) {
446448
if (def->m_base.m_copy) {
447449
/* Somebody already imported the module,

0 commit comments

Comments
 (0)