From 13faf0f3d9391bfb2aa3898420d101883740f0c1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 20 Nov 2025 10:40:46 +0100 Subject: [PATCH 01/10] gh-141780: Make PyModule_FromSlotsAndSpec enable GIL if needed --- Include/internal/pycore_import.h | 11 ++++++-- Objects/moduleobject.c | 17 ++++++++++++ Python/import.c | 45 ++++++++++++++++++-------------- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index d64a18bb09e08f..4c8b8c0ed868d6 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -128,11 +128,18 @@ PyAPI_FUNC(int) _PyImport_ClearExtension(PyObject *name, PyObject *filename); // state of the module argument: // - If module is NULL or a PyModuleObject with md_gil == Py_MOD_GIL_NOT_USED, // call _PyEval_DisableGIL(). -// - Otherwise, call _PyEval_EnableGILPermanent(). If the GIL was not already -// enabled permanently, issue a warning referencing the module's name. +// - Otherwise, call _PyImport_EnableGILAndWarn // // This function may raise an exception. extern int _PyImport_CheckGILForModule(PyObject *module, PyObject *module_name); +// Assuming that the GIL is enabled from a call to +// _PyEval_EnableGILTransient(), call _PyEval_EnableGILPermanent(). +// If the GIL was not already enabled permanently, issue a warning referencing +// the module's name. +// Leave a message in verbose mode. +// +// This function may raise an exception. +extern int _PyImport_EnableGILAndWarn(PyThreadState *, PyObject *module_name); #endif #ifdef __cplusplus diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 6c1c5f5eb89c0c..5a0b16ba57242d 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -2,6 +2,7 @@ #include "Python.h" #include "pycore_call.h" // _PyObject_CallNoArgs() +#include "pycore_ceval.h" // _PyEval_EnableGILTransient() #include "pycore_dict.h" // _PyDict_EnablePerThreadRefcounting() #include "pycore_fileutils.h" // _Py_wgetcwd #include "pycore_import.h" // _PyImport_GetNextModuleIndex() @@ -522,6 +523,22 @@ module_from_def_and_spec( #undef COPY_COMMON_SLOT } +#ifdef Py_GIL_DISABLED + // For modules created directly from slots (not from a def), we enable + // the GIL here (pairing `_PyEval_EnableGILTransient` with + // an immediate `_PyImport_EnableGILAndWarn`). + // For modules created from a def, the caller is responsible for this. + if (!original_def && requires_gil) { + PyThreadState *tstate = _PyThreadState_GET(); + if (_PyEval_EnableGILTransient(tstate) < 0) { + goto error; + } + if (_PyImport_EnableGILAndWarn(tstate, nameobj) < 0) { + goto error; + } + } +#endif + /* By default, multi-phase init modules are expected to work under multiple interpreters. */ if (!has_multiple_interpreters_slot) { diff --git a/Python/import.c b/Python/import.c index 9ab2d3b3552235..ea32bf622fb34b 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1550,25 +1550,9 @@ _PyImport_CheckGILForModule(PyObject* module, PyObject *module_name) if (!PyModule_Check(module) || ((PyModuleObject *)module)->md_requires_gil) { - if (_PyEval_EnableGILPermanent(tstate)) { - int warn_result = PyErr_WarnFormat( - PyExc_RuntimeWarning, - 1, - "The global interpreter lock (GIL) has been enabled to load " - "module '%U', which has not declared that it can run safely " - "without the GIL. To override this behavior and keep the GIL " - "disabled (at your own risk), run with PYTHON_GIL=0 or -Xgil=0.", - module_name - ); - if (warn_result < 0) { - return warn_result; - } - } - - const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp); - if (config->enable_gil == _PyConfig_GIL_DEFAULT && config->verbose) { - PySys_FormatStderr("# loading module '%U', which requires the GIL\n", - module_name); + assert(((PyModuleObject *)module)->md_token_is_def); + if (_PyImport_EnableGILAndWarn(tstate, module_name) < 0) { + return -1; } } else { @@ -1577,6 +1561,27 @@ _PyImport_CheckGILForModule(PyObject* module, PyObject *module_name) return 0; } + +int +_PyImport_EnableGILAndWarn(PyThreadState *tstate, PyObject *module_name) { + if (_PyEval_EnableGILPermanent(tstate)) { + return PyErr_WarnFormat( + PyExc_RuntimeWarning, + 1, + "The global interpreter lock (GIL) has been enabled to load " + "module '%U', which has not declared that it can run safely " + "without the GIL. To override this behavior and keep the GIL " + "disabled (at your own risk), run with PYTHON_GIL=0 or -Xgil=0.", + module_name + ); + } + const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp); + if (config->enable_gil == _PyConfig_GIL_DEFAULT && config->verbose) { + PySys_FormatStderr("# loading module '%U', which requires the GIL\n", + module_name); + } + return 0; +} #endif static PyThreadState * @@ -4785,6 +4790,8 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file) _PyImport_GetModuleExportHooks(&info, fp, &p0, &ex0); if (ex0) { mod = import_run_modexport(tstate, ex0, &info, spec); + // Modules created from slots handle GIL enablement (Py_mod_gil slot) + // when they're created. goto cleanup; } if (p0 == NULL) { From 60a669cc03b4e37418e29cfc73cefc58a1239dcf Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 20 Nov 2025 11:31:17 +0100 Subject: [PATCH 02/10] Make test.test_importlib and _testmultiphase importable in subinterpreters --- Lib/test/test_importlib/util.py | 3 ++- Modules/_testmultiphase.c | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py index edbe78545a2536..efbec667317d5f 100644 --- a/Lib/test/test_importlib/util.py +++ b/Lib/test/test_importlib/util.py @@ -15,7 +15,8 @@ import tempfile import types -_testsinglephase = import_helper.import_module("_testsinglephase") +# gh-116303: Skip test module dependent tests if test modules are unavailable +import_helper.import_module("_testmultiphase") BUILTINS = types.SimpleNamespace() diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c index cd2d7b65598277..03bea2b4dec60f 100644 --- a/Modules/_testmultiphase.c +++ b/Modules/_testmultiphase.c @@ -1024,6 +1024,8 @@ PyModExport__test_from_modexport(void) { static PyModuleDef_Slot slots[] = { {Py_mod_name, "_test_from_modexport"}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; return slots; @@ -1073,6 +1075,8 @@ PyModExport__test_from_modexport_create_nonmodule(void) static PyModuleDef_Slot slots[] = { {Py_mod_name, "_test_from_modexport_create_nonmodule"}, {Py_mod_create, modexport_create_string}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; return slots; @@ -1165,6 +1169,8 @@ PyModExport__test_from_modexport_smoke(void) {Py_mod_methods, methods}, {Py_mod_state_free, modexport_smoke_free}, {Py_mod_token, (void*)&modexport_smoke_test_token}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; return slots; From 8a8a3449d08f47e053aecff6c318ee9d083e57a3 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 20 Nov 2025 11:33:40 +0100 Subject: [PATCH 03/10] Test that Py_MOD_GIL_USED re-enables the GIL --- Lib/test/test_import/__init__.py | 59 ++++++++++++++++++++++++++++++++ Modules/_testmultiphase.c | 25 ++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index fd9750eae80445..e7c3ce015d3f1b 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1,4 +1,5 @@ import builtins +import concurrent.interpreters import errno import glob import json @@ -3407,6 +3408,35 @@ def test_from_modexport(self): self.assertEqual(module.__name__, modname) + def test_from_modexport_gil_used(self): + # Test that a module with Py_MOD_GIL_USED (re-)enables the GIL. + # Do this in a new interpreter to avoid interfering with global state. + modname = '_test_from_modexport_gil_used' + filename = _testmultiphase.__file__ + interp = concurrent.interpreters.create() + self.addCleanup(interp.close) + queue = concurrent.interpreters.create_queue() + interp.prepare_main( + modname=modname, + filename=filename, + queue=queue, + ) + enabled_before = sys._is_gil_enabled() + interp.exec(f"""if True: + import sys + from {__name__} import import_extension_from_file + module = import_extension_from_file(modname, filename, + put_in_sys_modules=False) + queue.put(module.__name__) + queue.put(sys._is_gil_enabled()) + """) + + self.assertEqual(queue.get(), modname) + self.assertEqual(queue.get(), True) + self.assertTrue(queue.empty()) + + self.assertEqual(enabled_before, sys._is_gil_enabled()) + def test_from_modexport_null(self): modname = '_test_from_modexport_null' filename = _testmultiphase.__file__ @@ -3428,6 +3458,35 @@ def test_from_modexport_create_nonmodule(self): put_in_sys_modules=False) self.assertIsInstance(module, str) + def test_from_modexport_create_nonmodule_gil_used(self): + # Test that a module with Py_MOD_GIL_USED (re-)enables the GIL. + # Do this in a new interpreter to avoid interfering with global state. + modname = '_test_from_modexport_create_nonmodule_gil_used' + filename = _testmultiphase.__file__ + interp = concurrent.interpreters.create() + self.addCleanup(interp.close) + queue = concurrent.interpreters.create_queue() + interp.prepare_main( + modname=modname, + filename=filename, + queue=queue, + ) + enabled_before = sys._is_gil_enabled() + interp.exec(f"""if True: + import sys + from {__name__} import import_extension_from_file + module = import_extension_from_file(modname, filename, + put_in_sys_modules=False) + queue.put(module) + queue.put(sys._is_gil_enabled()) + """) + + self.assertIsInstance(queue.get(), str) + self.assertEqual(queue.get(), True) + self.assertTrue(queue.empty()) + + self.assertEqual(enabled_before, sys._is_gil_enabled()) + def test_from_modexport_smoke(self): # General positive test for sundry features # (PyModule_FromSlotsAndSpec tests exercise these more carefully) diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c index 03bea2b4dec60f..e286eaae820b2b 100644 --- a/Modules/_testmultiphase.c +++ b/Modules/_testmultiphase.c @@ -1031,6 +1031,18 @@ PyModExport__test_from_modexport(void) return slots; } +PyMODEXPORT_FUNC +PyModExport__test_from_modexport_gil_used(void) +{ + static PyModuleDef_Slot slots[] = { + {Py_mod_name, "_test_from_modexport_gil_used"}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_USED}, + {0}, + }; + return slots; +} + PyMODEXPORT_FUNC PyModExport__test_from_modexport_null(void) { @@ -1082,6 +1094,19 @@ PyModExport__test_from_modexport_create_nonmodule(void) return slots; } +PyMODEXPORT_FUNC +PyModExport__test_from_modexport_create_nonmodule_gil_used(void) +{ + static PyModuleDef_Slot slots[] = { + {Py_mod_name, "_test_from_modexport_create_nonmodule"}, + {Py_mod_create, modexport_create_string}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_USED}, + {0}, + }; + return slots; +} + static PyModuleDef_Slot modexport_empty_slots[] = { {0}, }; From e2c9c7e407efd012380ddbddcfe3d2740623baae Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 20 Nov 2025 13:19:08 +0100 Subject: [PATCH 04/10] blurb --- .../2025-11-20-13-18-57.gh-issue-141780.xDrVNr.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-11-20-13-18-57.gh-issue-141780.xDrVNr.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-20-13-18-57.gh-issue-141780.xDrVNr.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-20-13-18-57.gh-issue-141780.xDrVNr.rst new file mode 100644 index 00000000000000..8700ac14824417 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-20-13-18-57.gh-issue-141780.xDrVNr.rst @@ -0,0 +1,2 @@ +Fix :c:macro:`Py_mod_gil` with API added in :pep:`793`: +:c:func:`!PyModule_FromSlotsAndSpec` and ``PyModExport`` hooks From b18c679b4a0334571d506a0a6dcc1f3567461f8e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 20 Nov 2025 14:24:49 +0100 Subject: [PATCH 05/10] Pass with -Werror --- Lib/test/test_import/__init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index e7c3ce015d3f1b..ae51421688806f 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -3424,9 +3424,12 @@ def test_from_modexport_gil_used(self): enabled_before = sys._is_gil_enabled() interp.exec(f"""if True: import sys + from test.support.warnings_helper import check_warnings from {__name__} import import_extension_from_file - module = import_extension_from_file(modname, filename, - put_in_sys_modules=False) + with check_warnings((".*GIL..has been enabled.*", RuntimeWarning), + quiet=True): + module = import_extension_from_file(modname, filename, + put_in_sys_modules=False) queue.put(module.__name__) queue.put(sys._is_gil_enabled()) """) @@ -3474,9 +3477,12 @@ def test_from_modexport_create_nonmodule_gil_used(self): enabled_before = sys._is_gil_enabled() interp.exec(f"""if True: import sys + from test.support.warnings_helper import check_warnings from {__name__} import import_extension_from_file - module = import_extension_from_file(modname, filename, - put_in_sys_modules=False) + with check_warnings((".*GIL..has been enabled.*", RuntimeWarning), + quiet=True): + module = import_extension_from_file(modname, filename, + put_in_sys_modules=False) queue.put(module) queue.put(sys._is_gil_enabled()) """) From 103f427fdb38595e57e4d329424203ea24c16446 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 20 Nov 2025 14:28:04 +0100 Subject: [PATCH 06/10] Skip test_from_modexport_empty_slots on GIL-ful builds --- Lib/test/test_import/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index ae51421688806f..dcc345135717e9 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -3521,6 +3521,7 @@ class Sub(tp): pass self.assertEqual(_testcapi.pytype_getmodulebytoken(Sub, token), module) + @requires_gil_enabled("empty slots re-enable GIL") def test_from_modexport_empty_slots(self): # Module to test that: # - no slots are mandatory for PyModExport From 91d59e9b3985f984506a5f4b49be4f26f60479fe Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 20 Nov 2025 14:41:51 +0100 Subject: [PATCH 07/10] Use @requires_subinterpreters --- Lib/test/test_import/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index dcc345135717e9..271361ae816449 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1,5 +1,4 @@ import builtins -import concurrent.interpreters import errno import glob import json @@ -66,8 +65,10 @@ _testmultiphase = None try: import _interpreters + import concurrent.interpreters except ModuleNotFoundError: _interpreters = None + concurrent = None try: import _testinternalcapi except ImportError: @@ -3408,6 +3409,7 @@ def test_from_modexport(self): self.assertEqual(module.__name__, modname) + @requires_subinterpreters def test_from_modexport_gil_used(self): # Test that a module with Py_MOD_GIL_USED (re-)enables the GIL. # Do this in a new interpreter to avoid interfering with global state. @@ -3461,6 +3463,7 @@ def test_from_modexport_create_nonmodule(self): put_in_sys_modules=False) self.assertIsInstance(module, str) + @requires_subinterpreters def test_from_modexport_create_nonmodule_gil_used(self): # Test that a module with Py_MOD_GIL_USED (re-)enables the GIL. # Do this in a new interpreter to avoid interfering with global state. From 3e82c25f104154140de0ed03be617acb2391f729 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 20 Nov 2025 15:07:19 +0100 Subject: [PATCH 08/10] Add more gil & interpreters slots --- Lib/test/test_capi/test_module.py | 3 ++- Modules/_testcapi/module.c | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_module.py b/Lib/test/test_capi/test_module.py index 7ec23e637d7de6..823e2ab6b2ef0d 100644 --- a/Lib/test/test_capi/test_module.py +++ b/Lib/test/test_capi/test_module.py @@ -3,7 +3,7 @@ import unittest import types -from test.support import import_helper, subTests +from test.support import import_helper, subTests, requires_gil_enabled # Skip this test if the _testcapi module isn't available. _testcapi = import_helper.import_module('_testcapi') @@ -25,6 +25,7 @@ def def_and_token(mod): ) class TestModFromSlotsAndSpec(unittest.TestCase): + @requires_gil_enabled("empty slots re-enable GIL") def test_empty(self): mod = _testcapi.module_from_slots_empty(FakeSpec()) self.assertIsInstance(mod, types.ModuleType) diff --git a/Modules/_testcapi/module.c b/Modules/_testcapi/module.c index 9349445351eaca..7b5861bc08ed77 100644 --- a/Modules/_testcapi/module.c +++ b/Modules/_testcapi/module.c @@ -27,6 +27,8 @@ module_from_slots_name(PyObject *self, PyObject *spec) { PyModuleDef_Slot slots[] = { {Py_mod_name, "currently ignored..."}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; return PyModule_FromSlotsAndSpec(slots, spec); @@ -37,6 +39,8 @@ module_from_slots_doc(PyObject *self, PyObject *spec) { PyModuleDef_Slot slots[] = { {Py_mod_doc, "the docstring"}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; return PyModule_FromSlotsAndSpec(slots, spec); @@ -47,6 +51,8 @@ module_from_slots_size(PyObject *self, PyObject *spec) { PyModuleDef_Slot slots[] = { {Py_mod_state_size, (void*)123}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec); @@ -72,6 +78,8 @@ module_from_slots_methods(PyObject *self, PyObject *spec) { PyModuleDef_Slot slots[] = { {Py_mod_methods, a_methoddef_array}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; return PyModule_FromSlotsAndSpec(slots, spec); @@ -90,6 +98,8 @@ module_from_slots_gc(PyObject *self, PyObject *spec) {Py_mod_state_traverse, noop_traverse}, {Py_mod_state_clear, noop_clear}, {Py_mod_state_free, noop_free}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec); @@ -118,6 +128,8 @@ module_from_slots_token(PyObject *self, PyObject *spec) { PyModuleDef_Slot slots[] = { {Py_mod_token, (void*)&test_token}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec); @@ -144,6 +156,8 @@ module_from_slots_exec(PyObject *self, PyObject *spec) { PyModuleDef_Slot slots[] = { {Py_mod_exec, simple_exec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec); @@ -175,6 +189,8 @@ module_from_slots_create(PyObject *self, PyObject *spec) { PyModuleDef_Slot slots[] = { {Py_mod_create, create_attr_from_spec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; return PyModule_FromSlotsAndSpec(slots, spec); @@ -205,6 +221,8 @@ module_from_slots_repeat_slot(PyObject *self, PyObject *spec) PyModuleDef_Slot slots[] = { {slot_id, "anything"}, {slot_id, "anything else"}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; return PyModule_FromSlotsAndSpec(slots, spec); @@ -219,6 +237,8 @@ module_from_slots_null_slot(PyObject *self, PyObject *spec) } PyModuleDef_Slot slots[] = { {slot_id, NULL}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; return PyModule_FromSlotsAndSpec(slots, spec); @@ -233,6 +253,8 @@ module_from_def_slot(PyObject *self, PyObject *spec) } PyModuleDef_Slot slots[] = { {slot_id, "anything"}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; PyModuleDef def = { @@ -280,6 +302,7 @@ module_from_def_multiple_exec(PyObject *self, PyObject *spec) static PyModuleDef_Slot slots[] = { {Py_mod_exec, simple_exec}, {Py_mod_exec, another_exec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0}, }; From 5b8b87c3140edd19a79490d1f66eac20a77f2a7f Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 20 Nov 2025 15:12:32 +0100 Subject: [PATCH 09/10] Fix assertion logic --- Python/import.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Python/import.c b/Python/import.c index ea32bf622fb34b..309242400c709c 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1550,7 +1550,9 @@ _PyImport_CheckGILForModule(PyObject* module, PyObject *module_name) if (!PyModule_Check(module) || ((PyModuleObject *)module)->md_requires_gil) { - assert(((PyModuleObject *)module)->md_token_is_def); + if (PyModule_Check(module)) { + assert(((PyModuleObject *)module)->md_token_is_def); + } if (_PyImport_EnableGILAndWarn(tstate, module_name) < 0) { return -1; } From cde6c25858833988023682a024199bb6ddc5fe0a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 21 Nov 2025 16:13:24 +0100 Subject: [PATCH 10/10] Python/import.c: PEP7 Co-authored-by: Sam Gross --- Python/import.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/import.c b/Python/import.c index 309242400c709c..0ac7d15976adbd 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1565,7 +1565,8 @@ _PyImport_CheckGILForModule(PyObject* module, PyObject *module_name) } int -_PyImport_EnableGILAndWarn(PyThreadState *tstate, PyObject *module_name) { +_PyImport_EnableGILAndWarn(PyThreadState *tstate, PyObject *module_name) +{ if (_PyEval_EnableGILPermanent(tstate)) { return PyErr_WarnFormat( PyExc_RuntimeWarning,