Skip to main content

_asynciomodule.c - asyncio C accelerator

Modules/_asynciomodule.c is the C accelerator for asyncio.futures and asyncio.tasks. Python falls back to the pure-Python equivalents in Lib/asyncio/futures.py and Lib/asyncio/tasks.py when this module is absent. The file registers _asyncio.Future, _asyncio.Task, and _asyncio.current_task among others.

Map

SymbolKindLines (approx)Purpose
FutureObjstruct1-80C layout for asyncio.Future: state enum, result/exception slots, callbacks list, loop ref
TaskObjstruct81-130Extends FutureObj with coro, context, fut_waiter, step handle
future_initfunction200-260Validates running loop, zeroes state to PENDING
future_set_resultfunction320-370Guards against non-PENDING state, stores result, schedules callbacks
future_set_exceptionfunction380-440Validates exception type (no StopIteration), stores, schedules callbacks
future_schedule_callbacksfunction450-490Iterates callback list, calls loop.call_soon for each entry
task_step_implfunction900-1020Core coroutine driver: calls coro.send(value) or coro.throw(exc), handles StopIteration as return
task_stepfunction1025-1060Clears fut_waiter, restores context, delegates to task_step_impl
task_wakeupfunction1065-1100Future done-callback that extracts result/exception and re-enters task_step
task_eager_startfunction1400-14803.14 eager factory: runs first coroutine step synchronously before returning Task
_asyncio_current_task_implfunction1800-1830Returns running task for current thread state
module_execfunction2900-2970Adds types and state-machine constants to the module dict

Reading

FutureObj and the state machine

Every asyncio.Future is backed by a FutureObj:

/* Modules/_asynciomodule.c:42 FutureObj */
typedef struct {
PyObject_HEAD
PyObject *fut_loop;
PyObject *fut_callbacks;
PyObject *fut_exception;
PyObject *fut_result;
PyObject *fut_source_tb;
fut_state fut_state; /* PENDING, CANCELLED, FINISHED */
int fut_log_tb;
int fut_blocking;
PyObject *dict;
PyObject *fut_weakreflist;
pyfutureobj_t *fut_cancel_message;
} FutureObj;

State transitions are strict. future_set_result raises InvalidStateError if fut_state != PENDING. After storing the result it calls future_schedule_callbacks, which iterates fut_callbacks and posts each entry via loop.call_soon. Callbacks are cleared from the list immediately so a second add_done_callback after completion goes directly to call_soon.

task_step_impl: the coroutine send loop

task_step_impl is the heart of cooperative scheduling:

/* Modules/_asynciomodule.c:912 task_step_impl */
static PyObject *
task_step_impl(TaskObj *task, PyObject *exc)
{
PyObject *result;
if (exc == NULL) {
result = _PyObject_CallMethodIdOneArg(task->task_coro,
&PyId_send, Py_None);
} else {
result = _PyObject_CallMethodIdOneArg(task->task_coro,
&PyId_throw, exc);
}
if (result != NULL) {
/* coroutine yielded a Future-like */
...
PyObject *o = PyObject_GetAttrString(result, "_asyncio_future_blocking");
...
} else if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
/* coroutine returned */
PyObject *val = ((PyStopIterationObject *)exc)->value;
future_set_result((FutureObj *)task, val);
} else {
future_set_exception((FutureObj *)task, PyErr_GetRaisedException());
}
}

When the coroutine yields a Future, task_step_impl sets fut_blocking = 0 on that future and registers task_wakeup as a done-callback. task_wakeup fires when the awaited future settles, extracting its result or exception and calling task_step again.

3.14 eager task factory

CPython 3.12 introduced loop.set_task_factory with an eager variant. 3.14 consolidates this into task_eager_start:

/* Modules/_asynciomodule.c:1408 task_eager_start */
static PyObject *
task_eager_start(TaskObj *task)
{
/* Run the first step synchronously. */
if (task_step_impl(task, NULL) == NULL) {
...
}
if (task->task_state != TASK_STATE_PENDING) {
/* Task finished in first step - return it directly */
return (PyObject *)task;
}
/* Not done yet - schedule normally */
return _PyObject_CallMethodIdNoArgs(task->task_loop, &PyId_call_soon, ...);
}

The eager factory avoids one event-loop round-trip for coroutines that complete synchronously (common with cache hits or already-resolved futures).

gopy notes

  • FutureObj maps naturally to a Go struct; the fut_state enum becomes a Go int32 with atomic access for thread safety.
  • task_step_impl is the trickiest part to port. In gopy the coroutine is a Go generator-style object; send becomes a method call returning (value, error). StopIteration maps to io.EOF or a sentinel error.
  • fut_callbacks uses a Python list. The gopy port uses a []CallbackEntry slice; no refcount overhead.
  • The eager factory requires that the first task_step be synchronous. gopy achieves this by calling TaskStep directly in the factory before handing off to the scheduler.
  • _asyncio_current_task_impl reads per-thread state. In gopy this becomes a goroutine-local stored in the ThreadState struct passed through the call stack.