Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion Doc/c-api/contextvars.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,37 @@ Context variable functions:
Create a new ``ContextVar`` object. The *name* parameter is used
for introspection and debug purposes. The *def* parameter specifies
a default value for the context variable, or ``NULL`` for no default.
If an error has occurred, this function returns ``NULL``.
Automatic inheritance of the variable's bindings by
:class:`threading.Thread` follows
:data:`sys.flags.thread_inherit_context`. If an error has occurred, this
function returns ``NULL``.

.. c:function:: PyObject *PyContextVar_NewWithFlags(const char *name, PyObject *def, int flags)

Create a new ``ContextVar`` object with the specified *flags*. The *name*,
*def*, and return value are the same as for :c:func:`PyContextVar_New`.
*flags* must be one of the following values:

.. c:macro:: Py_CONTEXTVAR_INHERIT_THREAD_DEFAULT

Follow :data:`sys.flags.thread_inherit_context` when deciding whether a
new :class:`threading.Thread` inherits the variable's binding. This is
equivalent to :c:func:`PyContextVar_New`.

.. c:macro:: Py_CONTEXTVAR_INHERIT_THREAD_NEVER

Do not automatically inherit the variable's binding, regardless of
:data:`sys.flags.thread_inherit_context`.

.. c:macro:: Py_CONTEXTVAR_INHERIT_THREAD_ALWAYS

Automatically inherit the variable's binding, regardless of
:data:`sys.flags.thread_inherit_context`.

Passing any other value causes the function to return ``NULL`` with
:exc:`ValueError` set.

.. versionadded:: 3.16

.. c:function:: int PyContextVar_Get(PyObject *var, PyObject *default_value, PyObject **value)

Expand Down
5 changes: 5 additions & 0 deletions Doc/data/refcounts.dat
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,11 @@ PyContextVar_New:PyObject*::+1:
PyContextVar_New:const char*:name::
PyContextVar_New:PyObject*:def:+1:

PyContextVar_NewWithFlags:PyObject*::+1:
PyContextVar_NewWithFlags:const char*:name::
PyContextVar_NewWithFlags:PyObject*:def:+1:
PyContextVar_NewWithFlags:int:flags::

PyContextVar_Set:PyObject*::+1:
PyContextVar_Set:PyObject*:var:0:
PyContextVar_Set:PyObject*:value:+1:
Expand Down
5 changes: 3 additions & 2 deletions Doc/howto/free-threading-python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,9 @@ is set to true by default which causes threads created with
:class:`threading.Thread` to start with a copy of the
:class:`~contextvars.Context()` of the caller of
:meth:`~threading.Thread.start`. In the default GIL-enabled build, the flag
defaults to false so threads start with an
empty :class:`~contextvars.Context()`.
defaults to false, so threads do not inherit context variable bindings by
default. Individual variables can override either setting with the
:class:`~contextvars.ContextVar` constructor's *thread_inheritable* parameter.


Warning filters
Expand Down
41 changes: 40 additions & 1 deletion Doc/library/contextvars.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ See also :pep:`567` for additional details.
Context Variables
-----------------

.. class:: ContextVar(name, [*, default])
.. class:: ContextVar(name, [*, default, thread_inheritable])

This class is used to declare a new Context Variable, e.g.::

Expand All @@ -37,6 +37,38 @@ Context Variables
:meth:`ContextVar.get` when no value for the variable is found
in the current context.

The optional keyword-only *thread_inheritable* parameter controls whether
the variable's current binding is automatically inherited by a
:class:`threading.Thread` when no explicit :class:`Context` is supplied.
If ``None`` (the default), :data:`sys.flags.thread_inherit_context`
determines whether the binding is inherited. If true, the binding is
inherited regardless of that flag; if false, it is not inherited regardless
of the flag.

This parameter only affects implicit context inheritance by
:meth:`threading.Thread.start`. The bindings are captured from the caller
of :meth:`~threading.Thread.start`, not the creator of the
:class:`~threading.Thread` object. It does not affect threads started
directly through :mod:`_thread` or by extension modules.

An explicitly supplied thread context is not filtered, and neither are
contexts copied for other purposes, including asynchronous tasks and
:func:`asyncio.to_thread`. An inherited binding is an ordinary binding in
the new thread's context: it is visible to :func:`copy_context`, may be
changed independently, and may in turn be inherited by threads started from
that thread. Only the binding is copied; the bound object itself is not
copied. A mutable bound object can therefore be accessed concurrently by
the starting and new threads.

For a thread pool, inheritance occurs when each worker thread starts, not
each time work is submitted. Consequently,
:class:`~concurrent.futures.ThreadPoolExecutor` does not propagate the
submitter's current bindings to an existing worker merely because a variable
is thread-inheritable.

.. versionchanged:: 3.16
Added the *thread_inheritable* parameter.

**Important:** Context Variables should be created at the top module
level and never in closures. :class:`Context` objects hold strong
references to context variables which prevents context variables
Expand All @@ -51,6 +83,13 @@ Context Variables

.. versionadded:: 3.7.1

.. attribute:: ContextVar.thread_inheritable

The value of the *thread_inheritable* constructor parameter: ``None``,
``True``, or ``False``. This is a read-only property.

.. versionadded:: 3.16

.. method:: get([default])

Return a value for the context variable for the current context.
Expand Down
13 changes: 8 additions & 5 deletions Doc/library/threading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -539,11 +539,14 @@ since it is impossible to detect the termination of alien threads.
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.
caller of :meth:`~Thread.start`; if false, they will not inherit bindings by
default. The :class:`~contextvars.ContextVar` constructor's
*thread_inheritable* parameter can override the flag for an individual
variable. 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`.
Explicit contexts are not filtered by per-variable settings. 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
Expand Down
25 changes: 14 additions & 11 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -674,12 +674,13 @@ 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 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`.
* :samp:`-X thread_inherit_context={0,1}` controls whether
:class:`~threading.Thread` inherits context variable bindings from the
caller of ``Thread.start()`` by default. Individual context variables can
override this setting with the :class:`~contextvars.ContextVar`
constructor's *thread_inheritable* parameter. 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

Expand Down Expand Up @@ -1366,11 +1367,13 @@ conflict.

.. 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 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>`.
This variable controls whether :class:`~threading.Thread` inherits context
variable bindings from the caller of ``Thread.start()`` by default.
Individual context variables can override it with the
:class:`~contextvars.ContextVar` constructor's *thread_inheritable*
parameter. 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

Expand Down
13 changes: 13 additions & 0 deletions Include/cpython/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ PyAPI_FUNC(int) PyContext_ClearWatcher(int watcher_id);
PyAPI_FUNC(PyObject *) PyContextVar_New(
const char *name, PyObject *default_value);

/* Flags for PyContextVar_NewWithFlags(). */
#define Py_CONTEXTVAR_INHERIT_THREAD_DEFAULT 0
#define Py_CONTEXTVAR_INHERIT_THREAD_NEVER 1
#define Py_CONTEXTVAR_INHERIT_THREAD_ALWAYS 2

/* Create a new context variable with the specified flags.

default_value can be NULL. flags must be one of the
Py_CONTEXTVAR_INHERIT_THREAD_* values.
*/
PyAPI_FUNC(PyObject *) PyContextVar_NewWithFlags(
const char *name, PyObject *default_value, int flags);


/* Get a value for the variable.

Expand Down
16 changes: 16 additions & 0 deletions Include/internal/pycore_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,28 @@ struct _pycontextobject {
PyHamtObject *ctx_vars;
PyObject *ctx_weakreflist;
int ctx_entered;
// Redundant subset of ctx_vars holding exactly the bindings inherited by
// an implicitly created child thread. Since the HAMT is immutable, it can
// be shared directly with the child's context. While this and ctx_vars
// are the same object, one HAMT update serves both fields; adding a
// non-inheritable binding makes them diverge. This relies on both
// var_thread_inherit and thread_inherit_context remaining immutable.
PyHamtObject *ctx_inheritable_vars;
};


typedef enum {
_Py_CONTEXTVAR_INHERIT_DEFAULT = 0,
_Py_CONTEXTVAR_INHERIT_FALSE = 1,
_Py_CONTEXTVAR_INHERIT_TRUE = 2,
} _PyContextVarInherit;


struct _pycontextvarobject {
PyObject_HEAD
PyObject *var_name;
PyObject *var_default;
_PyContextVarInherit var_thread_inherit;
#ifndef Py_GIL_DISABLED
PyObject *var_cached;
uint64_t var_cached_tsid;
Expand All @@ -54,6 +69,7 @@ struct _pycontexttokenobject {
// _testinternalcapi.hamt() used by tests.
// Export for '_testcapi' shared extension
PyAPI_FUNC(PyObject*) _PyContext_NewHamtForTests(void);
PyAPI_FUNC(PyObject*) _PyContext_NewThreadContext(void);

PyAPI_FUNC(int) _PyContext_Enter(PyThreadState *ts, PyObject *octx);
PyAPI_FUNC(int) _PyContext_Exit(PyThreadState *ts, PyObject *octx);
Expand Down
Loading
Loading