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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy