Modules/_contextvarsmodule.c
Modules/_contextvarsmodule.c is a thin C wrapper. It contains no data
structures and no algorithmic logic of its own. Its sole job is to register the
contextvars module and expose three public types (Context, ContextVar,
Token) plus two free functions (copy_context and run). All real
implementation lives in Python/context.c, which maintains the HAMT-backed
immutable snapshot model and defines PyContext_Type, PyContextVar_Type, and
PyContextToken_Type.
This split is intentional: Python/context.c is part of the core interpreter
and is always linked in, while Modules/_contextvarsmodule.c is the thin
import contextvars entry point that wires those internal types into the public
module namespace.
cpython 3.14 @ ab2d84fe1023/Modules/_contextvarsmodule.c
Map
| Lines (approx) | Symbol | Role |
|---|---|---|
| 1-25 | Includes | context.h, moduleobject.h, pycore_context.h |
| 26-70 | contextvars_copy_context | Wraps PyContext_CopyCurrent; the only free function besides run |
| 71-120 | contextvars_run_impl | Enters a context snapshot, runs a callable, restores the previous context |
| 121-170 | _contextvarsmodule_methods | Method table: copy_context and run |
| 171-230 | _contextvarsmodule_exec | Adds Context, ContextVar, Token to the module dict via PyModule_AddType |
| 231-265 | _contextvarsmodule_slots | Py_mod_exec slot array for multi-phase init |
| 266-300 | PyModuleDef + PyInit__contextvars | Module definition and multi-phase PyModuleDef_Init entry point |
Reading
contextvars_run_impl: running a callable in a context snapshot
The contextvars.run function is slightly more complex than copy_context
because it must enter the context, run the callable, and restore the previous
context even if the callable raises. The C implementation delegates to
PyContext_Enter and PyContext_Exit from Python/context.c.
// CPython: Modules/_contextvarsmodule.c:71 contextvars_run_impl
static PyObject *
contextvars_run_impl(PyObject *self, PyObject *ctx, PyObject *func,
PyObject *args, PyObject *kwargs)
{
if (PyContext_Enter(ctx) == -1)
return NULL;
PyObject *result = PyObject_Call(func, args, kwargs);
if (PyContext_Exit(ctx) == -1) {
Py_XDECREF(result);
return NULL;
}
return result;
}
The PyContext_Enter / PyContext_Exit pair in Python/context.c swaps the
per-thread ts->context pointer atomically under the GIL, so the running
callable and everything it calls see the snapshot's variable bindings.
copy_context: the module-level free function
copy_context() is the only free function in the module method table that does
not take a context argument. It snapshots the current thread's context by
calling PyContext_CopyCurrent, which performs a shallow copy of the HAMT
trie rooted at ts->context.
// CPython: Modules/_contextvarsmodule.c:26 contextvars_copy_context
static PyObject *
contextvars_copy_context(PyObject *self, PyObject *args)
{
if (!_PyArg_NoKeywords("copy_context", args))
return NULL;
return PyContext_CopyCurrent();
}
The returned Context object is independent of the running context. Mutations
via ContextVar.set inside the new context do not affect the original, because
each set produces a new HAMT root rather than mutating the existing trie.
Relationship to the PyContext* internal API
_contextvarsmodule.c only calls the public PyContext_* functions. The
internal _PyContext_* API (in Include/internal/pycore_context.h) is used
exclusively by Python/context.c itself and by the interpreter's frame
machinery that propagates context across generator and coroutine resume points.
The public API surface that this module relies on:
PyContext_CopyCurrent(used bycopy_context)PyContext_Enter,PyContext_Exit(used bycontextvars_run_impl)PyContext_Type,PyContextVar_Type,PyContextToken_Type(registered in_contextvarsmodule_exec)
Module exec: type registration and multi-phase init
The module uses the PEP 451 multi-phase init pattern. PyModuleDef_Init
returns a module spec rather than a fully initialised module object, and the
Py_mod_exec slot calls _contextvarsmodule_exec to populate the dict.
// CPython: Modules/_contextvarsmodule.c:171 _contextvarsmodule_exec
static int
_contextvarsmodule_exec(PyObject *m)
{
if (PyModule_AddType(m, &PyContext_Type) < 0)
return -1;
if (PyModule_AddType(m, &PyContextVar_Type) < 0)
return -1;
if (PyModule_AddType(m, &PyContextToken_Type) < 0)
return -1;
return 0;
}
// CPython: Modules/_contextvarsmodule.c:266 PyInit__contextvars
PyMODINIT_FUNC
PyInit__contextvars(void)
{
return PyModuleDef_Init(&_contextvarsmodule);
}
Multi-phase init means the module can be safely imported into sub-interpreters
that have independent type registries, because _contextvarsmodule_exec runs
once per interpreter rather than once per process.
gopy notes
Status: not yet ported. Planned package path: module/contextvars/.
The gopy port of this module is deliberately thin, mirroring CPython's structure:
module/contextvars/module.goregisters the module and wires upcopy_contextandrunas Go functions that call into the context implementation.- The
Context,ContextVar, andTokentypes are implemented inobjects/context.go(portingPython/context.c) alongside the HAMT implementation inobjects/hamt.go. contextvars_run_implmaps to a Go function that callsobjects.ContextEnter, defersobjects.ContextExit, then invokes the callable. The defer ensures cleanup even if the callable panics.copy_contextmaps to a call toobjects.ContextCopyCurrent, which returns a new*objects.Contextbacked by a shallow-copied HAMT root.- The
_PyContext_*internal API has no Go equivalent; all context operations go through the public function set inobjects/context.go.