From ba99f2e455f29a5b3bd2f12f85e0b8985ec49bb2 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Sat, 28 Dec 2024 13:00:59 -0800 Subject: [PATCH 1/8] Make _contextvars a builtin module. --- Makefile.pre.in | 1 + Modules/Setup | 1 - Modules/Setup.stdlib.in | 1 - Modules/config.c.in | 4 ++++ PCbuild/pythoncore.vcxproj | 2 +- PCbuild/pythoncore.vcxproj.filters | 6 +++--- Modules/_contextvarsmodule.c => Python/_contextvars.c | 2 +- .../clinic/_contextvars.c.h | 0 configure.ac | 1 - 9 files changed, 10 insertions(+), 8 deletions(-) rename Modules/_contextvarsmodule.c => Python/_contextvars.c (97%) rename Modules/clinic/_contextvarsmodule.c.h => Python/clinic/_contextvars.c.h (100%) diff --git a/Makefile.pre.in b/Makefile.pre.in index 67acf0fc520087..18484a42abb6e8 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -420,6 +420,7 @@ PARSER_HEADERS= \ # Python PYTHON_OBJS= \ + Python/_contextvars.o \ Python/_warnings.o \ Python/Python-ast.o \ Python/Python-tokenize.o \ diff --git a/Modules/Setup b/Modules/Setup index ddf39e0b966610..e01c7bb1a8a45e 100644 --- a/Modules/Setup +++ b/Modules/Setup @@ -132,7 +132,6 @@ PYTHONPATH=$(COREPYTHONPATH) #_asyncio _asynciomodule.c #_bisect _bisectmodule.c -#_contextvars _contextvarsmodule.c #_csv _csv.c #_datetime _datetimemodule.c #_decimal _decimal/_decimal.c diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 6bb05a06a3465d..174e8339083f7a 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -31,7 +31,6 @@ @MODULE_ARRAY_TRUE@array arraymodule.c @MODULE__ASYNCIO_TRUE@_asyncio _asynciomodule.c @MODULE__BISECT_TRUE@_bisect _bisectmodule.c -@MODULE__CONTEXTVARS_TRUE@_contextvars _contextvarsmodule.c @MODULE__CSV_TRUE@_csv _csv.c @MODULE__HEAPQ_TRUE@_heapq _heapqmodule.c @MODULE__JSON_TRUE@_json _json.c diff --git a/Modules/config.c.in b/Modules/config.c.in index c578cd103dc629..704f58506048a3 100644 --- a/Modules/config.c.in +++ b/Modules/config.c.in @@ -19,6 +19,7 @@ extern PyObject* PyInit__imp(void); extern PyObject* PyInit_gc(void); extern PyObject* PyInit__ast(void); extern PyObject* PyInit__tokenize(void); +extern PyObject* PyInit__contextvars(void); extern PyObject* _PyWarnings_Init(void); extern PyObject* PyInit__string(void); @@ -45,6 +46,9 @@ struct _inittab _PyImport_Inittab[] = { /* This lives in gcmodule.c */ {"gc", PyInit_gc}, + /* This lives in Python/_contextvars.c */ + {"_contextvars", PyInit__contextvars}, + /* This lives in _warnings.c */ {"_warnings", _PyWarnings_Init}, diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 9ebf58ae8a9bc4..ef6dbf9f8e4222 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -423,7 +423,6 @@ - @@ -570,6 +569,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 6c76a6ab592a84..b661aad2019454 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -1262,6 +1262,9 @@ PC + + Python + Python @@ -1526,9 +1529,6 @@ Objects - - Modules - Modules\zlib diff --git a/Modules/_contextvarsmodule.c b/Python/_contextvars.c similarity index 97% rename from Modules/_contextvarsmodule.c rename to Python/_contextvars.c index 3f96f07909b69a..0f8b8004c1af22 100644 --- a/Modules/_contextvarsmodule.c +++ b/Python/_contextvars.c @@ -1,6 +1,6 @@ #include "Python.h" -#include "clinic/_contextvarsmodule.c.h" +#include "clinic/_contextvars.c.h" /*[clinic input] module _contextvars diff --git a/Modules/clinic/_contextvarsmodule.c.h b/Python/clinic/_contextvars.c.h similarity index 100% rename from Modules/clinic/_contextvarsmodule.c.h rename to Python/clinic/_contextvars.c.h diff --git a/configure.ac b/configure.ac index cf16e77f0a1503..ce10d518f24c6c 100644 --- a/configure.ac +++ b/configure.ac @@ -7775,7 +7775,6 @@ dnl always enabled extension modules PY_STDLIB_MOD_SIMPLE([array]) PY_STDLIB_MOD_SIMPLE([_asyncio]) PY_STDLIB_MOD_SIMPLE([_bisect]) -PY_STDLIB_MOD_SIMPLE([_contextvars]) PY_STDLIB_MOD_SIMPLE([_csv]) PY_STDLIB_MOD_SIMPLE([_heapq]) PY_STDLIB_MOD_SIMPLE([_json]) From 6d00c2aeec9a0e222d8df6383acde2f707f33bf6 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Fri, 20 Dec 2024 16:23:16 -0800 Subject: [PATCH 2/8] Add 'context' parameter to Thread. * Add ``sys.flags.inherit_context``. * Add ``-X inherit_context`` and :envvar:`PYTHON_INHERIT_CONTEXT` --- Doc/library/sys.rst | 15 ++++- Doc/library/threading.rst | 16 +++++- Doc/using/cmdline.rst | 19 +++++++ Include/cpython/initconfig.h | 1 + Lib/test/test_capi/test_config.py | 10 +++- Lib/test/test_context.py | 55 +++++++++++++++++++ Lib/test/test_decimal.py | 7 ++- Lib/test/test_embed.py | 7 ++- Lib/test/test_sys.py | 5 +- Lib/threading.py | 24 +++++++- ...-01-06-10-55-41.gh-issue-128555.tAK_AY.rst | 16 ++++++ Python/initconfig.c | 51 +++++++++++++++++ Python/sysmodule.c | 2 + 13 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 855237e0984972..43bda09323f557 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -535,7 +535,8 @@ always available. Unless explicitly noted otherwise, all variables are read-only .. data:: flags The :term:`named tuple` *flags* exposes the status of command line - flags. The attributes are read only. + flags. Flags should only be accessed only by name and not by index. The + attributes are read only. .. list-table:: @@ -594,6 +595,12 @@ always available. Unless explicitly noted otherwise, all variables are read-only * - .. attribute:: flags.warn_default_encoding - :option:`-X warn_default_encoding <-X>` + * - .. attribute:: flags.gil + - :option:`-X gil <-X>` and :envvar:`PYTHON_GIL` + + * - .. attribute:: flags.inherit_context + - :option:`-X inherit_context <-X>` and :envvar:`PYTHON_INHERIT_CONTEXT` + .. versionchanged:: 3.2 Added ``quiet`` attribute for the new :option:`-q` flag. @@ -620,6 +627,12 @@ always available. Unless explicitly noted otherwise, all variables are read-only .. versionchanged:: 3.11 Added the ``int_max_str_digits`` attribute. + .. versionchanged:: 3.13 + Added the ``gil`` attribute. + + .. versionchanged:: 3.14 + Added the ``inherit_context`` attribute. + .. data:: float_info diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 00511df32e4388..65e498aabb8016 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -334,7 +334,7 @@ since it is impossible to detect the termination of alien threads. .. class:: Thread(group=None, target=None, name=None, args=(), kwargs={}, *, \ - daemon=None) + daemon=None, context=None) This constructor should always be called with keyword arguments. Arguments are: @@ -359,6 +359,17 @@ since it is impossible to detect the termination of alien threads. If ``None`` (the default), the daemonic property is inherited from the current thread. + *context* is the :class:`~contextvars.Context` value to use when starting + the thread. The default value is ``None`` which indicates that the + :data:`sys.flags.inherit_context` flag controls the behaviour. If + the flag is true, threads will start with a copy of the context of the + caller of :meth:`~Thread.start`. If false, they will start with + an empty context. To explicitly start with an empty context, + pass a new instance of :class:`~contextvars.Context()`. To explicitly + start with a copy of the current context, pass the value from + :func:`~contextvars.copy_context()`. The flag defaults true on + free-threaded builds and false otherwise. + If the subclass overrides the constructor, it must make sure to invoke the base class constructor (``Thread.__init__()``) before doing anything else to the thread. @@ -369,6 +380,9 @@ since it is impossible to detect the termination of alien threads. .. versionchanged:: 3.10 Use the *target* name if *name* argument is omitted. + .. versionchanged:: 3.14 + Added the *context* parameter. + .. method:: start() Start the thread's activity. diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 2a59cf3f62d4c5..be5816f7c8d2b0 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -628,6 +628,15 @@ Miscellaneous options .. versionadded:: 3.13 + * :samp:`-X inherit_context={0,1}` causes :class:`~threading.Thread` + to, by default, use a copy of context of of the caller of + ``Thread.start()`` when starting. Otherwise, threads will start + with an empty context. If unset, the value of this option defaults + to ``1`` on free-threaded builds and to ``0`` otherwise. See also + :envvar:`PYTHON_INHERIT_CONTEXT`. + + .. versionadded:: 3.14 + It also allows passing arbitrary values and retrieving them through the :data:`sys._xoptions` dictionary. @@ -1221,6 +1230,16 @@ conflict. .. versionadded:: 3.13 +.. envvar:: PYTHON_INHERIT_CONTEXT + + If this variable is set to ``1`` then :class:`~threading.Thread` will, + by default, use a copy of context of of the caller of ``Thread.start()`` + when starting. Otherwise, new threads will start with an empty context. + If unset, this variable defaults to ``1`` on free-threaded builds and to + ``0`` otherwise. See also :option:`-X inherit_context<-X>`. + + .. versionadded:: 3.14 + Debug-mode variables ~~~~~~~~~~~~~~~~~~~~ diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 8ef19f677066c2..d8edfa973b96f4 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -179,6 +179,7 @@ typedef struct PyConfig { int use_frozen_modules; int safe_path; int int_max_str_digits; + int inherit_context; #ifdef __APPLE__ int use_system_logger; #endif diff --git a/Lib/test/test_capi/test_config.py b/Lib/test/test_capi/test_config.py index a3179efe4a8235..a54c11377993a8 100644 --- a/Lib/test/test_capi/test_config.py +++ b/Lib/test/test_capi/test_config.py @@ -55,6 +55,7 @@ def test_config_get(self): ("filesystem_errors", str, None), ("hash_seed", int, None), ("home", str | None, None), + ("inherit_context", int, None), ("import_time", bool, None), ("inspect", bool, None), ("install_signal_handlers", bool, None), @@ -98,7 +99,7 @@ def test_config_get(self): ] if support.Py_DEBUG: options.append(("run_presite", str | None, None)) - if sysconfig.get_config_var('Py_GIL_DISABLED'): + if support.Py_GIL_DISABLED: options.append(("enable_gil", int, None)) options.append(("tlbc_enabled", int, None)) if support.MS_WINDOWS: @@ -170,7 +171,7 @@ def test_config_get_sys_flags(self): ("warn_default_encoding", "warn_default_encoding", False), ("safe_path", "safe_path", False), ("int_max_str_digits", "int_max_str_digits", False), - # "gil" is tested below + # "gil" and "inherit_context" are tested below ): with self.subTest(flag=flag, name=name, negate=negate): value = config_get(name) @@ -182,11 +183,14 @@ def test_config_get_sys_flags(self): config_get('use_hash_seed') == 0 or config_get('hash_seed') != 0) - if sysconfig.get_config_var('Py_GIL_DISABLED'): + if support.Py_GIL_DISABLED: value = config_get('enable_gil') expected = (value if value != -1 else None) self.assertEqual(sys.flags.gil, expected) + expected_inherit_context = 1 if support.Py_GIL_DISABLED else 0 + self.assertEqual(sys.flags.inherit_context, expected_inherit_context) + def test_config_get_non_existent(self): # Test PyConfig_Get() on non-existent option name config_get = _testcapi.config_get diff --git a/Lib/test/test_context.py b/Lib/test/test_context.py index 82d1797ab3b79e..53ae7b65e1dfbf 100644 --- a/Lib/test/test_context.py +++ b/Lib/test/test_context.py @@ -1,3 +1,4 @@ +import sys import collections.abc import concurrent.futures import contextvars @@ -383,6 +384,60 @@ def sub(num): tp.shutdown() self.assertEqual(results, list(range(10))) + @isolated_context + @threading_helper.requires_working_threading() + def test_context_thread_inherit(self): + import threading + + cvar = contextvars.ContextVar('cvar') + + def run_context_none(): + if sys.flags.inherit_context: + expected = 1 + else: + expected = None + self.assertEqual(cvar.get(None), expected) + + # By default, context is inherited based on the + # sys.flags.inherit_context option. + cvar.set(1) + thread = threading.Thread(target=run_context_none) + thread.start() + thread.join() + + # Passing 'None' explicitly should have same behaviour as not + # passing parameter. + thread = threading.Thread(target=run_context_none, context=None) + thread.start() + thread.join() + + # An explicit Context value can also be passed + custom_ctx = contextvars.Context() + custom_var = None + + def setup_context(): + nonlocal custom_var + custom_var = contextvars.ContextVar('custom') + custom_var.set(2) + + custom_ctx.run(setup_context) + + def run_custom(): + self.assertEqual(custom_var.get(), 2) + + thread = threading.Thread(target=run_custom, context=custom_ctx) + thread.start() + thread.join() + + # You can also pass a new Context() object to start with an empty context + def run_empty(): + with self.assertRaises(LookupError): + cvar.get() + + thread = threading.Thread(target=run_empty, context=contextvars.Context()) + thread.start() + thread.join() + # HAMT Tests diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 02d3fa985e75b9..48a31e5098c1d1 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -44,6 +44,7 @@ import random import inspect import threading +import contextvars if sys.platform == 'darwin': @@ -1725,8 +1726,10 @@ def test_threading(self): self.finish1 = threading.Event() self.finish2 = threading.Event() - th1 = threading.Thread(target=thfunc1, args=(self,)) - th2 = threading.Thread(target=thfunc2, args=(self,)) + th1 = threading.Thread(target=thfunc1, args=(self,), + context=contextvars.Context()) + th2 = threading.Thread(target=thfunc2, args=(self,), + context=contextvars.Context()) th1.start() th2.start() diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index cd65496cafb04d..739e112073d036 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -50,7 +50,7 @@ INIT_LOOPS = 4 MAX_HASH_SEED = 4294967295 -ABI_THREAD = 't' if sysconfig.get_config_var('Py_GIL_DISABLED') else '' +ABI_THREAD = 't' if support.Py_GIL_DISABLED else '' # PLATSTDLIB_LANDMARK copied from Modules/getpath.py if os.name == 'nt': PLATSTDLIB_LANDMARK = f'{sys.platlibdir}' @@ -60,6 +60,10 @@ PLATSTDLIB_LANDMARK = (f'{sys.platlibdir}/python{VERSION_MAJOR}.' f'{VERSION_MINOR}{ABI_THREAD}/lib-dynload') +if support.Py_GIL_DISABLED: + DEFAULT_INHERIT_CONTEXT = 1 +else: + DEFAULT_INHERIT_CONTEXT = 0 # If we are running from a build dir, but the stdlib has been installed, # some tests need to expect different results. @@ -586,6 +590,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'tracemalloc': 0, 'perf_profiling': 0, 'import_time': False, + 'inherit_context': DEFAULT_INHERIT_CONTEXT, 'code_debug_ranges': True, 'show_ref_count': False, 'dump_refs': False, diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 39857445a02255..d782874fbe80dd 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1845,8 +1845,9 @@ def test_pythontypes(self): # symtable entry # XXX # sys.flags - # FIXME: The +1 will not be necessary once gh-122575 is fixed - check(sys.flags, vsize('') + self.P * (1 + len(sys.flags))) + # FIXME: The +2 is for the 'gil' and 'inherit_context' flags and + # will not be necessary once gh-122575 is fixed + check(sys.flags, vsize('') + self.P * (2 + len(sys.flags))) def test_asyncgen_hooks(self): old = sys.get_asyncgen_hooks() diff --git a/Lib/threading.py b/Lib/threading.py index da9cdf0b09d83c..eec76a175bfa1e 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -3,6 +3,7 @@ import os as _os import sys as _sys import _thread +import _contextvars from time import monotonic as _time from _weakrefset import WeakSet @@ -871,7 +872,7 @@ class Thread: _initialized = False def __init__(self, group=None, target=None, name=None, - args=(), kwargs=None, *, daemon=None): + args=(), kwargs=None, *, daemon=None, context=None): """This constructor should always be called with keyword arguments. Arguments are: *group* should be None; reserved for future extension when a ThreadGroup @@ -888,6 +889,14 @@ class is implemented. *kwargs* is a dictionary of keyword arguments for the target invocation. Defaults to {}. + *context* is the contextvars.Context value to use for the thread. + The default value is None, which means to check + sys.flags.inherit_context. If that flag is true, use a copy of + the context of the caller. If false, use an empty context. To + explicitly start with an empty context, pass a new instance of + contextvars.Context(). To explicitly start with a copy of the + current context, pass the value from contextvars.copy_context(). + If a subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything else to the thread. @@ -917,6 +926,7 @@ class is implemented. self._daemonic = daemon else: self._daemonic = current_thread().daemon + self._context = context self._ident = None if _HAVE_THREAD_NATIVE_ID: self._native_id = None @@ -972,6 +982,16 @@ def start(self): with _active_limbo_lock: _limbo[self] = self + + if self._context is None: + # No context provided + if _sys.flags.inherit_context: + # start with a copy of the context of the caller + self._context = _contextvars.copy_context() + else: + # start with an empty context + self._context = _contextvars.Context() + try: # Start joinable thread _start_joinable_thread(self._bootstrap, handle=self._handle, @@ -1051,7 +1071,7 @@ def _bootstrap_inner(self): _sys.setprofile(_profile_hook) try: - self.run() + self._context.run(self.run) except: self._invoke_excepthook(self) finally: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst new file mode 100644 index 00000000000000..fd48a047a6d185 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst @@ -0,0 +1,16 @@ +Add the :data:`sys.flags.inherit_context` flag. + +* This flag is set to true by default on the free-threaded build + and false otherwise. If the flag is true, starting a new thread using + :class:`threading.Thread` will, by default, use a copy of the + :class:`contextvars.Context` from the caller of + :meth:`threading.Thread.start` rather than using an empty context. + +* Add the :option:`-X inherit_context <-X>` command-line option and + :envvar:`PYTHON_INHERIT_CONTEXT` environment variable, which set the + :data:`~sys.flags.inherit_context` flag. + +* Add the ``context`` keyword parameter to :class:`~threading.Thread`. It can + be used to explicitly pass a context value to be used by a new thread. + +* Make the :mod:`_contextvars` module built-in. diff --git a/Python/initconfig.c b/Python/initconfig.c index 4db77ef47d2362..187796b6c16c1a 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -141,6 +141,7 @@ static const PyConfigSpec PYCONFIG_SPEC[] = { SPEC(filesystem_errors, WSTR, READ_ONLY, NO_SYS), SPEC(hash_seed, ULONG, READ_ONLY, NO_SYS), SPEC(home, WSTR_OPT, READ_ONLY, NO_SYS), + SPEC(inherit_context, INT, READ_ONLY, NO_SYS), SPEC(import_time, BOOL, READ_ONLY, NO_SYS), SPEC(install_signal_handlers, BOOL, READ_ONLY, NO_SYS), SPEC(isolated, BOOL, READ_ONLY, NO_SYS), // sys.flags.isolated @@ -302,6 +303,9 @@ The following implementation-specific options are available:\n\ -X importtime: show how long each import takes; also PYTHONPROFILEIMPORTTIME\n\ -X int_max_str_digits=N: limit the size of int<->str conversions;\n\ 0 disables the limit; also PYTHONINTMAXSTRDIGITS\n\ +-X inherit_context=[0|1]: enable (1) or disable (0) threads inheriting context\n\ + vars by default; enabled by default in the free-threaded build and\n\ + disabled otherwise; also PYTHON_INHERIT_CONTEXT\n\ -X no_debug_ranges: don't include extra location information in code objects;\n\ also PYTHONNODEBUGRANGES\n\ -X perf: support the Linux \"perf\" profiler; also PYTHONPERFSUPPORT=1\n\ @@ -887,6 +891,7 @@ config_check_consistency(const PyConfig *config) assert(config->cpu_count != 0); // config->use_frozen_modules is initialized later // by _PyConfig_InitImportConfig(). + assert(config->inherit_context >= 0); #ifdef __APPLE__ assert(config->use_system_logger >= 0); #endif @@ -992,6 +997,11 @@ _PyConfig_InitCompatConfig(PyConfig *config) config->_is_python_build = 0; config->code_debug_ranges = 1; config->cpu_count = -1; +#ifdef Py_GIL_DISABLED + config->inherit_context = 1; +#else + config->inherit_context = 0; +#endif #ifdef __APPLE__ config->use_system_logger = 0; #endif @@ -1024,6 +1034,11 @@ config_init_defaults(PyConfig *config) #ifdef MS_WINDOWS config->legacy_windows_stdio = 0; #endif +#ifdef Py_GIL_DISABLED + config->inherit_context = 1; +#else + config->inherit_context = 0; +#endif #ifdef __APPLE__ config->use_system_logger = 0; #endif @@ -1058,6 +1073,11 @@ PyConfig_InitIsolatedConfig(PyConfig *config) config->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS; config->safe_path = 1; config->pathconfig_warnings = 0; +#ifdef Py_GIL_DISABLED + config->inherit_context = 1; +#else + config->inherit_context = 0; +#endif #ifdef MS_WINDOWS config->legacy_windows_stdio = 0; #endif @@ -1887,6 +1907,32 @@ config_init_cpu_count(PyConfig *config) "n must be greater than 0"); } +static PyStatus +config_init_inherit_context(PyConfig *config) +{ + const char *env = config_get_env(config, "PYTHON_INHERIT_CONTEXT"); + if (env) { + int enabled; + if (_Py_str_to_int(env, &enabled) < 0 || (enabled < 0) || (enabled > 1)) { + return _PyStatus_ERR( + "PYTHON_INHERIT_CONTEXT=N: N is missing or invalid"); + } + config->inherit_context = enabled; + } + + const wchar_t *xoption = config_get_xoption(config, L"inherit_context"); + if (xoption) { + int enabled; + const wchar_t *sep = wcschr(xoption, L'='); + if (!sep || (config_wstr_to_int(sep + 1, &enabled) < 0) || (enabled < 0) || (enabled > 1)) { + return _PyStatus_ERR( + "-X inherit_context=n: n is missing or invalid"); + } + config->inherit_context = enabled; + } + return _PyStatus_OK(); +} + static PyStatus config_init_tlbc(PyConfig *config) { @@ -2166,6 +2212,11 @@ config_read_complex_options(PyConfig *config) } #endif + status = config_init_inherit_context(config); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + status = config_init_tlbc(config); if (_PyStatus_EXCEPTION(status)) { return status; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index d5cb448eb618e8..6ab9d2b17a8453 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -3141,6 +3141,7 @@ static PyStructSequence_Field flags_fields[] = { {"safe_path", "-P"}, {"int_max_str_digits", "-X int_max_str_digits"}, {"gil", "-X gil"}, + {"inherit_context", "-X inherit_context"}, {0} }; @@ -3244,6 +3245,7 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags) #else SetFlagObj(PyLong_FromLong(1)); #endif + SetFlag(config->inherit_context); #undef SetFlagObj #undef SetFlag return 0; From a868fe97356f97f56468d43b3ca23b4b95636f62 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Thu, 6 Feb 2025 16:10:34 -0800 Subject: [PATCH 3/8] Tweak blurb markup. --- .../2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst index fd48a047a6d185..22c7399bee2204 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst @@ -13,4 +13,4 @@ Add the :data:`sys.flags.inherit_context` flag. * Add the ``context`` keyword parameter to :class:`~threading.Thread`. It can be used to explicitly pass a context value to be used by a new thread. -* Make the :mod:`_contextvars` module built-in. +* Make the ``_contextvars`` module built-in. From 16fa2c3891e143eca728a84b6942670ff810276d Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Thu, 6 Feb 2025 16:13:00 -0800 Subject: [PATCH 4/8] Doc markup fix. --- Doc/library/threading.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 65e498aabb8016..d333a094f5883e 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -367,7 +367,7 @@ since it is impossible to detect the termination of alien threads. an empty context. To explicitly start with an empty context, pass a new instance of :class:`~contextvars.Context()`. To explicitly start with a copy of the current context, pass the value from - :func:`~contextvars.copy_context()`. The flag defaults true on + :func:`~contextvars.copy_context`. The flag defaults true on free-threaded builds and false otherwise. If the subclass overrides the constructor, it must make sure to invoke the From 09e72b8d76ec25b1b23477ff7b8beb4218ee26a4 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Tue, 11 Feb 2025 15:23:37 -0800 Subject: [PATCH 5/8] Rename flag to `thread_inherit_context`. --- Doc/library/sys.rst | 7 ++-- Doc/library/threading.rst | 13 +++--- Doc/using/cmdline.rst | 8 ++-- Include/cpython/initconfig.h | 2 +- Lib/test/test_capi/test_config.py | 6 +-- Lib/test/test_context.py | 4 +- Lib/test/test_embed.py | 6 +-- Lib/test/test_sys.py | 2 +- Lib/threading.py | 10 ++--- ...-01-06-10-55-41.gh-issue-128555.tAK_AY.rst | 8 ++-- Python/initconfig.c | 40 ++++++++++--------- Python/sysmodule.c | 4 +- 12 files changed, 56 insertions(+), 54 deletions(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 43bda09323f557..779b179988b935 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -598,8 +598,9 @@ always available. Unless explicitly noted otherwise, all variables are read-only * - .. attribute:: flags.gil - :option:`-X gil <-X>` and :envvar:`PYTHON_GIL` - * - .. attribute:: flags.inherit_context - - :option:`-X inherit_context <-X>` and :envvar:`PYTHON_INHERIT_CONTEXT` + * - .. attribute:: flags.thread_inherit_context + - :option:`-X thread_inherit_context <-X>` and + :envvar:`PYTHON_THREAD_INHERIT_CONTEXT` .. versionchanged:: 3.2 Added ``quiet`` attribute for the new :option:`-q` flag. @@ -631,7 +632,7 @@ always available. Unless explicitly noted otherwise, all variables are read-only Added the ``gil`` attribute. .. versionchanged:: 3.14 - Added the ``inherit_context`` attribute. + Added the ``thread_inherit_context`` attribute. .. data:: float_info diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index d333a094f5883e..8049f2979f768a 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -361,14 +361,13 @@ since it is impossible to detect the termination of alien threads. *context* is the :class:`~contextvars.Context` value to use when starting the thread. The default value is ``None`` which indicates that the - :data:`sys.flags.inherit_context` flag controls the behaviour. If + :data:`sys.flags.thread_inherit_context` flag controls the behaviour. If the flag is true, threads will start with a copy of the context of the - caller of :meth:`~Thread.start`. If false, they will start with - an empty context. To explicitly start with an empty context, - pass a new instance of :class:`~contextvars.Context()`. To explicitly - start with a copy of the current context, pass the value from - :func:`~contextvars.copy_context`. The flag defaults true on - free-threaded builds and false otherwise. + caller of :meth:`~Thread.start`. If false, they will start with an empty + context. To explicitly start with an empty context, pass a new instance of + :class:`~contextvars.Context()`. To explicitly start with a copy of the + current context, pass the value from :func:`~contextvars.copy_context`. The + flag defaults true on free-threaded builds and false otherwise. If the subclass overrides the constructor, it must make sure to invoke the base class constructor (``Thread.__init__()``) before doing anything else to diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index be5816f7c8d2b0..8a4d4fbcd94433 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -628,12 +628,12 @@ Miscellaneous options .. versionadded:: 3.13 - * :samp:`-X inherit_context={0,1}` causes :class:`~threading.Thread` + * :samp:`-X thread_inherit_context={0,1}` causes :class:`~threading.Thread` to, by default, use a copy of context of of the caller of ``Thread.start()`` when starting. Otherwise, threads will start with an empty context. If unset, the value of this option defaults to ``1`` on free-threaded builds and to ``0`` otherwise. See also - :envvar:`PYTHON_INHERIT_CONTEXT`. + :envvar:`PYTHON_THREAD_INHERIT_CONTEXT`. .. versionadded:: 3.14 @@ -1230,13 +1230,13 @@ conflict. .. versionadded:: 3.13 -.. envvar:: PYTHON_INHERIT_CONTEXT +.. envvar:: PYTHON_THREAD_INHERIT_CONTEXT If this variable is set to ``1`` then :class:`~threading.Thread` will, by default, use a copy of context of of the caller of ``Thread.start()`` when starting. Otherwise, new threads will start with an empty context. If unset, this variable defaults to ``1`` on free-threaded builds and to - ``0`` otherwise. See also :option:`-X inherit_context<-X>`. + ``0`` otherwise. See also :option:`-X thread_inherit_context<-X>`. .. versionadded:: 3.14 diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index d8edfa973b96f4..202f4a8964736e 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -179,7 +179,7 @@ typedef struct PyConfig { int use_frozen_modules; int safe_path; int int_max_str_digits; - int inherit_context; + int thread_inherit_context; #ifdef __APPLE__ int use_system_logger; #endif diff --git a/Lib/test/test_capi/test_config.py b/Lib/test/test_capi/test_config.py index a54c11377993a8..21c66a7b613335 100644 --- a/Lib/test/test_capi/test_config.py +++ b/Lib/test/test_capi/test_config.py @@ -55,7 +55,7 @@ def test_config_get(self): ("filesystem_errors", str, None), ("hash_seed", int, None), ("home", str | None, None), - ("inherit_context", int, None), + ("thread_inherit_context", int, None), ("import_time", bool, None), ("inspect", bool, None), ("install_signal_handlers", bool, None), @@ -171,7 +171,7 @@ def test_config_get_sys_flags(self): ("warn_default_encoding", "warn_default_encoding", False), ("safe_path", "safe_path", False), ("int_max_str_digits", "int_max_str_digits", False), - # "gil" and "inherit_context" are tested below + # "gil" and "thread_inherit_context" are tested below ): with self.subTest(flag=flag, name=name, negate=negate): value = config_get(name) @@ -189,7 +189,7 @@ def test_config_get_sys_flags(self): self.assertEqual(sys.flags.gil, expected) expected_inherit_context = 1 if support.Py_GIL_DISABLED else 0 - self.assertEqual(sys.flags.inherit_context, expected_inherit_context) + self.assertEqual(sys.flags.thread_inherit_context, expected_inherit_context) def test_config_get_non_existent(self): # Test PyConfig_Get() on non-existent option name diff --git a/Lib/test/test_context.py b/Lib/test/test_context.py index 53ae7b65e1dfbf..b026f5cb64a571 100644 --- a/Lib/test/test_context.py +++ b/Lib/test/test_context.py @@ -392,14 +392,14 @@ def test_context_thread_inherit(self): cvar = contextvars.ContextVar('cvar') def run_context_none(): - if sys.flags.inherit_context: + if sys.flags.thread_inherit_context: expected = 1 else: expected = None self.assertEqual(cvar.get(None), expected) # By default, context is inherited based on the - # sys.flags.inherit_context option. + # sys.flags.thread_inherit_context option. cvar.set(1) thread = threading.Thread(target=run_context_none) thread.start() diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 739e112073d036..772d62320a5ba9 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -61,9 +61,9 @@ f'{VERSION_MINOR}{ABI_THREAD}/lib-dynload') if support.Py_GIL_DISABLED: - DEFAULT_INHERIT_CONTEXT = 1 + DEFAULT_THREAD_INHERIT_CONTEXT = 1 else: - DEFAULT_INHERIT_CONTEXT = 0 + DEFAULT_THREAD_INHERIT_CONTEXT = 0 # If we are running from a build dir, but the stdlib has been installed, # some tests need to expect different results. @@ -590,7 +590,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'tracemalloc': 0, 'perf_profiling': 0, 'import_time': False, - 'inherit_context': DEFAULT_INHERIT_CONTEXT, + 'thread_inherit_context': DEFAULT_THREAD_INHERIT_CONTEXT, 'code_debug_ranges': True, 'show_ref_count': False, 'dump_refs': False, diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index d782874fbe80dd..b58355700f2c6d 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1845,7 +1845,7 @@ def test_pythontypes(self): # symtable entry # XXX # sys.flags - # FIXME: The +2 is for the 'gil' and 'inherit_context' flags and + # FIXME: The +2 is for the 'gil' and 'thread_inherit_context' flags and # will not be necessary once gh-122575 is fixed check(sys.flags, vsize('') + self.P * (2 + len(sys.flags))) diff --git a/Lib/threading.py b/Lib/threading.py index eec76a175bfa1e..fc27de3f7a2d79 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -891,11 +891,11 @@ class is implemented. *context* is the contextvars.Context value to use for the thread. The default value is None, which means to check - sys.flags.inherit_context. If that flag is true, use a copy of - the context of the caller. If false, use an empty context. To + sys.flags.thread_inherit_context. If that flag is true, use a copy + of the context of the caller. If false, use an empty context. To explicitly start with an empty context, pass a new instance of - contextvars.Context(). To explicitly start with a copy of the - current context, pass the value from contextvars.copy_context(). + contextvars.Context(). To explicitly start with a copy of the current + context, pass the value from contextvars.copy_context(). If a subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything @@ -985,7 +985,7 @@ def start(self): if self._context is None: # No context provided - if _sys.flags.inherit_context: + if _sys.flags.thread_inherit_context: # start with a copy of the context of the caller self._context = _contextvars.copy_context() else: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst index 22c7399bee2204..e0b468e76a062b 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-06-10-55-41.gh-issue-128555.tAK_AY.rst @@ -1,4 +1,4 @@ -Add the :data:`sys.flags.inherit_context` flag. +Add the :data:`sys.flags.thread_inherit_context` flag. * This flag is set to true by default on the free-threaded build and false otherwise. If the flag is true, starting a new thread using @@ -6,9 +6,9 @@ Add the :data:`sys.flags.inherit_context` flag. :class:`contextvars.Context` from the caller of :meth:`threading.Thread.start` rather than using an empty context. -* Add the :option:`-X inherit_context <-X>` command-line option and - :envvar:`PYTHON_INHERIT_CONTEXT` environment variable, which set the - :data:`~sys.flags.inherit_context` flag. +* Add the :option:`-X thread_inherit_context <-X>` command-line option and + :envvar:`PYTHON_THREAD_INHERIT_CONTEXT` environment variable, which set the + :data:`~sys.flags.thread_inherit_context` flag. * Add the ``context`` keyword parameter to :class:`~threading.Thread`. It can be used to explicitly pass a context value to be used by a new thread. diff --git a/Python/initconfig.c b/Python/initconfig.c index 187796b6c16c1a..02d43c0236c28e 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -141,7 +141,7 @@ static const PyConfigSpec PYCONFIG_SPEC[] = { SPEC(filesystem_errors, WSTR, READ_ONLY, NO_SYS), SPEC(hash_seed, ULONG, READ_ONLY, NO_SYS), SPEC(home, WSTR_OPT, READ_ONLY, NO_SYS), - SPEC(inherit_context, INT, READ_ONLY, NO_SYS), + SPEC(thread_inherit_context, INT, READ_ONLY, NO_SYS), SPEC(import_time, BOOL, READ_ONLY, NO_SYS), SPEC(install_signal_handlers, BOOL, READ_ONLY, NO_SYS), SPEC(isolated, BOOL, READ_ONLY, NO_SYS), // sys.flags.isolated @@ -303,9 +303,6 @@ The following implementation-specific options are available:\n\ -X importtime: show how long each import takes; also PYTHONPROFILEIMPORTTIME\n\ -X int_max_str_digits=N: limit the size of int<->str conversions;\n\ 0 disables the limit; also PYTHONINTMAXSTRDIGITS\n\ --X inherit_context=[0|1]: enable (1) or disable (0) threads inheriting context\n\ - vars by default; enabled by default in the free-threaded build and\n\ - disabled otherwise; also PYTHON_INHERIT_CONTEXT\n\ -X no_debug_ranges: don't include extra location information in code objects;\n\ also PYTHONNODEBUGRANGES\n\ -X perf: support the Linux \"perf\" profiler; also PYTHONPERFSUPPORT=1\n\ @@ -329,6 +326,9 @@ The following implementation-specific options are available:\n\ PYTHON_TLBC\n" #endif "\ +-X thread_inherit_context=[0|1]: enable (1) or disable (0) threads inheriting\n\ + context vars by default; enabled by default in the free-threaded\n\ + build and disabled otherwise; also PYTHON_THREAD_INHERIT_CONTEXT\n\ -X tracemalloc[=N]: trace Python memory allocations; N sets a traceback limit\n \ of N frames (default: 1); also PYTHONTRACEMALLOC=N\n\ -X utf8[=0|1]: enable (1) or disable (0) UTF-8 mode; also PYTHONUTF8\n\ @@ -416,6 +416,8 @@ static const char usage_envvars[] = #ifdef Py_GIL_DISABLED "PYTHON_TLBC : when set to 0, disables thread-local bytecode (-X tlbc)\n" #endif +"PYTHON_THREAD_INHERIT_CONTEXT: threads inherit context vars if 1\n" +" (-X thread_inherit_context)\n" "PYTHONTRACEMALLOC: trace Python memory allocations (-X tracemalloc)\n" "PYTHONUNBUFFERED: disable stdout/stderr buffering (-u)\n" "PYTHONUTF8 : control the UTF-8 mode (-X utf8)\n" @@ -891,7 +893,7 @@ config_check_consistency(const PyConfig *config) assert(config->cpu_count != 0); // config->use_frozen_modules is initialized later // by _PyConfig_InitImportConfig(). - assert(config->inherit_context >= 0); + assert(config->thread_inherit_context >= 0); #ifdef __APPLE__ assert(config->use_system_logger >= 0); #endif @@ -998,9 +1000,9 @@ _PyConfig_InitCompatConfig(PyConfig *config) config->code_debug_ranges = 1; config->cpu_count = -1; #ifdef Py_GIL_DISABLED - config->inherit_context = 1; + config->thread_inherit_context = 1; #else - config->inherit_context = 0; + config->thread_inherit_context = 0; #endif #ifdef __APPLE__ config->use_system_logger = 0; @@ -1035,9 +1037,9 @@ config_init_defaults(PyConfig *config) config->legacy_windows_stdio = 0; #endif #ifdef Py_GIL_DISABLED - config->inherit_context = 1; + config->thread_inherit_context = 1; #else - config->inherit_context = 0; + config->thread_inherit_context = 0; #endif #ifdef __APPLE__ config->use_system_logger = 0; @@ -1074,9 +1076,9 @@ PyConfig_InitIsolatedConfig(PyConfig *config) config->safe_path = 1; config->pathconfig_warnings = 0; #ifdef Py_GIL_DISABLED - config->inherit_context = 1; + config->thread_inherit_context = 1; #else - config->inherit_context = 0; + config->thread_inherit_context = 0; #endif #ifdef MS_WINDOWS config->legacy_windows_stdio = 0; @@ -1908,27 +1910,27 @@ config_init_cpu_count(PyConfig *config) } static PyStatus -config_init_inherit_context(PyConfig *config) +config_init_thread_inherit_context(PyConfig *config) { - const char *env = config_get_env(config, "PYTHON_INHERIT_CONTEXT"); + const char *env = config_get_env(config, "PYTHON_THREAD_INHERIT_CONTEXT"); if (env) { int enabled; if (_Py_str_to_int(env, &enabled) < 0 || (enabled < 0) || (enabled > 1)) { return _PyStatus_ERR( - "PYTHON_INHERIT_CONTEXT=N: N is missing or invalid"); + "PYTHON_THREAD_INHERIT_CONTEXT=N: N is missing or invalid"); } - config->inherit_context = enabled; + config->thread_inherit_context = enabled; } - const wchar_t *xoption = config_get_xoption(config, L"inherit_context"); + const wchar_t *xoption = config_get_xoption(config, L"thread_inherit_context"); if (xoption) { int enabled; const wchar_t *sep = wcschr(xoption, L'='); if (!sep || (config_wstr_to_int(sep + 1, &enabled) < 0) || (enabled < 0) || (enabled > 1)) { return _PyStatus_ERR( - "-X inherit_context=n: n is missing or invalid"); + "-X thread_inherit_context=n: n is missing or invalid"); } - config->inherit_context = enabled; + config->thread_inherit_context = enabled; } return _PyStatus_OK(); } @@ -2212,7 +2214,7 @@ config_read_complex_options(PyConfig *config) } #endif - status = config_init_inherit_context(config); + status = config_init_thread_inherit_context(config); if (_PyStatus_EXCEPTION(status)) { return status; } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 6ab9d2b17a8453..71285c074be066 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -3141,7 +3141,7 @@ static PyStructSequence_Field flags_fields[] = { {"safe_path", "-P"}, {"int_max_str_digits", "-X int_max_str_digits"}, {"gil", "-X gil"}, - {"inherit_context", "-X inherit_context"}, + {"thread_inherit_context", "-X thread_inherit_context"}, {0} }; @@ -3245,7 +3245,7 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags) #else SetFlagObj(PyLong_FromLong(1)); #endif - SetFlag(config->inherit_context); + SetFlag(config->thread_inherit_context); #undef SetFlagObj #undef SetFlag return 0; From 872920d525f3938bd22c623d4e0041f012688aba Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Tue, 11 Feb 2025 12:41:50 -0800 Subject: [PATCH 6/8] Regenerate 'configure' script. --- configure | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/configure b/configure index d46bc563a67245..260a49fc83149b 100755 --- a/configure +++ b/configure @@ -801,8 +801,6 @@ MODULE__HEAPQ_FALSE MODULE__HEAPQ_TRUE MODULE__CSV_FALSE MODULE__CSV_TRUE -MODULE__CONTEXTVARS_FALSE -MODULE__CONTEXTVARS_TRUE MODULE__BISECT_FALSE MODULE__BISECT_TRUE MODULE__ASYNCIO_FALSE @@ -30727,28 +30725,6 @@ then : -fi - - - if test "$py_cv_module__contextvars" != "n/a" -then : - py_cv_module__contextvars=yes -fi - if test "$py_cv_module__contextvars" = yes; then - MODULE__CONTEXTVARS_TRUE= - MODULE__CONTEXTVARS_FALSE='#' -else - MODULE__CONTEXTVARS_TRUE='#' - MODULE__CONTEXTVARS_FALSE= -fi - - as_fn_append MODULE_BLOCK "MODULE__CONTEXTVARS_STATE=$py_cv_module__contextvars$as_nl" - if test "x$py_cv_module__contextvars" = xyes -then : - - - - fi @@ -33599,10 +33575,6 @@ if test -z "${MODULE__BISECT_TRUE}" && test -z "${MODULE__BISECT_FALSE}"; then as_fn_error $? "conditional \"MODULE__BISECT\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi -if test -z "${MODULE__CONTEXTVARS_TRUE}" && test -z "${MODULE__CONTEXTVARS_FALSE}"; then - as_fn_error $? "conditional \"MODULE__CONTEXTVARS\" was never defined. -Usually this means the macro was only invoked conditionally." "$LINENO" 5 -fi if test -z "${MODULE__CSV_TRUE}" && test -z "${MODULE__CSV_FALSE}"; then as_fn_error $? "conditional \"MODULE__CSV\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 From 2c48fc5122522a8aaefa358c1b7e860bc0d268de Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Wed, 19 Feb 2025 09:53:11 -0800 Subject: [PATCH 7/8] Add comment about why 'context' is passed in test. --- Lib/test/test_decimal.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 48a31e5098c1d1..884b272482e72d 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1726,6 +1726,9 @@ def test_threading(self): self.finish1 = threading.Event() self.finish2 = threading.Event() + # This test wants to start threads with an empty context, no matter + # the setting of sys.flags.thread_inherit_context. We pass the + # 'context' argument explicitly with an empty context instance. th1 = threading.Thread(target=thfunc1, args=(self,), context=contextvars.Context()) th2 = threading.Thread(target=thfunc2, args=(self,), From 53eb72d5511ab0f6e2cd6c0c8b8ea6c52db45c6b Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Fri, 21 Feb 2025 12:07:59 -0800 Subject: [PATCH 8/8] Revise "decimal' docs, adding note about flag. --- Doc/library/decimal.rst | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index 9318af60b60f95..2111ff67edaa3d 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1884,13 +1884,20 @@ the current thread. If :func:`setcontext` has not been called before :func:`getcontext`, then :func:`getcontext` will automatically create a new context for use in the -current thread. - -The new context is copied from a prototype context called *DefaultContext*. To -control the defaults so that each thread will use the same values throughout the -application, directly modify the *DefaultContext* object. This should be done -*before* any threads are started so that there won't be a race condition between -threads calling :func:`getcontext`. For example:: +current thread. New context objects have default values set from the +:data:`decimal.DefaultContext` object. + +The :data:`sys.flags.thread_inherit_context` flag affects the context for +new threads. If the flag is false, new threads will start with an empty +context. In this case, :func:`getcontext` will create a new context object +when called and use the default values from *DefaultContext*. If the flag +is true, new threads will start with a copy of context from the caller of +:meth:`Thread.start`. + +To control the defaults so that each thread will use the same values throughout +the application, directly modify the *DefaultContext* object. This should be +done *before* any threads are started so that there won't be a race condition +between threads calling :func:`getcontext`. For example:: # Set applicationwide defaults for all threads about to be launched DefaultContext.prec = 12