Skip to content

gh-154562: Add ContextVar(thread_inheritable=...). - #154564

Draft
nascheme wants to merge 4 commits into
python:mainfrom
nascheme:ctx_thread_inheritable_vars
Draft

gh-154562: Add ContextVar(thread_inheritable=...).#154564
nascheme wants to merge 4 commits into
python:mainfrom
nascheme:ctx_thread_inheritable_vars

Conversation

@nascheme

@nascheme nascheme commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

Add the keyword-only thread_inheritable parameter to contextvars.ContextVar to control automatic inheritance of individual variable bindings by new threading.Thread instances:

  • None (the default) follows sys.flags.thread_inherit_context.
  • True always inherits the binding.
  • False never inherits the binding.

The policy is exposed through the read-only ContextVar.thread_inheritable attribute.

This gives libraries per-variable control between inheriting no context and inheriting the entire context. In particular, libraries can migrate global state to context-local state while preserving inheritance by new threads, without requiring applications to change a process-wide flag. Variables can also be excluded when the flag is enabled.

Behavior

  • When no explicit context= is supplied, Thread.start() captures the caller's current bindings selected by the global flag and each variable's thread_inheritable policy.
  • Existing ContextVar construction remains unchanged: omitting thread_inheritable, or passing None, follows sys.flags.thread_inherit_context.
  • Inherited bindings are ordinary context bindings: they are visible to copy_context(), can be changed independently in the child, and are inherited transitively by threads started from that child.
  • Explicitly supplied contexts are authoritative and are not filtered.
  • Threads started by other means, such as _thread.start_new_thread() or extension-module APIs, are unaffected.
  • Inheritance occurs when a worker thread starts, not when work is submitted to an existing thread-pool worker.

The C API adds PyContextVar_NewWithFlags() with Py_CONTEXTVAR_INHERIT_THREAD_DEFAULT, Py_CONTEXTVAR_INHERIT_THREAD_ALWAYS, and Py_CONTEXTVAR_INHERIT_THREAD_NEVER.

Implementation

Each Context maintains a second HAMT containing exactly the bindings selected for implicit thread inheritance. This redundant subset is kept synchronized by ContextVar.set() and reset() according to the interpreter-wide flag and each variable's immutable policy.

Thread.start() uses the private _contextvars._new_thread_context() factory to initialize the child directly from this subset. Because HAMTs are immutable, the subset can be shared with the child without scanning or copying bindings, so startup cost is independent of the number of bound variables.

ContextVar gains an internal three-state inheritance policy. PyContext remains publicly opaque, so the additional field does not affect the public C API layout.

Context variables created this way will automatically be inherited by
the context for new threads, regardless of the setting of
`thread_inherit_context`.
@read-the-docs-community

read-the-docs-community Bot commented Jul 23, 2026

Copy link
Copy Markdown

@johnslavik
johnslavik self-requested a review July 23, 2026 22:28
@nascheme

Copy link
Copy Markdown
Member Author

Another option would be to add a thread_inheritable keyword to ContextVar. This avoids having to come up with a good class-method name and also allows querying of the instance attribute. Not sure why that would be useful but a keyword arg seems like the comman and straightforward way to do this.

Example library code that wants to detect support:

    # logic to fallback to regular ContextVar
     if hasattr(ContextVar, "thread_inheritable"):
           var = ContextVar("var", thread_inheritable=True)
       else:
           var = ContextVar("var"# logic to fallback to global binding
     if hasattr(ContextVar, "thread_inheritable"):
           # Context-local and inherited by new threads.
           def _new_var(name, **kwargs):
               return ContextVar(name, thread_inheritable=True, **kwargs)
       elif getattr(sys.flags, "thread_inherit_context", False):
           # Threads inherit the whole context, so an ordinary
           # context variable is inherited too.
           _new_var = ContextVar
       else:
           # Keep the library's historical global semantics.
           _new_var = _GlobalVar

@nascheme
nascheme force-pushed the ctx_thread_inheritable_vars branch from f180876 to f38695a Compare July 24, 2026 18:46
@nascheme
nascheme marked this pull request as ready for review July 24, 2026 21:52
@nascheme
nascheme requested a review from 1st1 as a code owner July 24, 2026 21:52
@nascheme

Copy link
Copy Markdown
Member Author

Using a keyword has the problem that you could pass thread_inheritable=False. Should that be allowed or maybe it should just raise an error? Using a class-method avoids this concern.

@ngoldbaum

Copy link
Copy Markdown
Contributor

Using a keyword has the problem that you could pass thread_inheritable=False

I actually think this is useful, if you make thread_inheritable=None the default (current False) and then make thread_inheritable=False mean "never inherited AND exempt from the deprecation-warning snapshot". That gives libraries like asgiref the ability to opt out of the deprecation if they explicitly want the old behavior.

@nascheme

Copy link
Copy Markdown
Member Author

I switched this back to draft. Given discussion on Discourse, I think we should have the tri-value thread_inheritable keyword. This needs some more discussion to make sure we get the API correct.

@nascheme
nascheme marked this pull request as draft July 27, 2026 18:53
nascheme added 2 commits July 27, 2026 15:43
Use a keyword arg so that `ContextVar(thread_inheritable=False)` is
supported. The default is `thread_inheritable=None`, which means to use
the value of `thread_inherit_context`.  This allows libraries to
explicitly specify if they want a variable to be inherited by new
threads.

Add C function `PyContextVar_NewWithFlags` for creating context
variables which override automatic thread context inheritance.
@nascheme nascheme changed the title gh-154562: Add ContextVar.thread_inheritable(). gh-154562: Add ContextVar(thread_inheritable=...). Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants