From 971a9a10d539773a3b7cdadbcdc4328b2064649a Mon Sep 17 00:00:00 2001 From: Brett Simmers Date: Thu, 7 Mar 2024 09:18:31 -0800 Subject: [PATCH] Add `-X gil` option, add to `sys.flags`, modify test to cover env var and option --- Doc/using/cmdline.rst | 9 +++++ Include/internal/pycore_initconfig.h | 6 +-- Lib/subprocess.py | 2 +- Lib/test/_test_embed_set_config.py | 9 +++++ Lib/test/test_cmd_line.py | 59 ++++++++++++++-------------- Lib/test/test_embed.py | 2 +- Python/initconfig.c | 43 ++++++++++++++------ Python/sysmodule.c | 11 ++++++ 8 files changed, 95 insertions(+), 46 deletions(-) diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 8df7e35bfb954e..0282cc1c2d4e35 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -559,6 +559,9 @@ Miscellaneous options :mod:`__main__`. This can be used to execute code early during Python initialization. Python needs to be :ref:`built in debug mode ` for this option to exist. See also :envvar:`PYTHON_PRESITE`. + * :samp:`-X gil={0,1}` forces the GIL to be disabled or enabled, + respectively. Only available in builds configured with + :option:`--disable-gil`. See also :envvar:`PYTHON_GIL`. It also allows passing arbitrary values and retrieving them through the :data:`sys._xoptions` dictionary. @@ -601,6 +604,9 @@ Miscellaneous options .. versionchanged:: 3.13 Added the ``-X cpu_count`` and ``-X presite`` options. + .. versionadded:: 3.13 + The ``-X gil`` option. + .. _using-on-controlling-color: Controlling color @@ -1143,6 +1149,9 @@ conflict. If this variable is set to ``1``, the global interpreter lock (GIL) will be forced on. Setting it to ``0`` forces the GIL off. + See also the :option:`-X gil` command-line option, which takes precedence + over this variable. + Needs Python configured with the :option:`--disable-gil` build option. .. versionadded:: 3.13 diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h index 30b83ce5035204..1c68161341860a 100644 --- a/Include/internal/pycore_initconfig.h +++ b/Include/internal/pycore_initconfig.h @@ -158,11 +158,11 @@ typedef enum { gh-116329: This will eventually change to "the GIL is disabled but can be reenabled by loading an incompatible extension module." */ - _PyConfig_GIL_DEFAULT, + _PyConfig_GIL_DEFAULT = -1, /* The GIL has been forced off or on, and will not be affected by module loading. */ - _PyConfig_GIL_DISABLE, - _PyConfig_GIL_ENABLE, + _PyConfig_GIL_DISABLE = 0, + _PyConfig_GIL_ENABLE = 1, } _PyConfigGILEnum; // Export for '_testembed' program diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 20db7747d5db13..1437bf8148282c 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -350,7 +350,7 @@ def _args_from_interpreter_flags(): if dev_mode: args.extend(('-X', 'dev')) for opt in ('faulthandler', 'tracemalloc', 'importtime', - 'frozen_modules', 'showrefcount', 'utf8'): + 'frozen_modules', 'showrefcount', 'utf8', 'gil'): if opt in xoptions: value = xoptions[opt] if value is True: diff --git a/Lib/test/_test_embed_set_config.py b/Lib/test/_test_embed_set_config.py index 75b6b7d1b39fa4..8c818bf1b08e37 100644 --- a/Lib/test/_test_embed_set_config.py +++ b/Lib/test/_test_embed_set_config.py @@ -9,6 +9,7 @@ import os import sys import unittest +from test import support from test.support import MS_WINDOWS @@ -211,6 +212,14 @@ def test_flags(self): self.set_config(use_hash_seed=1, hash_seed=123) self.assertEqual(sys.flags.hash_randomization, 1) + if support.Py_GIL_DISABLED: + self.set_config(enable_gil=-1) + self.assertEqual(sys.flags.gil, None) + self.set_config(enable_gil=0) + self.assertEqual(sys.flags.gil, 0) + self.set_config(enable_gil=1) + self.assertEqual(sys.flags.gil, 1) + def test_options(self): self.check(warnoptions=[]) self.check(warnoptions=["default", "ignore"]) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index f678c6783f8ca0..c633f6493cfab7 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -870,36 +870,37 @@ def test_pythondevmode_env(self): 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, '') + "PYTHON_GIL and -X gil only supported in Py_GIL_DISABLED builds") + def test_python_gil(self): + cases = [ + # (env, opt, expected, msg) + (None, None, 'None', "no options set"), + ('0', None, '0', "PYTHON_GIL=0"), + ('1', None, '1', "PYTHON_GIL=1"), + ('1', '0', '0', "-X gil=0 overrides PYTHON_GIL=1"), + (None, '0', '0', "-X gil=0"), + (None, '1', '1', "-X gil=1"), + ] - env['PYTHON_GIL'] = '1' - proc = run() - self.assertEqual(proc.returncode, 0, proc) - self.assertEqual(proc.stdout.rstrip(), '2') - self.assertEqual(proc.stderr, '') + code = "import sys; print(sys.flags.gil)" + environ = dict(os.environ) + + for env, opt, expected, msg in cases: + with self.subTest(msg, env=env, opt=opt): + environ.pop('PYTHON_GIL', None) + if env is not None: + environ['PYTHON_GIL'] = env + extra_args = [] + if opt is not None: + extra_args = ['-X', f'gil={opt}'] + + proc = subprocess.run([sys.executable, *extra_args, '-c', code], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, env=environ) + self.assertEqual(proc.returncode, 0, proc) + self.assertEqual(proc.stdout.rstrip(), expected) + self.assertEqual(proc.stderr, '') @unittest.skipUnless(sys.platform == 'win32', 'bpo-32457 only applies on Windows') diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index b9f83d69a6d662..ab1d579ed12755 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -524,7 +524,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): if support.Py_DEBUG: CONFIG_COMPAT['run_presite'] = None if support.Py_GIL_DISABLED: - CONFIG_COMPAT['enable_gil'] = 0 + CONFIG_COMPAT['enable_gil'] = -1 if MS_WINDOWS: CONFIG_COMPAT.update({ 'legacy_windows_stdio': 0, diff --git a/Python/initconfig.c b/Python/initconfig.c index dd0eb67ec4e89f..e3a62e53334163 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -1583,6 +1583,24 @@ config_wstr_to_int(const wchar_t *wstr, int *result) return 0; } +static PyStatus +config_read_gil(PyConfig *config, size_t len, wchar_t first_char) +{ +#ifdef Py_GIL_DISABLED + if (len == 1 && first_char == L'0') { + config->enable_gil = _PyConfig_GIL_DISABLE; + } + else if (len == 1 && first_char == L'1') { + config->enable_gil = _PyConfig_GIL_ENABLE; + } + else { + return _PyStatus_ERR("PYTHON_GIL / -X gil must be \"0\" or \"1\""); + } + return _PyStatus_OK(); +#else + return _PyStatus_ERR("PYTHON_GIL / -X gil are not supported by this build"); +#endif +} static PyStatus config_read_env_vars(PyConfig *config) @@ -1663,19 +1681,11 @@ config_read_env_vars(PyConfig *config) 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\""); + size_t len = strlen(gil); + status = config_read_gil(config, len, gil[0]); + if (_PyStatus_EXCEPTION(status)) { + return status; } -#else - return _PyStatus_ERR("PYTHON_GIL is not supported by this build"); -#endif } return _PyStatus_OK(); @@ -2233,6 +2243,15 @@ config_read(PyConfig *config, int compute_path_config) config->show_ref_count = 1; } + const wchar_t *x_gil = config_get_xoption_value(config, L"gil"); + if (x_gil != NULL) { + size_t len = wcslen(x_gil); + status = config_read_gil(config, len, x_gil[0]); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + } + #ifdef Py_STATS if (config_get_xoption(config, L"pystats")) { config->_pystats = 1; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index a4161da02980a7..cd193c1581c679 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -3048,6 +3048,7 @@ static PyStructSequence_Field flags_fields[] = { {"warn_default_encoding", "-X warn_default_encoding"}, {"safe_path", "-P"}, {"int_max_str_digits", "-X int_max_str_digits"}, + {"gil", "-X gil"}, {0} }; @@ -3097,6 +3098,16 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags) SetFlag(config->warn_default_encoding); SetFlagObj(PyBool_FromLong(config->safe_path)); SetFlag(config->int_max_str_digits); +#ifdef Py_GIL_DISABLED + if (config->enable_gil == _PyConfig_GIL_DEFAULT) { + SetFlagObj(Py_NewRef(Py_None)); + } + else { + SetFlag(config->enable_gil); + } +#else + SetFlagObj(PyLong_FromLong(1)); +#endif #undef SetFlagObj #undef SetFlag return 0;