Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-116167: Allow disabling the GIL with PYTHON_GIL=0 or -X gil=0 #116338

Merged
merged 11 commits into from
Mar 11, 2024
3 changes: 3 additions & 0 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ typedef struct PyConfig {
int int_max_str_digits;

int cpu_count;
#ifdef Py_GIL_DISABLED
int enable_gil;
#endif

/* --- Path configuration inputs ------------ */
int pathconfig_warnings;
Expand Down
5 changes: 5 additions & 0 deletions Include/internal/pycore_gil.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ extern "C" {
#define FORCE_SWITCHING

struct _gil_runtime_state {
#ifdef Py_GIL_DISABLED
/* Whether or not this GIL is being used. Can change from 0 to 1 at runtime
if, for example, a module that requires the GIL is loaded. */
int enabled;
#endif
/* microseconds (the Python API uses seconds, though) */
unsigned long interval;
/* Last PyThreadState holding / having held the GIL. This helps us
Expand Down
12 changes: 12 additions & 0 deletions Include/internal/pycore_initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ typedef enum {
_PyConfig_INIT_ISOLATED = 3
} _PyConfigInitEnum;

typedef enum {
/* For now, this means the GIL is enabled.
gh-116329: This will eventually change to "the GIL is disabled but can
be reenabled by loading an incompatible extension module." */
_PyConfig_GIL_DEFAULT,

/* The GIL has been forced off or on, and will not be affected by module loading. */
_PyConfig_GIL_DISABLE,
_PyConfig_GIL_ENABLE,
} _PyConfigGILEnum;

// Export for '_testembed' program
PyAPI_FUNC(void) _PyConfig_InitCompatConfig(PyConfig *config);

Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,38 @@ def test_pythondevmode_env(self):
self.assertEqual(proc.stdout.rstrip(), 'True')
self.assertEqual(proc.returncode, 0, proc)

@unittest.skipUnless(support.Py_GIL_DISABLED,
"PYTHON_GIL only supported in Py_GIL_DISABLED builds")
def test_python_gil_env(self):
code = """if 1:
import _testinternalcapi
print(_testinternalcapi.get_configs()['config'].get('enable_gil'))
"""
args = [sys.executable, '-c', code]
env = dict(os.environ)
env.pop('PYTHON_GIL', None)

def run():
return subprocess.run(args, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True, env=env)

proc = run()
self.assertEqual(proc.returncode, 0, proc)
self.assertEqual(proc.stdout.rstrip(), '0')
self.assertEqual(proc.stderr, '')

env['PYTHON_GIL'] = '0'
proc = run()
self.assertEqual(proc.returncode, 0, proc)
self.assertEqual(proc.stdout.rstrip(), '1')
self.assertEqual(proc.stderr, '')

env['PYTHON_GIL'] = '1'
proc = run()
self.assertEqual(proc.returncode, 0, proc)
self.assertEqual(proc.stdout.rstrip(), '2')
self.assertEqual(proc.stderr, '')

@unittest.skipUnless(sys.platform == 'win32',
'bpo-32457 only applies on Windows')
def test_argv0_normalization(self):
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
CONFIG_COMPAT['_pystats'] = 0
if support.Py_DEBUG:
CONFIG_COMPAT['run_presite'] = None
if support.Py_GIL_DISABLED:
CONFIG_COMPAT['enable_gil'] = 0
if MS_WINDOWS:
CONFIG_COMPAT.update({
'legacy_windows_stdio': 0,
Expand Down
15 changes: 15 additions & 0 deletions Python/ceval_gil.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ drop_gil(PyInterpreterState *interp, PyThreadState *tstate)
// XXX assert(tstate == NULL || !tstate->_status.cleared);

struct _gil_runtime_state *gil = ceval->gil;
#ifdef Py_GIL_DISABLED
if (!gil->enabled) {
return;
}
#endif
if (!_Py_atomic_load_ptr_relaxed(&gil->locked)) {
Py_FatalError("drop_gil: GIL is not locked");
}
Expand Down Expand Up @@ -294,6 +299,11 @@ take_gil(PyThreadState *tstate)
assert(_PyThreadState_CheckConsistency(tstate));
PyInterpreterState *interp = tstate->interp;
struct _gil_runtime_state *gil = interp->ceval.gil;
#ifdef Py_GIL_DISABLED
if (!gil->enabled) {
return;
}
#endif

/* Check that _PyEval_InitThreads() was called to create the lock */
assert(gil_created(gil));
Expand Down Expand Up @@ -438,6 +448,11 @@ static void
init_own_gil(PyInterpreterState *interp, struct _gil_runtime_state *gil)
{
assert(!gil_created(gil));
#ifdef Py_GIL_DISABLED
// gh-116329: Once it is safe to do so, change this condition to
// (enable_gil == _PyConfig_GIL_ENABLE), so the GIL is disabled by default.
gil->enabled = _PyInterpreterState_GetConfig(interp)->enable_gil != _PyConfig_GIL_DISABLE;
#endif
create_gil(gil);
assert(gil_created(gil));
interp->ceval.gil = gil;
Expand Down
24 changes: 24 additions & 0 deletions Python/initconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ static const PyConfigSpec PYCONFIG_SPEC[] = {
SPEC(safe_path, UINT),
SPEC(int_max_str_digits, INT),
SPEC(cpu_count, INT),
#ifdef Py_GIL_DISABLED
SPEC(enable_gil, INT),
#endif
SPEC(pathconfig_warnings, UINT),
SPEC(program_name, WSTR),
SPEC(pythonpath_env, WSTR_OPT),
Expand Down Expand Up @@ -276,6 +279,9 @@ static const char usage_envvars[] =
"PYTHON_CPU_COUNT: Overrides the return value of os.process_cpu_count(),\n"
" os.cpu_count(), and multiprocessing.cpu_count() if set to\n"
" a positive integer.\n"
#ifdef Py_GIL_DISABLED
"PYTHON_GIL : When set to 0, disables the GIL.\n"
#endif
"PYTHONDEVMODE : enable the development mode.\n"
"PYTHONPYCACHEPREFIX: root directory for bytecode cache (pyc) files.\n"
"PYTHONWARNDEFAULTENCODING: enable opt-in EncodingWarning for 'encoding=None'.\n"
Expand Down Expand Up @@ -860,6 +866,9 @@ _PyConfig_InitCompatConfig(PyConfig *config)
config->_is_python_build = 0;
config->code_debug_ranges = 1;
config->cpu_count = -1;
#ifdef Py_GIL_DISABLED
config->enable_gil = _PyConfig_GIL_DEFAULT;
#endif
}


Expand Down Expand Up @@ -1642,6 +1651,21 @@ config_read_env_vars(PyConfig *config)
config->safe_path = 1;
}

const char *gil = config_get_env(config, "PYTHON_GIL");
if (gil != NULL) {
#ifdef Py_GIL_DISABLED
if (strcmp(gil, "0") == 0) {
config->enable_gil = _PyConfig_GIL_DISABLE;
} else if (strcmp(gil, "1") == 0) {
config->enable_gil = _PyConfig_GIL_ENABLE;
} else {
return _PyStatus_ERR("PYTHON_GIL must be \"0\" or \"1\"");
}
colesbury marked this conversation as resolved.
Show resolved Hide resolved
#else
return _PyStatus_ERR("PYTHON_GIL is not supported by this build");
#endif
}

return _PyStatus_OK();
}

Expand Down
Loading