Skip to content

Commit bf66bce

Browse files
authored
gh-141780: Make PyModule_FromSlotsAndSpec enable GIL if needed (GH-141785)
1 parent 6462322 commit bf66bce

File tree

9 files changed

+183
-22
lines changed

9 files changed

+183
-22
lines changed

Include/internal/pycore_import.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,18 @@ PyAPI_FUNC(int) _PyImport_ClearExtension(PyObject *name, PyObject *filename);
128128
// state of the module argument:
129129
// - If module is NULL or a PyModuleObject with md_gil == Py_MOD_GIL_NOT_USED,
130130
// call _PyEval_DisableGIL().
131-
// - Otherwise, call _PyEval_EnableGILPermanent(). If the GIL was not already
132-
// enabled permanently, issue a warning referencing the module's name.
131+
// - Otherwise, call _PyImport_EnableGILAndWarn
133132
//
134133
// This function may raise an exception.
135134
extern int _PyImport_CheckGILForModule(PyObject *module, PyObject *module_name);
135+
// Assuming that the GIL is enabled from a call to
136+
// _PyEval_EnableGILTransient(), call _PyEval_EnableGILPermanent().
137+
// If the GIL was not already enabled permanently, issue a warning referencing
138+
// the module's name.
139+
// Leave a message in verbose mode.
140+
//
141+
// This function may raise an exception.
142+
extern int _PyImport_EnableGILAndWarn(PyThreadState *, PyObject *module_name);
136143
#endif
137144

138145
#ifdef __cplusplus

Lib/test/test_capi/test_module.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import unittest
55
import types
6-
from test.support import import_helper, subTests
6+
from test.support import import_helper, subTests, requires_gil_enabled
77

88
# Skip this test if the _testcapi module isn't available.
99
_testcapi = import_helper.import_module('_testcapi')
@@ -25,6 +25,7 @@ def def_and_token(mod):
2525
)
2626

2727
class TestModFromSlotsAndSpec(unittest.TestCase):
28+
@requires_gil_enabled("empty slots re-enable GIL")
2829
def test_empty(self):
2930
mod = _testcapi.module_from_slots_empty(FakeSpec())
3031
self.assertIsInstance(mod, types.ModuleType)

Lib/test/test_import/__init__.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@
6565
_testmultiphase = None
6666
try:
6767
import _interpreters
68+
import concurrent.interpreters
6869
except ModuleNotFoundError:
6970
_interpreters = None
71+
concurrent = None
7072
try:
7173
import _testinternalcapi
7274
except ImportError:
@@ -3407,6 +3409,39 @@ def test_from_modexport(self):
34073409

34083410
self.assertEqual(module.__name__, modname)
34093411

3412+
@requires_subinterpreters
3413+
def test_from_modexport_gil_used(self):
3414+
# Test that a module with Py_MOD_GIL_USED (re-)enables the GIL.
3415+
# Do this in a new interpreter to avoid interfering with global state.
3416+
modname = '_test_from_modexport_gil_used'
3417+
filename = _testmultiphase.__file__
3418+
interp = concurrent.interpreters.create()
3419+
self.addCleanup(interp.close)
3420+
queue = concurrent.interpreters.create_queue()
3421+
interp.prepare_main(
3422+
modname=modname,
3423+
filename=filename,
3424+
queue=queue,
3425+
)
3426+
enabled_before = sys._is_gil_enabled()
3427+
interp.exec(f"""if True:
3428+
import sys
3429+
from test.support.warnings_helper import check_warnings
3430+
from {__name__} import import_extension_from_file
3431+
with check_warnings((".*GIL..has been enabled.*", RuntimeWarning),
3432+
quiet=True):
3433+
module = import_extension_from_file(modname, filename,
3434+
put_in_sys_modules=False)
3435+
queue.put(module.__name__)
3436+
queue.put(sys._is_gil_enabled())
3437+
""")
3438+
3439+
self.assertEqual(queue.get(), modname)
3440+
self.assertEqual(queue.get(), True)
3441+
self.assertTrue(queue.empty())
3442+
3443+
self.assertEqual(enabled_before, sys._is_gil_enabled())
3444+
34103445
def test_from_modexport_null(self):
34113446
modname = '_test_from_modexport_null'
34123447
filename = _testmultiphase.__file__
@@ -3428,6 +3463,39 @@ def test_from_modexport_create_nonmodule(self):
34283463
put_in_sys_modules=False)
34293464
self.assertIsInstance(module, str)
34303465

3466+
@requires_subinterpreters
3467+
def test_from_modexport_create_nonmodule_gil_used(self):
3468+
# Test that a module with Py_MOD_GIL_USED (re-)enables the GIL.
3469+
# Do this in a new interpreter to avoid interfering with global state.
3470+
modname = '_test_from_modexport_create_nonmodule_gil_used'
3471+
filename = _testmultiphase.__file__
3472+
interp = concurrent.interpreters.create()
3473+
self.addCleanup(interp.close)
3474+
queue = concurrent.interpreters.create_queue()
3475+
interp.prepare_main(
3476+
modname=modname,
3477+
filename=filename,
3478+
queue=queue,
3479+
)
3480+
enabled_before = sys._is_gil_enabled()
3481+
interp.exec(f"""if True:
3482+
import sys
3483+
from test.support.warnings_helper import check_warnings
3484+
from {__name__} import import_extension_from_file
3485+
with check_warnings((".*GIL..has been enabled.*", RuntimeWarning),
3486+
quiet=True):
3487+
module = import_extension_from_file(modname, filename,
3488+
put_in_sys_modules=False)
3489+
queue.put(module)
3490+
queue.put(sys._is_gil_enabled())
3491+
""")
3492+
3493+
self.assertIsInstance(queue.get(), str)
3494+
self.assertEqual(queue.get(), True)
3495+
self.assertTrue(queue.empty())
3496+
3497+
self.assertEqual(enabled_before, sys._is_gil_enabled())
3498+
34313499
def test_from_modexport_smoke(self):
34323500
# General positive test for sundry features
34333501
# (PyModule_FromSlotsAndSpec tests exercise these more carefully)
@@ -3456,6 +3524,7 @@ class Sub(tp):
34563524
pass
34573525
self.assertEqual(_testcapi.pytype_getmodulebytoken(Sub, token), module)
34583526

3527+
@requires_gil_enabled("empty slots re-enable GIL")
34593528
def test_from_modexport_empty_slots(self):
34603529
# Module to test that:
34613530
# - no slots are mandatory for PyModExport

Lib/test/test_importlib/util.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
import tempfile
1616
import types
1717

18-
_testsinglephase = import_helper.import_module("_testsinglephase")
18+
# gh-116303: Skip test module dependent tests if test modules are unavailable
19+
import_helper.import_module("_testmultiphase")
1920

2021

2122
BUILTINS = types.SimpleNamespace()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :c:macro:`Py_mod_gil` with API added in :pep:`793`:
2+
:c:func:`!PyModule_FromSlotsAndSpec` and ``PyModExport`` hooks

Modules/_testcapi/module.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ module_from_slots_name(PyObject *self, PyObject *spec)
2727
{
2828
PyModuleDef_Slot slots[] = {
2929
{Py_mod_name, "currently ignored..."},
30+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
31+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
3032
{0},
3133
};
3234
return PyModule_FromSlotsAndSpec(slots, spec);
@@ -37,6 +39,8 @@ module_from_slots_doc(PyObject *self, PyObject *spec)
3739
{
3840
PyModuleDef_Slot slots[] = {
3941
{Py_mod_doc, "the docstring"},
42+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
43+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
4044
{0},
4145
};
4246
return PyModule_FromSlotsAndSpec(slots, spec);
@@ -47,6 +51,8 @@ module_from_slots_size(PyObject *self, PyObject *spec)
4751
{
4852
PyModuleDef_Slot slots[] = {
4953
{Py_mod_state_size, (void*)123},
54+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
55+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
5056
{0},
5157
};
5258
PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
@@ -72,6 +78,8 @@ module_from_slots_methods(PyObject *self, PyObject *spec)
7278
{
7379
PyModuleDef_Slot slots[] = {
7480
{Py_mod_methods, a_methoddef_array},
81+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
82+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
7583
{0},
7684
};
7785
return PyModule_FromSlotsAndSpec(slots, spec);
@@ -90,6 +98,8 @@ module_from_slots_gc(PyObject *self, PyObject *spec)
9098
{Py_mod_state_traverse, noop_traverse},
9199
{Py_mod_state_clear, noop_clear},
92100
{Py_mod_state_free, noop_free},
101+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
102+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
93103
{0},
94104
};
95105
PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
@@ -118,6 +128,8 @@ module_from_slots_token(PyObject *self, PyObject *spec)
118128
{
119129
PyModuleDef_Slot slots[] = {
120130
{Py_mod_token, (void*)&test_token},
131+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
132+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
121133
{0},
122134
};
123135
PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
@@ -144,6 +156,8 @@ module_from_slots_exec(PyObject *self, PyObject *spec)
144156
{
145157
PyModuleDef_Slot slots[] = {
146158
{Py_mod_exec, simple_exec},
159+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
160+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
147161
{0},
148162
};
149163
PyObject *mod = PyModule_FromSlotsAndSpec(slots, spec);
@@ -175,6 +189,8 @@ module_from_slots_create(PyObject *self, PyObject *spec)
175189
{
176190
PyModuleDef_Slot slots[] = {
177191
{Py_mod_create, create_attr_from_spec},
192+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
193+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
178194
{0},
179195
};
180196
return PyModule_FromSlotsAndSpec(slots, spec);
@@ -205,6 +221,8 @@ module_from_slots_repeat_slot(PyObject *self, PyObject *spec)
205221
PyModuleDef_Slot slots[] = {
206222
{slot_id, "anything"},
207223
{slot_id, "anything else"},
224+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
225+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
208226
{0},
209227
};
210228
return PyModule_FromSlotsAndSpec(slots, spec);
@@ -219,6 +237,8 @@ module_from_slots_null_slot(PyObject *self, PyObject *spec)
219237
}
220238
PyModuleDef_Slot slots[] = {
221239
{slot_id, NULL},
240+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
241+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
222242
{0},
223243
};
224244
return PyModule_FromSlotsAndSpec(slots, spec);
@@ -233,6 +253,8 @@ module_from_def_slot(PyObject *self, PyObject *spec)
233253
}
234254
PyModuleDef_Slot slots[] = {
235255
{slot_id, "anything"},
256+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
257+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
236258
{0},
237259
};
238260
PyModuleDef def = {
@@ -280,6 +302,7 @@ module_from_def_multiple_exec(PyObject *self, PyObject *spec)
280302
static PyModuleDef_Slot slots[] = {
281303
{Py_mod_exec, simple_exec},
282304
{Py_mod_exec, another_exec},
305+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
283306
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
284307
{0},
285308
};

Modules/_testmultiphase.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,20 @@ PyModExport__test_from_modexport(void)
10241024
{
10251025
static PyModuleDef_Slot slots[] = {
10261026
{Py_mod_name, "_test_from_modexport"},
1027+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
1028+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
1029+
{0},
1030+
};
1031+
return slots;
1032+
}
1033+
1034+
PyMODEXPORT_FUNC
1035+
PyModExport__test_from_modexport_gil_used(void)
1036+
{
1037+
static PyModuleDef_Slot slots[] = {
1038+
{Py_mod_name, "_test_from_modexport_gil_used"},
1039+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
1040+
{Py_mod_gil, Py_MOD_GIL_USED},
10271041
{0},
10281042
};
10291043
return slots;
@@ -1073,6 +1087,21 @@ PyModExport__test_from_modexport_create_nonmodule(void)
10731087
static PyModuleDef_Slot slots[] = {
10741088
{Py_mod_name, "_test_from_modexport_create_nonmodule"},
10751089
{Py_mod_create, modexport_create_string},
1090+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
1091+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
1092+
{0},
1093+
};
1094+
return slots;
1095+
}
1096+
1097+
PyMODEXPORT_FUNC
1098+
PyModExport__test_from_modexport_create_nonmodule_gil_used(void)
1099+
{
1100+
static PyModuleDef_Slot slots[] = {
1101+
{Py_mod_name, "_test_from_modexport_create_nonmodule"},
1102+
{Py_mod_create, modexport_create_string},
1103+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
1104+
{Py_mod_gil, Py_MOD_GIL_USED},
10761105
{0},
10771106
};
10781107
return slots;
@@ -1165,6 +1194,8 @@ PyModExport__test_from_modexport_smoke(void)
11651194
{Py_mod_methods, methods},
11661195
{Py_mod_state_free, modexport_smoke_free},
11671196
{Py_mod_token, (void*)&modexport_smoke_test_token},
1197+
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
1198+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
11681199
{0},
11691200
};
11701201
return slots;

Objects/moduleobject.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "Python.h"
44
#include "pycore_call.h" // _PyObject_CallNoArgs()
5+
#include "pycore_ceval.h" // _PyEval_EnableGILTransient()
56
#include "pycore_dict.h" // _PyDict_EnablePerThreadRefcounting()
67
#include "pycore_fileutils.h" // _Py_wgetcwd
78
#include "pycore_import.h" // _PyImport_GetNextModuleIndex()
@@ -522,6 +523,22 @@ module_from_def_and_spec(
522523
#undef COPY_COMMON_SLOT
523524
}
524525

526+
#ifdef Py_GIL_DISABLED
527+
// For modules created directly from slots (not from a def), we enable
528+
// the GIL here (pairing `_PyEval_EnableGILTransient` with
529+
// an immediate `_PyImport_EnableGILAndWarn`).
530+
// For modules created from a def, the caller is responsible for this.
531+
if (!original_def && requires_gil) {
532+
PyThreadState *tstate = _PyThreadState_GET();
533+
if (_PyEval_EnableGILTransient(tstate) < 0) {
534+
goto error;
535+
}
536+
if (_PyImport_EnableGILAndWarn(tstate, nameobj) < 0) {
537+
goto error;
538+
}
539+
}
540+
#endif
541+
525542
/* By default, multi-phase init modules are expected
526543
to work under multiple interpreters. */
527544
if (!has_multiple_interpreters_slot) {

0 commit comments

Comments
 (0)