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
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index eaa8aa711db173..c8f44ad7a4d644 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,13 @@ 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.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.
@@ -620,6 +628,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 ``thread_inherit_context`` attribute.
+
.. data:: float_info
diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst
index 00511df32e4388..8049f2979f768a 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,16 @@ 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.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.
+
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 +379,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..8a4d4fbcd94433 100644
--- a/Doc/using/cmdline.rst
+++ b/Doc/using/cmdline.rst
@@ -628,6 +628,15 @@ Miscellaneous options
.. versionadded:: 3.13
+ * :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_THREAD_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_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 thread_inherit_context<-X>`.
+
+ .. versionadded:: 3.14
+
Debug-mode variables
~~~~~~~~~~~~~~~~~~~~
diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h
index 8ef19f677066c2..202f4a8964736e 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 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 a3179efe4a8235..21c66a7b613335 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),
+ ("thread_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 "thread_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.thread_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..b026f5cb64a571 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.thread_inherit_context:
+ expected = 1
+ else:
+ expected = None
+ self.assertEqual(cvar.get(None), expected)
+
+ # By default, context is inherited based on the
+ # sys.flags.thread_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..884b272482e72d 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,13 @@ 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,))
+ # 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,),
+ context=contextvars.Context())
th1.start()
th2.start()
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index cd65496cafb04d..772d62320a5ba9 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_THREAD_INHERIT_CONTEXT = 1
+else:
+ 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.
@@ -586,6 +590,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'tracemalloc': 0,
'perf_profiling': 0,
'import_time': False,
+ '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 39857445a02255..b58355700f2c6d 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 'thread_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..fc27de3f7a2d79 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.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().
+
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.thread_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/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/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..e0b468e76a062b
--- /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.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
+ :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 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.
+
+* Make the ``_contextvars`` module built-in.
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/Python/initconfig.c b/Python/initconfig.c
index 4db77ef47d2362..02d43c0236c28e 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(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
@@ -325,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\
@@ -412,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"
@@ -887,6 +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->thread_inherit_context >= 0);
#ifdef __APPLE__
assert(config->use_system_logger >= 0);
#endif
@@ -992,6 +999,11 @@ _PyConfig_InitCompatConfig(PyConfig *config)
config->_is_python_build = 0;
config->code_debug_ranges = 1;
config->cpu_count = -1;
+#ifdef Py_GIL_DISABLED
+ config->thread_inherit_context = 1;
+#else
+ config->thread_inherit_context = 0;
+#endif
#ifdef __APPLE__
config->use_system_logger = 0;
#endif
@@ -1024,6 +1036,11 @@ config_init_defaults(PyConfig *config)
#ifdef MS_WINDOWS
config->legacy_windows_stdio = 0;
#endif
+#ifdef Py_GIL_DISABLED
+ config->thread_inherit_context = 1;
+#else
+ config->thread_inherit_context = 0;
+#endif
#ifdef __APPLE__
config->use_system_logger = 0;
#endif
@@ -1058,6 +1075,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->thread_inherit_context = 1;
+#else
+ config->thread_inherit_context = 0;
+#endif
#ifdef MS_WINDOWS
config->legacy_windows_stdio = 0;
#endif
@@ -1887,6 +1909,32 @@ config_init_cpu_count(PyConfig *config)
"n must be greater than 0");
}
+static PyStatus
+config_init_thread_inherit_context(PyConfig *config)
+{
+ 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_THREAD_INHERIT_CONTEXT=N: N is missing or invalid");
+ }
+ config->thread_inherit_context = enabled;
+ }
+
+ 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 thread_inherit_context=n: n is missing or invalid");
+ }
+ config->thread_inherit_context = enabled;
+ }
+ return _PyStatus_OK();
+}
+
static PyStatus
config_init_tlbc(PyConfig *config)
{
@@ -2166,6 +2214,11 @@ config_read_complex_options(PyConfig *config)
}
#endif
+ status = config_init_thread_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..71285c074be066 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"},
+ {"thread_inherit_context", "-X thread_inherit_context"},
{0}
};
@@ -3244,6 +3245,7 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags)
#else
SetFlagObj(PyLong_FromLong(1));
#endif
+ SetFlag(config->thread_inherit_context);
#undef SetFlagObj
#undef SetFlag
return 0;
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
diff --git a/configure.ac b/configure.ac
index faa8909530389d..fffbf1ddf620c0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7767,7 +7767,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])