Skip to content

Commit c2627d6

Browse files
authored
gh-116322: Add Py_mod_gil module slot (#116882)
This PR adds the ability to enable the GIL if it was disabled at interpreter startup, and modifies the multi-phase module initialization path to enable the GIL when loading a module, unless that module's spec includes a slot indicating it can run safely without the GIL. PEP 703 called the constant for the slot `Py_mod_gil_not_used`; I went with `Py_MOD_GIL_NOT_USED` for consistency with gh-104148. A warning will be issued up to once per interpreter for the first GIL-using module that is loaded. If `-v` is given, a shorter message will be printed to stderr every time a GIL-using module is loaded (including the first one that issues a warning).
1 parent 3e818af commit c2627d6

File tree

123 files changed

+376
-62
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

123 files changed

+376
-62
lines changed

Doc/c-api/module.rst

+38
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,31 @@ The available slot types are:
411411
412412
.. versionadded:: 3.12
413413
414+
.. c:macro:: Py_mod_gil
415+
416+
Specifies one of the following values:
417+
418+
.. c:macro:: Py_MOD_GIL_USED
419+
420+
The module depends on the presence of the global interpreter lock (GIL),
421+
and may access global state without synchronization.
422+
423+
.. c:macro:: Py_MOD_GIL_NOT_USED
424+
425+
The module is safe to run without an active GIL.
426+
427+
This slot is ignored by Python builds not configured with
428+
:option:`--disable-gil`. Otherwise, it determines whether or not importing
429+
this module will cause the GIL to be automatically enabled. See
430+
:envvar:`PYTHON_GIL` and :option:`-X gil <-X>` for more detail.
431+
432+
Multiple ``Py_mod_gil`` slots may not be specified in one module definition.
433+
434+
If ``Py_mod_gil`` is not specified, the import machinery defaults to
435+
``Py_MOD_GIL_USED``.
436+
437+
.. versionadded: 3.13
438+
414439
See :PEP:`489` for more details on multi-phase initialization.
415440
416441
Low-level module creation functions
@@ -609,6 +634,19 @@ state:
609634
610635
.. versionadded:: 3.9
611636
637+
.. c:function:: int PyModule_ExperimentalSetGIL(PyObject *module, void *gil)
638+
639+
Indicate that *module* does or does not support running without the global
640+
interpreter lock (GIL), using one of the values from
641+
:c:macro:`Py_mod_gil`. It must be called during *module*'s initialization
642+
function. If this function is not called during module initialization, the
643+
import machinery assumes the module does not support running without the
644+
GIL. This function is only available in Python builds configured with
645+
:option:`--disable-gil`.
646+
Return ``-1`` on error, ``0`` on success.
647+
648+
.. versionadded:: 3.13
649+
612650
613651
Module lookup
614652
^^^^^^^^^^^^^

Include/internal/pycore_moduleobject.h

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ typedef struct {
2222
PyObject *md_weaklist;
2323
// for logging purposes after md_dict is cleared
2424
PyObject *md_name;
25+
#ifdef Py_GIL_DISABLED
26+
void *md_gil;
27+
#endif
2528
} PyModuleObject;
2629

2730
static inline PyModuleDef* _PyModule_GetDef(PyObject *mod) {

Include/moduleobject.h

+15-1
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,13 @@ struct PyModuleDef_Slot {
7676
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030c0000
7777
# define Py_mod_multiple_interpreters 3
7878
#endif
79+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
80+
# define Py_mod_gil 4
81+
#endif
82+
7983

8084
#ifndef Py_LIMITED_API
81-
#define _Py_mod_LAST_SLOT 3
85+
#define _Py_mod_LAST_SLOT 4
8286
#endif
8387

8488
#endif /* New in 3.5 */
@@ -90,6 +94,16 @@ struct PyModuleDef_Slot {
9094
# define Py_MOD_PER_INTERPRETER_GIL_SUPPORTED ((void *)2)
9195
#endif
9296

97+
/* for Py_mod_gil: */
98+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
99+
# define Py_MOD_GIL_USED ((void *)0)
100+
# define Py_MOD_GIL_NOT_USED ((void *)1)
101+
#endif
102+
103+
#if !defined(Py_LIMITED_API) && defined(Py_GIL_DISABLED)
104+
PyAPI_FUNC(int) PyModule_ExperimentalSetGIL(PyObject *module, void *gil);
105+
#endif
106+
93107
struct PyModuleDef {
94108
PyModuleDef_Base m_base;
95109
const char* m_name;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import types
2+
import unittest
3+
from test.test_importlib import util
4+
5+
machinery = util.import_importlib('importlib.machinery')
6+
7+
from test.test_importlib.extension.test_loader import MultiPhaseExtensionModuleTests
8+
9+
10+
class NonModuleExtensionTests:
11+
setUp = MultiPhaseExtensionModuleTests.setUp
12+
load_module_by_name = MultiPhaseExtensionModuleTests.load_module_by_name
13+
14+
def _test_nonmodule(self):
15+
# Test returning a non-module object from create works.
16+
name = self.name + '_nonmodule'
17+
mod = self.load_module_by_name(name)
18+
self.assertNotEqual(type(mod), type(unittest))
19+
self.assertEqual(mod.three, 3)
20+
21+
# issue 27782
22+
def test_nonmodule_with_methods(self):
23+
# Test creating a non-module object with methods defined.
24+
name = self.name + '_nonmodule_with_methods'
25+
mod = self.load_module_by_name(name)
26+
self.assertNotEqual(type(mod), type(unittest))
27+
self.assertEqual(mod.three, 3)
28+
self.assertEqual(mod.bar(10, 1), 9)
29+
30+
def test_null_slots(self):
31+
# Test that NULL slots aren't a problem.
32+
name = self.name + '_null_slots'
33+
module = self.load_module_by_name(name)
34+
self.assertIsInstance(module, types.ModuleType)
35+
self.assertEqual(module.__name__, name)
36+
37+
38+
(Frozen_NonModuleExtensionTests,
39+
Source_NonModuleExtensionTests
40+
) = util.test_both(NonModuleExtensionTests, machinery=machinery)
41+
42+
43+
if __name__ == '__main__':
44+
unittest.main()

Lib/test/test_importlib/extension/test_loader.py

+11-24
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
import warnings
1111
import importlib.util
1212
import importlib
13-
from test.support import MISSING_C_DOCSTRINGS
13+
from test import support
14+
from test.support import MISSING_C_DOCSTRINGS, script_helper
1415

1516

1617
class LoaderTests:
@@ -325,29 +326,6 @@ def test_unloadable_nonascii(self):
325326
self.load_module_by_name(name)
326327
self.assertEqual(cm.exception.name, name)
327328

328-
def test_nonmodule(self):
329-
# Test returning a non-module object from create works.
330-
name = self.name + '_nonmodule'
331-
mod = self.load_module_by_name(name)
332-
self.assertNotEqual(type(mod), type(unittest))
333-
self.assertEqual(mod.three, 3)
334-
335-
# issue 27782
336-
def test_nonmodule_with_methods(self):
337-
# Test creating a non-module object with methods defined.
338-
name = self.name + '_nonmodule_with_methods'
339-
mod = self.load_module_by_name(name)
340-
self.assertNotEqual(type(mod), type(unittest))
341-
self.assertEqual(mod.three, 3)
342-
self.assertEqual(mod.bar(10, 1), 9)
343-
344-
def test_null_slots(self):
345-
# Test that NULL slots aren't a problem.
346-
name = self.name + '_null_slots'
347-
module = self.load_module_by_name(name)
348-
self.assertIsInstance(module, types.ModuleType)
349-
self.assertEqual(module.__name__, name)
350-
351329
def test_bad_modules(self):
352330
# Test SystemError is raised for misbehaving extensions.
353331
for name_base in [
@@ -401,5 +379,14 @@ def test_nonascii(self):
401379
) = util.test_both(MultiPhaseExtensionModuleTests, machinery=machinery)
402380

403381

382+
class NonModuleExtensionTests(unittest.TestCase):
383+
def test_nonmodule_cases(self):
384+
# The test cases in this file cause the GIL to be enabled permanently
385+
# in free-threaded builds, so they are run in a subprocess to isolate
386+
# this effect.
387+
script = support.findfile("test_importlib/extension/_test_nonmodule_cases.py")
388+
script_helper.run_test_script(script)
389+
390+
404391
if __name__ == '__main__':
405392
unittest.main()

Lib/test/test_sys.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1606,7 +1606,10 @@ def get_gen(): yield 1
16061606
check(int(PyLong_BASE**2-1), vsize('') + 2*self.longdigit)
16071607
check(int(PyLong_BASE**2), vsize('') + 3*self.longdigit)
16081608
# module
1609-
check(unittest, size('PnPPP'))
1609+
if support.Py_GIL_DISABLED:
1610+
check(unittest, size('PPPPPP'))
1611+
else:
1612+
check(unittest, size('PPPPP'))
16101613
# None
16111614
check(None, size(''))
16121615
# NotImplementedType
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Extension modules may indicate to the runtime that they can run without the
2+
GIL. Multi-phase init modules do so by calling providing
3+
``Py_MOD_GIL_NOT_USED`` for the ``Py_mod_gil`` slot, while single-phase init
4+
modules call ``PyModule_ExperimentalSetGIL(mod, Py_MOD_GIL_NOT_USED)`` from
5+
their init function.

Modules/_abc.c

+1
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,7 @@ _abcmodule_free(void *module)
970970
static PyModuleDef_Slot _abcmodule_slots[] = {
971971
{Py_mod_exec, _abcmodule_exec},
972972
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
973+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
973974
{0, NULL}
974975
};
975976

Modules/_asynciomodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -3795,6 +3795,7 @@ module_exec(PyObject *mod)
37953795
static struct PyModuleDef_Slot module_slots[] = {
37963796
{Py_mod_exec, module_exec},
37973797
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
3798+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
37983799
{0, NULL},
37993800
};
38003801

Modules/_bisectmodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ bisect_modexec(PyObject *m)
462462
static PyModuleDef_Slot bisect_slots[] = {
463463
{Py_mod_exec, bisect_modexec},
464464
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
465+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
465466
{0, NULL}
466467
};
467468

Modules/_blake2/blake2module.c

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ blake2_exec(PyObject *m)
137137
static PyModuleDef_Slot _blake2_slots[] = {
138138
{Py_mod_exec, blake2_exec},
139139
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
140+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
140141
{0, NULL}
141142
};
142143

Modules/_bz2module.c

+1
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ _bz2_free(void *module)
802802
static struct PyModuleDef_Slot _bz2_slots[] = {
803803
{Py_mod_exec, _bz2_exec},
804804
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
805+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
805806
{0, NULL}
806807
};
807808

Modules/_codecsmodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,7 @@ static PyMethodDef _codecs_functions[] = {
10501050

10511051
static PyModuleDef_Slot _codecs_slots[] = {
10521052
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
1053+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
10531054
{0, NULL}
10541055
};
10551056

Modules/_collectionsmodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -2817,6 +2817,7 @@ collections_exec(PyObject *module) {
28172817
static struct PyModuleDef_Slot collections_slots[] = {
28182818
{Py_mod_exec, collections_exec},
28192819
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
2820+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
28202821
{0, NULL}
28212822
};
28222823

Modules/_contextvarsmodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ _contextvars_exec(PyObject *m)
4545
static struct PyModuleDef_Slot _contextvars_slots[] = {
4646
{Py_mod_exec, _contextvars_exec},
4747
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
48+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
4849
{0, NULL}
4950
};
5051

Modules/_csv.c

+1
Original file line numberDiff line numberDiff line change
@@ -1796,6 +1796,7 @@ csv_exec(PyObject *module) {
17961796
static PyModuleDef_Slot csv_slots[] = {
17971797
{Py_mod_exec, csv_exec},
17981798
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
1799+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
17991800
{0, NULL}
18001801
};
18011802

Modules/_ctypes/_ctypes.c

+1
Original file line numberDiff line numberDiff line change
@@ -5948,6 +5948,7 @@ module_free(void *module)
59485948
static PyModuleDef_Slot module_slots[] = {
59495949
{Py_mod_exec, _ctypes_mod_exec},
59505950
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
5951+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
59515952
{0, NULL}
59525953
};
59535954

Modules/_ctypes/_ctypes_test.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
1+
// Need limited C API version 3.13 for Py_mod_gil
22
#include "pyconfig.h" // Py_GIL_DISABLED
33
#ifndef Py_GIL_DISABLED
4-
# define Py_LIMITED_API 0x030c0000
4+
# define Py_LIMITED_API 0x030d0000
55
#endif
66

77
// gh-85283: On Windows, Py_LIMITED_API requires Py_BUILD_CORE to not attempt
@@ -1167,6 +1167,7 @@ _testfunc_pylist_append(PyObject *list, PyObject *item)
11671167

11681168
static struct PyModuleDef_Slot _ctypes_test_slots[] = {
11691169
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
1170+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
11701171
{0, NULL}
11711172
};
11721173

Modules/_curses_panel.c

+1
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@ static PyModuleDef_Slot _curses_slots[] = {
697697
// XXX gh-103092: fix isolation.
698698
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
699699
//{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
700+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
700701
{0, NULL}
701702
};
702703

Modules/_cursesmodule.c

+3
Original file line numberDiff line numberDiff line change
@@ -4743,6 +4743,9 @@ PyInit__curses(void)
47434743
m = PyModule_Create(&_cursesmodule);
47444744
if (m == NULL)
47454745
return NULL;
4746+
#ifdef Py_GIL_DISABLED
4747+
PyModule_ExperimentalSetGIL(m, Py_MOD_GIL_NOT_USED);
4748+
#endif
47464749

47474750
/* Add some symbolic constants to the module */
47484751
d = PyModule_GetDict(m);

Modules/_datetimemodule.c

+3
Original file line numberDiff line numberDiff line change
@@ -6984,6 +6984,9 @@ PyInit__datetime(void)
69846984
PyObject *mod = PyModule_Create(&datetimemodule);
69856985
if (mod == NULL)
69866986
return NULL;
6987+
#ifdef Py_GIL_DISABLED
6988+
PyModule_ExperimentalSetGIL(mod, Py_MOD_GIL_NOT_USED);
6989+
#endif
69876990

69886991
if (_datetime_exec(mod) < 0) {
69896992
Py_DECREF(mod);

Modules/_dbmmodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ _dbm_module_free(void *module)
616616
static PyModuleDef_Slot _dbmmodule_slots[] = {
617617
{Py_mod_exec, _dbm_exec},
618618
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
619+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
619620
{0, NULL}
620621
};
621622

Modules/_decimal/_decimal.c

+1
Original file line numberDiff line numberDiff line change
@@ -6157,6 +6157,7 @@ decimal_free(void *module)
61576157
static struct PyModuleDef_Slot _decimal_slots[] = {
61586158
{Py_mod_exec, _decimal_exec},
61596159
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
6160+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
61606161
{0, NULL},
61616162
};
61626163

Modules/_elementtree.c

+1
Original file line numberDiff line numberDiff line change
@@ -4463,6 +4463,7 @@ module_exec(PyObject *m)
44634463
static struct PyModuleDef_Slot elementtree_slots[] = {
44644464
{Py_mod_exec, module_exec},
44654465
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
4466+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
44664467
{0, NULL},
44674468
};
44684469

Modules/_functoolsmodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,7 @@ _functools_free(void *module)
15591559
static struct PyModuleDef_Slot _functools_slots[] = {
15601560
{Py_mod_exec, _functools_exec},
15611561
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
1562+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
15621563
{0, NULL}
15631564
};
15641565

Modules/_gdbmmodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,7 @@ _gdbm_module_free(void *module)
825825
static PyModuleDef_Slot _gdbm_module_slots[] = {
826826
{Py_mod_exec, _gdbm_exec},
827827
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
828+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
828829
{0, NULL}
829830
};
830831

Modules/_hashopenssl.c

+1
Original file line numberDiff line numberDiff line change
@@ -2289,6 +2289,7 @@ static PyModuleDef_Slot hashlib_slots[] = {
22892289
{Py_mod_exec, hashlib_init_constructors},
22902290
{Py_mod_exec, hashlib_exception},
22912291
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
2292+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
22922293
{0, NULL}
22932294
};
22942295

Modules/_heapqmodule.c

+1
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ heapq_exec(PyObject *m)
681681
static struct PyModuleDef_Slot heapq_slots[] = {
682682
{Py_mod_exec, heapq_exec},
683683
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
684+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
684685
{0, NULL}
685686
};
686687

0 commit comments

Comments
 (0)