Skip to content

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

Open
nascheme wants to merge 2 commits into
python:mainfrom
nascheme:ctx_thread_inheritable_vars
Open

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

Conversation

@nascheme

@nascheme nascheme commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

Add contextvars.ContextVar.thread_inheritable(name, *, default), an alternative constructor for context variables whose bindings are inherited by new threading.Thread instances even when sys.flags.thread_inherit_context is false.

This gives libraries a middle ground between "threads inherit nothing" (the GIL-build default) and "threads inherit everything" (the -X thread_inherit_context=1 flag). A library converting module-level global state to context-local state can opt its own variables in to thread inheritance without requiring the application to enable the flag process-wide and without cooperation from the code that creates threads.

Behavior

  • When Thread.start() would otherwise start the thread with an empty context (flag false, no explicit context=), the new thread's context is instead initialized with the caller's current bindings of all thread-inheritable variables.
  • The seeded values are ordinary bindings: visible to copy_context(), independently settable in the child, and inherited transitively by threads the child starts.
  • Explicitly supplied contexts (Thread(context=...)) are unaffected, as are threads started by other means (_thread.start_new_thread(), C API).
  • When thread_inherit_context is true, a variable created this way behaves identically to a plain ContextVar, so the constructor is safe to use unconditionally.

Implementation

Each Context maintains a second HAMT holding the redundant subset of bindings belonging to thread-inheritable variables, kept in sync by ContextVar.set()/reset(). Thread.start() uses a private _contextvars._thread_start_context() factory that creates the child context directly from that subset, so thread startup cost is independent of how many variables are bound and unchanged when no inheritable variables exist (the subset is empty and shared).

ContextVar grows a var_thread_inheritable flag set only by the new classmethod; the plain constructor is unchanged. PyContext is publicly opaque, so the extra field does not affect the public C API.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants