Skip to main content

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

LinesSymbolRolegopy
1-20_contextvars_copy_context_implImplements copy_context() by forwarding to PyContext_CopyCurrent().module/contextlib/
20-50_contextvars_methods, _contextvars_execMethod table (only copy_context) and exec slot: registers the three types.module/contextlib/
50-68_contextvars_slots, _contextvarsmodule, PyInit__contextvarsModule 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.