From a73f36f403286af0b120b79785fb718c6ef03877 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 3 Nov 2022 15:37:24 -0600 Subject: [PATCH 1/8] Add _PyInterpreterConfig.own_gil. --- Include/cpython/initconfig.h | 3 +++ Lib/test/test_capi/test_misc.py | 29 ++++++++++++++++++++--------- Lib/test/test_import/__init__.py | 1 + Lib/test/test_threading.py | 1 + Modules/_testcapimodule.c | 12 ++++++++++-- Python/pylifecycle.c | 4 +++- 6 files changed, 38 insertions(+), 12 deletions(-) diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 9c1783d272f1cd..efae2409b50069 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -252,6 +252,7 @@ typedef struct { int allow_threads; int allow_daemon_threads; int check_multi_interp_extensions; + int own_gil; } PyInterpreterConfig; #define _PyInterpreterConfig_INIT \ @@ -262,6 +263,7 @@ typedef struct { .allow_threads = 1, \ .allow_daemon_threads = 0, \ .check_multi_interp_extensions = 1, \ + .own_gil = 1, \ } #define _PyInterpreterConfig_LEGACY_INIT \ @@ -272,6 +274,7 @@ typedef struct { .allow_threads = 1, \ .allow_daemon_threads = 1, \ .check_multi_interp_extensions = 0, \ + .own_gil = 0, \ } /* --- Helper functions --------------------------------------- */ diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 22be3c0814278e..996a1439e760ac 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1401,19 +1401,29 @@ def test_configured_settings(self): DAEMON_THREADS = 1<<11 FORK = 1<<15 EXEC = 1<<16 - - features = ['obmalloc', 'fork', 'exec', 'threads', 'daemon_threads', - 'extensions'] + ALL_FLAGS = (OBMALLOC | FORK | EXEC | THREADS | DAEMON_THREADS + | EXTENSIONS); + + features = [ + 'obmalloc', + 'fork', + 'exec', + 'threads', + 'daemon_threads', + 'extensions', + 'own_gil', + ] kwlist = [f'allow_{n}' for n in features] kwlist[0] = 'use_main_obmalloc' - kwlist[-1] = 'check_multi_interp_extensions' + kwlist[-2] = 'check_multi_interp_extensions' + kwlist[-1] = 'own_gil' # expected to work for config, expected in { - (True, True, True, True, True, True): - OBMALLOC | FORK | EXEC | THREADS | DAEMON_THREADS | EXTENSIONS, - (True, False, False, False, False, False): OBMALLOC, - (False, False, False, True, False, True): THREADS | EXTENSIONS, + (True, True, True, True, True, True, True): ALL_FLAGS, + (True, False, False, False, False, False, False): OBMALLOC, + (False, False, False, True, False, True, False): + THREADS | EXTENSIONS, }.items(): kwargs = dict(zip(kwlist, config)) expected = { @@ -1437,7 +1447,7 @@ def test_configured_settings(self): # expected to fail for config in [ - (False, False, False, False, False, False), + (False, False, False, False, False, False, False), ]: kwargs = dict(zip(kwlist, config)) with self.subTest(config): @@ -1473,6 +1483,7 @@ def test_overridden_setting_extensions_subinterp_check(self): 'allow_exec': True, 'allow_threads': True, 'allow_daemon_threads': True, + 'own_gil': False, } def check(enabled, override): diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 9211639b016e7a..9885a89a723b06 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1640,6 +1640,7 @@ class SubinterpImportTests(unittest.TestCase): ) ISOLATED = dict( use_main_obmalloc=False, + own_gil=False, ) NOT_ISOLATED = {k: not v for k, v in ISOLATED.items()} diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index fdd74c37e26235..97165264b34bbe 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1349,6 +1349,7 @@ def func(): allow_threads={allowed}, allow_daemon_threads={daemon_allowed}, check_multi_interp_extensions=False, + own_gil=False, ) """) with test.support.SuppressCrashReport(): diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 376f04f23e324a..79ab7d3f5555c2 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1488,6 +1488,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) int allow_threads = -1; int allow_daemon_threads = -1; int check_multi_interp_extensions = -1; + int own_gil = -1; int r; PyThreadState *substate, *mainstate; /* only initialise 'cflags.cf_flags' to test backwards compatibility */ @@ -1500,13 +1501,15 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) "allow_threads", "allow_daemon_threads", "check_multi_interp_extensions", + "own_gil", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "s$pppppp:run_in_subinterp_with_config", kwlist, + "s$ppppppp:run_in_subinterp_with_config", kwlist, &code, &use_main_obmalloc, &allow_fork, &allow_exec, &allow_threads, &allow_daemon_threads, - &check_multi_interp_extensions)) { + &check_multi_interp_extensions, + &own_gil)) { return NULL; } if (use_main_obmalloc < 0) { @@ -1525,6 +1528,10 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) PyErr_SetString(PyExc_ValueError, "missing allow_threads"); return NULL; } + if (own_gil < 0) { + PyErr_SetString(PyExc_ValueError, "missing own_gil"); + return NULL; + } if (allow_daemon_threads < 0) { PyErr_SetString(PyExc_ValueError, "missing allow_daemon_threads"); return NULL; @@ -1545,6 +1552,7 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) .allow_threads = allow_threads, .allow_daemon_threads = allow_daemon_threads, .check_multi_interp_extensions = check_multi_interp_extensions, + .own_gil = own_gil, }; PyStatus status = Py_NewInterpreterFromConfig(&substate, &config); if (PyStatus_Exception(status)) { diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 97957d3f17e6db..0349bca6ee8a6c 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -632,7 +632,9 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } - const PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT; + PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT; + // The main interpreter always has its own GIL. + config.own_gil = 1; status = init_interp_settings(interp, &config); if (_PyStatus_EXCEPTION(status)) { return status; From 37bf29c95dd6256496712fa0313233f762b7478d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 4 Nov 2022 17:38:25 -0600 Subject: [PATCH 2/8] Use PyInterpreterConfig.own_gil. --- Include/internal/pycore_ceval.h | 2 +- Include/internal/pycore_ceval_state.h | 1 + Lib/test/test_capi/test_misc.py | 13 +++++++++---- Lib/test/test_embed.py | 1 + Lib/test/test_import/__init__.py | 2 +- Modules/_testinternalcapi.c | 7 +++++++ Python/ceval_gil.c | 23 ++++++++++++++++++++++- Python/pylifecycle.c | 8 ++++---- 8 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 0bbc9efdda3ba7..b7a9bf40425bc7 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -97,7 +97,7 @@ _PyEval_Vector(PyThreadState *tstate, PyObject *kwnames); extern int _PyEval_ThreadsInitialized(void); -extern PyStatus _PyEval_InitGIL(PyThreadState *tstate); +extern PyStatus _PyEval_InitGIL(PyThreadState *tstate, int own_gil); extern void _PyEval_FiniGIL(PyInterpreterState *interp); extern void _PyEval_ReleaseLock(PyThreadState *tstate); diff --git a/Include/internal/pycore_ceval_state.h b/Include/internal/pycore_ceval_state.h index 1a00ec80270e0c..4781dd5735dcf6 100644 --- a/Include/internal/pycore_ceval_state.h +++ b/Include/internal/pycore_ceval_state.h @@ -86,6 +86,7 @@ struct _pending_calls { struct _ceval_state { int recursion_limit; struct _gil_runtime_state *gil; + int own_gil; /* This single variable consolidates all requests to break out of the fast path in the eval loop. */ _Py_atomic_int eval_breaker; diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 996a1439e760ac..3fc2c07f933061 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1420,14 +1420,18 @@ def test_configured_settings(self): # expected to work for config, expected in { - (True, True, True, True, True, True, True): ALL_FLAGS, - (True, False, False, False, False, False, False): OBMALLOC, + (True, True, True, True, True, True, True): + (ALL_FLAGS, True), + (True, False, False, False, False, False, False): + (OBMALLOC, False), (False, False, False, True, False, True, False): - THREADS | EXTENSIONS, + (THREADS | EXTENSIONS, False), }.items(): kwargs = dict(zip(kwlist, config)) + exp_flags, exp_gil = expected expected = { - 'feature_flags': expected, + 'feature_flags': exp_flags, + 'own_gil': exp_gil, } with self.subTest(config): r, w = os.pipe() @@ -1494,6 +1498,7 @@ def check(enabled, override): flags = BASE_FLAGS | EXTENSIONS if enabled else BASE_FLAGS settings = { 'feature_flags': flags, + 'own_gil': False, } expected = { diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index c9691bbf304915..582392ecddcb91 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1666,6 +1666,7 @@ def test_init_main_interpreter_settings(self): # All optional features should be enabled. 'feature_flags': OBMALLOC | FORK | EXEC | THREADS | DAEMON_THREADS, + 'own_gil': True, } out, err = self.run_embedded_interpreter( 'test_init_main_interpreter_settings', diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 9885a89a723b06..773b7094c6b8ce 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1640,7 +1640,7 @@ class SubinterpImportTests(unittest.TestCase): ) ISOLATED = dict( use_main_obmalloc=False, - own_gil=False, + own_gil=True, ) NOT_ISOLATED = {k: not v for k, v in ISOLATED.items()} diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 24152412107c09..f35e3b48df9321 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -729,6 +729,13 @@ get_interp_settings(PyObject *self, PyObject *args) return NULL; } + /* "own GIL" */ + PyObject *own_gil = interp->ceval.own_gil ? Py_True : Py_False; + if (PyDict_SetItemString(settings, "own_gil", own_gil) != 0) { + Py_DECREF(settings); + return NULL; + } + return settings; } diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index ad33a58dedd820..a390bec80d556c 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -500,9 +500,18 @@ PyEval_ThreadsInitialized(void) } PyStatus -_PyEval_InitGIL(PyThreadState *tstate) +_PyEval_InitGIL(PyThreadState *tstate, int own_gil) { assert(tstate->interp->ceval.gil == NULL); + if (!own_gil) { + PyInterpreterState *main_interp = _PyInterpreterState_Main(); + assert(tstate->interp != main_interp); + struct _gil_runtime_state *gil = main_interp->ceval.gil; + assert(gil_created(gil)); + tstate->interp->ceval.gil = gil; + tstate->interp->ceval.own_gil = 0; + return _PyStatus_OK(); + } /* XXX per-interpreter GIL */ struct _gil_runtime_state *gil = &tstate->interp->runtime->ceval.gil; @@ -512,8 +521,11 @@ _PyEval_InitGIL(PyThreadState *tstate) and destroy it. */ assert(gil_created(gil)); tstate->interp->ceval.gil = gil; + // XXX For now we lie. + tstate->interp->ceval.own_gil = 1; return _PyStatus_OK(); } + assert(own_gil); assert(!gil_created(gil)); @@ -521,6 +533,7 @@ _PyEval_InitGIL(PyThreadState *tstate) create_gil(gil); assert(gil_created(gil)); tstate->interp->ceval.gil = gil; + tstate->interp->ceval.own_gil = 1; take_gil(tstate); return _PyStatus_OK(); } @@ -530,6 +543,14 @@ _PyEval_FiniGIL(PyInterpreterState *interp) { if (interp->ceval.gil == NULL) { /* It was already finalized (or hasn't been initialized yet). */ + assert(!interp->ceval.own_gil); + return; + } + else if (!interp->ceval.own_gil) { + PyInterpreterState *main_interp = _PyInterpreterState_Main(); + assert(interp != main_interp); + assert(interp->ceval.gil == main_interp->ceval.gil); + interp->ceval.gil = NULL; return; } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 0349bca6ee8a6c..705708698c6a8b 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -585,7 +585,7 @@ init_interp_settings(PyInterpreterState *interp, static PyStatus -init_interp_create_gil(PyThreadState *tstate) +init_interp_create_gil(PyThreadState *tstate, int own_gil) { PyStatus status; @@ -600,7 +600,7 @@ init_interp_create_gil(PyThreadState *tstate) } /* Create the GIL and take it */ - status = _PyEval_InitGIL(tstate); + status = _PyEval_InitGIL(tstate, own_gil); if (_PyStatus_EXCEPTION(status)) { return status; } @@ -647,7 +647,7 @@ pycore_create_interpreter(_PyRuntimeState *runtime, _PyThreadState_Bind(tstate); (void) PyThreadState_Swap(tstate); - status = init_interp_create_gil(tstate); + status = init_interp_create_gil(tstate, config.own_gil); if (_PyStatus_EXCEPTION(status)) { return status; } @@ -2049,7 +2049,7 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) goto error; } - status = init_interp_create_gil(tstate); + status = init_interp_create_gil(tstate, config->own_gil); if (_PyStatus_EXCEPTION(status)) { goto error; } From e32c4879765f54a6a57b586ab1ac792c8ff95d7c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 5 May 2023 10:46:02 -0600 Subject: [PATCH 3/8] Fail unsupported extensions. --- Objects/moduleobject.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index c100d018d3f0df..90d8f5808315a5 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -323,7 +323,14 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio goto error; } } - // XXX Do a similar check once we have PyInterpreterState.ceval.own_gil. + // XXX This case needs a test. + 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); From 51d8587c0f1bcc05174341d4b8e03865936b904c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 5 May 2023 13:54:45 -0600 Subject: [PATCH 4/8] Add a test. --- Lib/test/test_import/__init__.py | 20 ++++++++++++++++++++ Modules/_testmultiphase.c | 19 ++++++++++++++++++- Objects/moduleobject.c | 1 - 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 773b7094c6b8ce..e2384a08ecaa90 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1861,6 +1861,26 @@ def test_multi_init_extension_non_isolated_compat(self): 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/Modules/_testmultiphase.c b/Modules/_testmultiphase.c index 58b064bb17cd87..299be18545ab5b 100644 --- a/Modules/_testmultiphase.c +++ b/Modules/_testmultiphase.c @@ -892,7 +892,7 @@ PyInit__test_module_state_shared(void) } -/* multiple interpreters supports */ +/* multiple interpreters support */ static PyModuleDef_Slot non_isolated_slots[] = { {Py_mod_exec, execfunc}, @@ -909,3 +909,20 @@ PyInit__test_non_isolated(void) { return PyModuleDef_Init(&non_isolated_def); } + + +static PyModuleDef_Slot shared_gil_only_slots[] = { + {Py_mod_exec, execfunc}, + {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 90d8f5808315a5..985be58d02c784 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -323,7 +323,6 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio goto error; } } - // XXX This case needs a test. else if (multiple_interpreters != Py_MOD_PER_INTERPRETER_GIL_SUPPORTED && interp->ceval.own_gil && !_Py_IsMainInterpreter(interp) From e2ddc272382d274c9a4b9bdcc2355073da049591 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 5 May 2023 13:19:04 -0600 Subject: [PATCH 5/8] Add a NEWS entry. --- .../2023-05-05-13-18-56.gh-issue-99113.hT1ajK.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-05-05-13-18-56.gh-issue-99113.hT1ajK.rst 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 00000000000000..afd26750846167 --- /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. From ac95d09f9613c69e5f568bda9da82e577fa6d908 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Sat, 6 May 2023 14:48:46 -0600 Subject: [PATCH 6/8] Add a note. --- Modules/_testmultiphase.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c index 299be18545ab5b..51a69617780ac7 100644 --- a/Modules/_testmultiphase.c +++ b/Modules/_testmultiphase.c @@ -913,6 +913,9 @@ PyInit__test_non_isolated(void) 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}, }; From 5ccc22055cfeb3bb629d7d8d2d827ab903801179 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Sat, 6 May 2023 15:01:34 -0600 Subject: [PATCH 7/8] Add a test for when an extension has multiple Py_mod_create slots. --- .../test_importlib/extension/test_loader.py | 1 + Modules/_testmultiphase.c | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py index 3bf2bbdcdcc4e6..e8c7c09c2b8ecb 100644 --- a/Lib/test/test_importlib/extension/test_loader.py +++ b/Lib/test/test_importlib/extension/test_loader.py @@ -348,6 +348,7 @@ def test_bad_modules(self): 'exec_err', 'exec_raise', 'exec_unreported_exception', + 'multiple_create_slots', ]: with self.subTest(name_base): name = self.name + '_' + name_base diff --git a/Modules/_testmultiphase.c b/Modules/_testmultiphase.c index 51a69617780ac7..a83ead9b919a55 100644 --- a/Modules/_testmultiphase.c +++ b/Modules/_testmultiphase.c @@ -681,6 +681,27 @@ PyInit__testmultiphase_export_unreported_exception(void) return PyModuleDef_Init(&main_def); } +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) { From 376217a8bfb08f49ac993cc4185f9fd92f3bb49e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Sat, 6 May 2023 15:06:34 -0600 Subject: [PATCH 8/8] Add a test for when an extension has multiple Py_mod_multiple_interpreters slots. --- .../test_importlib/extension/test_loader.py | 1 + Modules/_testmultiphase.c | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py index e8c7c09c2b8ecb..3a74b821eaee49 100644 --- a/Lib/test/test_importlib/extension/test_loader.py +++ b/Lib/test/test_importlib/extension/test_loader.py @@ -349,6 +349,7 @@ def test_bad_modules(self): '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/Modules/_testmultiphase.c b/Modules/_testmultiphase.c index a83ead9b919a55..ca71b6156b005d 100644 --- a/Modules/_testmultiphase.c +++ b/Modules/_testmultiphase.c @@ -915,6 +915,23 @@ PyInit__test_module_state_shared(void) /* 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}, {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},