summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_import/__init__.py20
-rw-r--r--Lib/test/test_importlib/extension/test_loader.py2
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2023-05-05-13-18-56.gh-issue-99113.hT1ajK.rst11
-rw-r--r--Modules/_testmultiphase.c60
-rw-r--r--Objects/moduleobject.c8
5 files changed, 99 insertions, 2 deletions
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index 773b709..e2384a0 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -1861,6 +1861,26 @@ class SubinterpImportTests(unittest.TestCase):
with self.subTest(f'{modname}: not strict'):
self.check_compatible_here(modname, filename, strict=False)
+ @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
+ def test_multi_init_extension_per_interpreter_gil_compat(self):
+ modname = '_test_shared_gil_only'
+ filename = _testmultiphase.__file__
+ loader = ExtensionFileLoader(modname, filename)
+ spec = importlib.util.spec_from_loader(modname, loader)
+ module = importlib.util.module_from_spec(spec)
+ loader.exec_module(module)
+ sys.modules[modname] = module
+
+ require_extension(module)
+ with self.subTest(f'{modname}: isolated, strict'):
+ self.check_incompatible_here(modname, filename, isolated=True)
+ with self.subTest(f'{modname}: not isolated, strict'):
+ self.check_compatible_here(modname, filename,
+ strict=True, isolated=False)
+ with self.subTest(f'{modname}: not isolated, not strict'):
+ self.check_compatible_here(modname, filename,
+ strict=False, isolated=False)
+
def test_python_compat(self):
module = 'threading'
require_pure_python(module)
diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py
index 3bf2bbd..3a74b82 100644
--- a/Lib/test/test_importlib/extension/test_loader.py
+++ b/Lib/test/test_importlib/extension/test_loader.py
@@ -348,6 +348,8 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
'exec_err',
'exec_raise',
'exec_unreported_exception',
+ 'multiple_create_slots',
+ 'multiple_multiple_interpreters_slots',
]:
with self.subTest(name_base):
name = self.name + '_' + name_base
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-05-13-18-56.gh-issue-99113.hT1ajK.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-05-13-18-56.gh-issue-99113.hT1ajK.rst
new file mode 100644
index 0000000..afd2675
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-05-05-13-18-56.gh-issue-99113.hT1ajK.rst
@@ -0,0 +1,11 @@
+Multi-phase init extension modules may now indicate that they support
+running in subinterpreters that have their own GIL. This is done by using
+``Py_MOD_PER_INTERPRETER_GIL_SUPPORTED`` as the value for the
+``Py_mod_multiple_interpreters`` module def slot. Otherwise the module, by
+default, cannot be imported in such subinterpreters. (This does not affect
+the main interpreter or subinterpreters that do not have their own GIL.) In
+addition to the isolation that multi-phase init already normally requires,
+support for per-interpreter GIL involves one additional constraint:
+thread-safety. If the module has external (linked) dependencies and those
+libraries have any state that isn't thread-safe then the module must do the
+additional work to add thread-safety. This should be an uncommon case.
diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c
index 58b064b..ca71b61 100644
--- a/Modules/_testmultiphase.c
+++ b/Modules/_testmultiphase.c
@@ -682,6 +682,27 @@ PyInit__testmultiphase_export_unreported_exception(void)
}
static PyObject*
+createfunc_noop(PyObject *spec, PyModuleDef *def)
+{
+ return PyModule_New("spam");
+}
+
+static PyModuleDef_Slot slots_multiple_create_slots[] = {
+ {Py_mod_create, createfunc_noop},
+ {Py_mod_create, createfunc_noop},
+ {0, NULL},
+};
+
+static PyModuleDef def_multiple_create_slots = TEST_MODULE_DEF(
+ "_testmultiphase_multiple_create_slots", slots_multiple_create_slots, NULL);
+
+PyMODINIT_FUNC
+PyInit__testmultiphase_multiple_create_slots(void)
+{
+ return PyModuleDef_Init(&def_multiple_create_slots);
+}
+
+static PyObject*
createfunc_null(PyObject *spec, PyModuleDef *def)
{
return NULL;
@@ -892,7 +913,24 @@ PyInit__test_module_state_shared(void)
}
-/* multiple interpreters supports */
+/* multiple interpreters support */
+
+static PyModuleDef_Slot slots_multiple_multiple_interpreters_slots[] = {
+ {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
+ {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
+ {0, NULL},
+};
+
+static PyModuleDef def_multiple_multiple_interpreters_slots = TEST_MODULE_DEF(
+ "_testmultiphase_multiple_multiple_interpreters_slots",
+ slots_multiple_multiple_interpreters_slots,
+ NULL);
+
+PyMODINIT_FUNC
+PyInit__testmultiphase_multiple_multiple_interpreters_slots(void)
+{
+ return PyModuleDef_Init(&def_multiple_multiple_interpreters_slots);
+}
static PyModuleDef_Slot non_isolated_slots[] = {
{Py_mod_exec, execfunc},
@@ -909,3 +947,23 @@ PyInit__test_non_isolated(void)
{
return PyModuleDef_Init(&non_isolated_def);
}
+
+
+static PyModuleDef_Slot shared_gil_only_slots[] = {
+ {Py_mod_exec, execfunc},
+ /* Note that Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED is the default.
+ We put it here explicitly to draw attention to the contrast
+ with Py_MOD_PER_INTERPRETER_GIL_SUPPORTED. */
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED},
+ {0, NULL},
+};
+
+static PyModuleDef shared_gil_only_def = TEST_MODULE_DEF("_test_shared_gil_only",
+ shared_gil_only_slots,
+ testexport_methods);
+
+PyMODINIT_FUNC
+PyInit__test_shared_gil_only(void)
+{
+ return PyModuleDef_Init(&shared_gil_only_def);
+}
diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c
index c100d01..985be58 100644
--- a/Objects/moduleobject.c
+++ b/Objects/moduleobject.c
@@ -323,7 +323,13 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
goto error;
}
}
- // XXX Do a similar check once we have PyInterpreterState.ceval.own_gil.
+ else if (multiple_interpreters != Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
+ && interp->ceval.own_gil
+ && !_Py_IsMainInterpreter(interp)
+ && _PyImport_CheckSubinterpIncompatibleExtensionAllowed(name) < 0)
+ {
+ goto error;
+ }
if (create) {
m = create(spec, def);