Python/_contextvars.c
cpython 3.14 @ ab2d84fe1023/Python/_contextvars.c
_contextvars.c is intentionally minimal. It is a multi-phase module that
does three things on exec: registers PyContext_Type, PyContextVar_Type,
and PyContextToken_Type with the module, and exposes copy_context() as the
only module-level function. The actual type implementations are in
Python/context.c, which is compiled directly into the interpreter core.
The separation exists because the context-variable machinery must be available
before any module import machinery runs, so it cannot live in an extension
module. _contextvars.c is therefore only the public-name entry point; it
delegates every call to the core C API (PyContext_CopyCurrent, etc.).
The public contextvars module (Lib/contextvars.py) re-exports names from
_contextvars, so importing contextvars in Python code ultimately calls
into the types defined in Python/context.c.
Map
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-20 | _contextvars_copy_context_impl | Implements copy_context() by forwarding to PyContext_CopyCurrent(). | module/contextlib/ |
| 20-50 | _contextvars_methods, _contextvars_exec | Method table (only copy_context) and exec slot: registers the three types. | module/contextlib/ |
| 50-68 | _contextvars_slots, _contextvarsmodule, PyInit__contextvars | Module definition, multi-phase init slots, and the export symbol. | module/contextlib/ |
Reading
Module type registration (lines 31 to 43)
cpython 3.14 @ ab2d84fe1023/Python/_contextvars.c#L31-43
The exec function is the only non-trivial code in this file:
static int
_contextvars_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;
}
The three type objects (PyContext_Type, PyContextVar_Type,
PyContextToken_Type) are defined as global variables inside
Python/context.c and are therefore already fully initialized when this exec
function runs. PyModule_AddType adds them under their short names (Context,
ContextVar, Token).
PyContextVar_Get / PyContextVar_Set (Python/context.c lines 274 to 367)
The lookup path for ContextVar.get() reads the current context from the
thread state, queries the HAMT for the variable's key, and falls back to the
per-variable default if the key is absent. A per-variable cache avoids the
HAMT lookup on repeated reads within the same context version:
int
PyContextVar_Get(PyObject *ovar, PyObject *def, PyObject **val)
{
PyContextVar *var = (PyContextVar *)ovar;
PyThreadState *ts = _PyThreadState_GET();
if (ts->context == NULL) {
goto not_found;
}
/* Fast path: cached value for the current context version */
if (var->var_cached != NULL &&
var->var_cached_tsid == ts->id &&
var->var_cached_tsver == ts->context_ver)
{
*val = var->var_cached;
goto found;
}
PyHamtObject *vars = ((PyContext *)ts->context)->ctx_vars;
PyObject *found = NULL;
int res = _PyHamt_Find(vars, (PyObject*)var, &found);
if (res == 1) {
var->var_cached = found;
var->var_cached_tsid = ts->id;
var->var_cached_tsver = ts->context_ver;
*val = found;
goto found;
}
...
}
PyContextVar_Set creates a new Token, mutates the current context's HAMT by
inserting the new value (HAMTs are persistent, so this produces a new root
without affecting other contexts that share the old root), and returns the
token. The token records the previous value so that reset() can undo the
assignment:
PyObject *
PyContextVar_Set(PyObject *ovar, PyObject *val)
{
PyContextVar *var = (PyContextVar *)ovar;
PyContext *ctx = context_get();
PyObject *old_val = NULL;
int found = _PyHamt_Find(ctx->ctx_vars, (PyObject *)var, &old_val);
Py_XINCREF(old_val);
PyContextToken *tok = token_new(ctx, var, old_val);
Py_XDECREF(old_val);
if (contextvar_set(var, val)) {
Py_DECREF(tok);
return NULL;
}
return (PyObject *)tok;
}
gopy mirror
module/contextlib/ carries the port. The HAMT (Persistent Hash Array Mapped
Trie) that backs Context.ctx_vars is ported in objects/ as the internal
map type. ContextVar.get() mirrors the thread-state lookup using Go's
goroutine-local equivalent, with the same cache-on-tsver optimization. The
Token one-shot guard (tok_used) maps to a boolean field checked before
reset() proceeds.