gh-154562: Add ContextVar.thread_inheritable().#154564
Conversation
Context variables created this way will automatically be inherited by the context for new threads, regardless of the setting of `thread_inherit_context`.
Documentation build overview
|
|
Another option would be to add a 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 |
f180876 to
f38695a
Compare
|
Using a keyword has the problem that you could pass |
I actually think this is useful, if you make |
Summary
Add
contextvars.ContextVar.thread_inheritable(name, *, default), an alternative constructor for context variables whose bindings are inherited by newthreading.Threadinstances even whensys.flags.thread_inherit_contextis false.This gives libraries a middle ground between "threads inherit nothing" (the GIL-build default) and "threads inherit everything" (the
-X thread_inherit_context=1flag). 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
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.copy_context(), independently settable in the child, and inherited transitively by threads the child starts.Thread(context=...)) are unaffected, as are threads started by other means (_thread.start_new_thread(), C API).thread_inherit_contextis true, a variable created this way behaves identically to a plainContextVar, so the constructor is safe to use unconditionally.Implementation
Each
Contextmaintains a second HAMT holding the redundant subset of bindings belonging to thread-inheritable variables, kept in sync byContextVar.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).ContextVargrows avar_thread_inheritable flagset only by the new classmethod; the plain constructor is unchanged.PyContextis publicly opaque, so the extra field does not affect the public C API.