Skip to content

Commit 2731913

Browse files
authored
gh-116167: Allow disabling the GIL with PYTHON_GIL=0 or -X gil=0 (#116338)
In free-threaded builds, running with `PYTHON_GIL=0` will now disable the GIL. Follow-up issues track work to re-enable the GIL when loading an incompatible extension, and to disable the GIL by default. In order to support re-enabling the GIL at runtime, all GIL-related data structures are initialized as usual, and disabling the GIL simply sets a flag that causes `take_gil()` and `drop_gil()` to return early.
1 parent 546eb7a commit 2731913

12 files changed

+163
-1
lines changed

Doc/using/cmdline.rst

+18
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,9 @@ Miscellaneous options
559559
:mod:`__main__`. This can be used to execute code early during Python
560560
initialization. Python needs to be :ref:`built in debug mode <debug-build>`
561561
for this option to exist. See also :envvar:`PYTHON_PRESITE`.
562+
* :samp:`-X gil={0,1}` forces the GIL to be disabled or enabled,
563+
respectively. Only available in builds configured with
564+
:option:`--disable-gil`. See also :envvar:`PYTHON_GIL`.
562565

563566
It also allows passing arbitrary values and retrieving them through the
564567
:data:`sys._xoptions` dictionary.
@@ -601,6 +604,9 @@ Miscellaneous options
601604
.. versionchanged:: 3.13
602605
Added the ``-X cpu_count`` and ``-X presite`` options.
603606

607+
.. versionchanged:: 3.13
608+
Added the ``-X gil`` option.
609+
604610
.. _using-on-controlling-color:
605611

606612
Controlling color
@@ -1138,6 +1144,18 @@ conflict.
11381144

11391145
.. versionadded:: 3.13
11401146

1147+
.. envvar:: PYTHON_GIL
1148+
1149+
If this variable is set to ``1``, the global interpreter lock (GIL) will be
1150+
forced on. Setting it to ``0`` forces the GIL off.
1151+
1152+
See also the :option:`-X gil <-X>` command-line option, which takes
1153+
precedence over this variable.
1154+
1155+
Needs Python configured with the :option:`--disable-gil` build option.
1156+
1157+
.. versionadded:: 3.13
1158+
11411159
Debug-mode variables
11421160
~~~~~~~~~~~~~~~~~~~~
11431161

Include/cpython/initconfig.h

+3
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ typedef struct PyConfig {
181181
int int_max_str_digits;
182182

183183
int cpu_count;
184+
#ifdef Py_GIL_DISABLED
185+
int enable_gil;
186+
#endif
184187

185188
/* --- Path configuration inputs ------------ */
186189
int pathconfig_warnings;

Include/internal/pycore_gil.h

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ extern "C" {
2020
#define FORCE_SWITCHING
2121

2222
struct _gil_runtime_state {
23+
#ifdef Py_GIL_DISABLED
24+
/* Whether or not this GIL is being used. Can change from 0 to 1 at runtime
25+
if, for example, a module that requires the GIL is loaded. */
26+
int enabled;
27+
#endif
2328
/* microseconds (the Python API uses seconds, though) */
2429
unsigned long interval;
2530
/* Last PyThreadState holding / having held the GIL. This helps us

Include/internal/pycore_initconfig.h

+12
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ typedef enum {
153153
_PyConfig_INIT_ISOLATED = 3
154154
} _PyConfigInitEnum;
155155

156+
typedef enum {
157+
/* For now, this means the GIL is enabled.
158+
159+
gh-116329: This will eventually change to "the GIL is disabled but can
160+
be reenabled by loading an incompatible extension module." */
161+
_PyConfig_GIL_DEFAULT = -1,
162+
163+
/* The GIL has been forced off or on, and will not be affected by module loading. */
164+
_PyConfig_GIL_DISABLE = 0,
165+
_PyConfig_GIL_ENABLE = 1,
166+
} _PyConfigGILEnum;
167+
156168
// Export for '_testembed' program
157169
PyAPI_FUNC(void) _PyConfig_InitCompatConfig(PyConfig *config);
158170

Lib/subprocess.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ def _args_from_interpreter_flags():
350350
if dev_mode:
351351
args.extend(('-X', 'dev'))
352352
for opt in ('faulthandler', 'tracemalloc', 'importtime',
353-
'frozen_modules', 'showrefcount', 'utf8'):
353+
'frozen_modules', 'showrefcount', 'utf8', 'gil'):
354354
if opt in xoptions:
355355
value = xoptions[opt]
356356
if value is True:

Lib/test/_test_embed_set_config.py

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os
1010
import sys
1111
import unittest
12+
from test import support
1213
from test.support import MS_WINDOWS
1314

1415

@@ -211,6 +212,19 @@ def test_flags(self):
211212
self.set_config(use_hash_seed=1, hash_seed=123)
212213
self.assertEqual(sys.flags.hash_randomization, 1)
213214

215+
if support.Py_GIL_DISABLED:
216+
self.set_config(enable_gil=-1)
217+
self.assertEqual(sys.flags.gil, None)
218+
self.set_config(enable_gil=0)
219+
self.assertEqual(sys.flags.gil, 0)
220+
self.set_config(enable_gil=1)
221+
self.assertEqual(sys.flags.gil, 1)
222+
else:
223+
# Builds without Py_GIL_DISABLED don't have
224+
# PyConfig.enable_gil. sys.flags.gil is always defined to 1, for
225+
# consistency.
226+
self.assertEqual(sys.flags.gil, 1)
227+
214228
def test_options(self):
215229
self.check(warnoptions=[])
216230
self.check(warnoptions=["default", "ignore"])

Lib/test/test_cmd_line.py

+33
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,39 @@ def test_pythondevmode_env(self):
869869
self.assertEqual(proc.stdout.rstrip(), 'True')
870870
self.assertEqual(proc.returncode, 0, proc)
871871

872+
@unittest.skipUnless(support.Py_GIL_DISABLED,
873+
"PYTHON_GIL and -X gil only supported in Py_GIL_DISABLED builds")
874+
def test_python_gil(self):
875+
cases = [
876+
# (env, opt, expected, msg)
877+
(None, None, 'None', "no options set"),
878+
('0', None, '0', "PYTHON_GIL=0"),
879+
('1', None, '1', "PYTHON_GIL=1"),
880+
('1', '0', '0', "-X gil=0 overrides PYTHON_GIL=1"),
881+
(None, '0', '0', "-X gil=0"),
882+
(None, '1', '1', "-X gil=1"),
883+
]
884+
885+
code = "import sys; print(sys.flags.gil)"
886+
environ = dict(os.environ)
887+
888+
for env, opt, expected, msg in cases:
889+
with self.subTest(msg, env=env, opt=opt):
890+
environ.pop('PYTHON_GIL', None)
891+
if env is not None:
892+
environ['PYTHON_GIL'] = env
893+
extra_args = []
894+
if opt is not None:
895+
extra_args = ['-X', f'gil={opt}']
896+
897+
proc = subprocess.run([sys.executable, *extra_args, '-c', code],
898+
stdout=subprocess.PIPE,
899+
stderr=subprocess.PIPE,
900+
text=True, env=environ)
901+
self.assertEqual(proc.returncode, 0, proc)
902+
self.assertEqual(proc.stdout.rstrip(), expected)
903+
self.assertEqual(proc.stderr, '')
904+
872905
@unittest.skipUnless(sys.platform == 'win32',
873906
'bpo-32457 only applies on Windows')
874907
def test_argv0_normalization(self):

Lib/test/test_embed.py

+2
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
523523
CONFIG_COMPAT['_pystats'] = 0
524524
if support.Py_DEBUG:
525525
CONFIG_COMPAT['run_presite'] = None
526+
if support.Py_GIL_DISABLED:
527+
CONFIG_COMPAT['enable_gil'] = -1
526528
if MS_WINDOWS:
527529
CONFIG_COMPAT.update({
528530
'legacy_windows_stdio': 0,

Misc/python.man

+4
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,10 @@ output. Setting it to 0 deactivates this behavior.
607607
.IP PYTHON_HISTORY
608608
This environment variable can be used to set the location of a history file
609609
(on Unix, it is \fI~/.python_history\fP by default).
610+
.IP PYTHON_GIL
611+
If this variable is set to 1, the global interpreter lock (GIL) will be forced
612+
on. Setting it to 0 forces the GIL off. Only available in builds configured
613+
with \fB--disable-gil\fP.
610614
.SS Debug-mode variables
611615
Setting these variables only has an effect in a debug build of Python, that is,
612616
if Python was configured with the

Python/ceval_gil.c

+15
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ drop_gil(PyInterpreterState *interp, PyThreadState *tstate)
219219
// XXX assert(tstate == NULL || !tstate->_status.cleared);
220220

221221
struct _gil_runtime_state *gil = ceval->gil;
222+
#ifdef Py_GIL_DISABLED
223+
if (!gil->enabled) {
224+
return;
225+
}
226+
#endif
222227
if (!_Py_atomic_load_ptr_relaxed(&gil->locked)) {
223228
Py_FatalError("drop_gil: GIL is not locked");
224229
}
@@ -294,6 +299,11 @@ take_gil(PyThreadState *tstate)
294299
assert(_PyThreadState_CheckConsistency(tstate));
295300
PyInterpreterState *interp = tstate->interp;
296301
struct _gil_runtime_state *gil = interp->ceval.gil;
302+
#ifdef Py_GIL_DISABLED
303+
if (!gil->enabled) {
304+
return;
305+
}
306+
#endif
297307

298308
/* Check that _PyEval_InitThreads() was called to create the lock */
299309
assert(gil_created(gil));
@@ -440,6 +450,11 @@ static void
440450
init_own_gil(PyInterpreterState *interp, struct _gil_runtime_state *gil)
441451
{
442452
assert(!gil_created(gil));
453+
#ifdef Py_GIL_DISABLED
454+
// gh-116329: Once it is safe to do so, change this condition to
455+
// (enable_gil == _PyConfig_GIL_ENABLE), so the GIL is disabled by default.
456+
gil->enabled = _PyInterpreterState_GetConfig(interp)->enable_gil != _PyConfig_GIL_DISABLE;
457+
#endif
443458
create_gil(gil);
444459
assert(gil_created(gil));
445460
interp->ceval.gil = gil;

Python/initconfig.c

+45
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ static const PyConfigSpec PYCONFIG_SPEC[] = {
9595
SPEC(safe_path, BOOL),
9696
SPEC(int_max_str_digits, INT),
9797
SPEC(cpu_count, INT),
98+
#ifdef Py_GIL_DISABLED
99+
SPEC(enable_gil, INT),
100+
#endif
98101
SPEC(pathconfig_warnings, BOOL),
99102
SPEC(program_name, WSTR),
100103
SPEC(pythonpath_env, WSTR_OPT),
@@ -278,6 +281,9 @@ static const char usage_envvars[] =
278281
"PYTHON_CPU_COUNT: Overrides the return value of os.process_cpu_count(),\n"
279282
" os.cpu_count(), and multiprocessing.cpu_count() if set to\n"
280283
" a positive integer.\n"
284+
#ifdef Py_GIL_DISABLED
285+
"PYTHON_GIL : When set to 0, disables the GIL.\n"
286+
#endif
281287
"PYTHONDEVMODE : enable the development mode.\n"
282288
"PYTHONPYCACHEPREFIX: root directory for bytecode cache (pyc) files.\n"
283289
"PYTHONWARNDEFAULTENCODING: enable opt-in EncodingWarning for 'encoding=None'.\n"
@@ -862,6 +868,9 @@ _PyConfig_InitCompatConfig(PyConfig *config)
862868
config->_is_python_build = 0;
863869
config->code_debug_ranges = 1;
864870
config->cpu_count = -1;
871+
#ifdef Py_GIL_DISABLED
872+
config->enable_gil = _PyConfig_GIL_DEFAULT;
873+
#endif
865874
}
866875

867876

@@ -1574,6 +1583,24 @@ config_wstr_to_int(const wchar_t *wstr, int *result)
15741583
return 0;
15751584
}
15761585

1586+
static PyStatus
1587+
config_read_gil(PyConfig *config, size_t len, wchar_t first_char)
1588+
{
1589+
#ifdef Py_GIL_DISABLED
1590+
if (len == 1 && first_char == L'0') {
1591+
config->enable_gil = _PyConfig_GIL_DISABLE;
1592+
}
1593+
else if (len == 1 && first_char == L'1') {
1594+
config->enable_gil = _PyConfig_GIL_ENABLE;
1595+
}
1596+
else {
1597+
return _PyStatus_ERR("PYTHON_GIL / -X gil must be \"0\" or \"1\"");
1598+
}
1599+
return _PyStatus_OK();
1600+
#else
1601+
return _PyStatus_ERR("PYTHON_GIL / -X gil are not supported by this build");
1602+
#endif
1603+
}
15771604

15781605
static PyStatus
15791606
config_read_env_vars(PyConfig *config)
@@ -1652,6 +1679,15 @@ config_read_env_vars(PyConfig *config)
16521679
config->safe_path = 1;
16531680
}
16541681

1682+
const char *gil = config_get_env(config, "PYTHON_GIL");
1683+
if (gil != NULL) {
1684+
size_t len = strlen(gil);
1685+
status = config_read_gil(config, len, gil[0]);
1686+
if (_PyStatus_EXCEPTION(status)) {
1687+
return status;
1688+
}
1689+
}
1690+
16551691
return _PyStatus_OK();
16561692
}
16571693

@@ -2207,6 +2243,15 @@ config_read(PyConfig *config, int compute_path_config)
22072243
config->show_ref_count = 1;
22082244
}
22092245

2246+
const wchar_t *x_gil = config_get_xoption_value(config, L"gil");
2247+
if (x_gil != NULL) {
2248+
size_t len = wcslen(x_gil);
2249+
status = config_read_gil(config, len, x_gil[0]);
2250+
if (_PyStatus_EXCEPTION(status)) {
2251+
return status;
2252+
}
2253+
}
2254+
22102255
#ifdef Py_STATS
22112256
if (config_get_xoption(config, L"pystats")) {
22122257
config->_pystats = 1;

Python/sysmodule.c

+11
Original file line numberDiff line numberDiff line change
@@ -3048,6 +3048,7 @@ static PyStructSequence_Field flags_fields[] = {
30483048
{"warn_default_encoding", "-X warn_default_encoding"},
30493049
{"safe_path", "-P"},
30503050
{"int_max_str_digits", "-X int_max_str_digits"},
3051+
{"gil", "-X gil"},
30513052
{0}
30523053
};
30533054

@@ -3097,6 +3098,16 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags)
30973098
SetFlag(config->warn_default_encoding);
30983099
SetFlagObj(PyBool_FromLong(config->safe_path));
30993100
SetFlag(config->int_max_str_digits);
3101+
#ifdef Py_GIL_DISABLED
3102+
if (config->enable_gil == _PyConfig_GIL_DEFAULT) {
3103+
SetFlagObj(Py_NewRef(Py_None));
3104+
}
3105+
else {
3106+
SetFlag(config->enable_gil);
3107+
}
3108+
#else
3109+
SetFlagObj(PyLong_FromLong(1));
3110+
#endif
31003111
#undef SetFlagObj
31013112
#undef SetFlag
31023113
return 0;

0 commit comments

Comments
 (0)