Open ZeroIntensity opened 1 year ago
For anyone insane enough to try this before I do, here's some information on the C API:
Async Python stuff is handled entirely by the PyAwaitable
API. For more information on what that is, take a look at this discussion.
The "awaitable" in the PyAwaitable
API (created by PyAwaitable_New
) is a sort of proxy object that holds all coroutines submitted to it and holds a proper generator for it. Each coroutine holds a callback (which may be NULL
) to be called when the value is received. Then, PyAwaitable_AddAwait
takes four parameters:
PyAwaitable
objectNULL
)NULL
)Some other notable PyAwaitable
methods:
PyAwaitable_SetResult
, set's the return value for the awaitable (think of it like a return
in an async function in Python)PyAwaitable_SaveValues
, saves a variable number of PyObject*
values to be unpacked later. This allows use for in callback functions. This does increment and decrement the reference counts properly.PyAwaitable_SaveArbValues
, saves a variable number of void*
values to be used in callbacks. Does not handle reference counting as it can take any pointer and not just PyObject*
PyAwaitable_UnpackValues
, unpacks values saved by PyAwaitable_SaveValues
. Sets to borrowed references.PyAwaitable_UnpackArbValues
, unpacks values saved by PyAwaitabled_SaveArbValues
.PyAwaitable_AWAIT(awaitable, coro)
, equivalent to PyAwaitable_AddAwait(awaitable, coro, NULL, NULL)
An example of PyAwaitable
:
static int my_callback(PyObject* awaitable, PyObject* result) {
PyObject* some_value;
if (PyAwaitable_UnpackValues(awaitable, &some_value) < 0) { // unpack the some_value reference
return -1;
}
// some_value is now a borrowed reference to the some_value object saved earlier
PyObject_Print(result, stdout, Py_PRINT_RAW);
puts("");
return 0;
}
// pretend this is mapped to a my_func in Python
static PyObject* my_func(PyObject* self, PyObject* args) {
PyObject* awaitable_func;
PyObject* some_value;
if (!PyArg_ParseTuple(args, "OO", &awaitable_func, &some_value)
return NULL;
// assume that awaitable_func is indeed an awaitable function
PyObject* awaitable = PyAwaitable_New();
if (!awaitable) return NULL;
if (PyAwaitable_SaveValues(awaitable, 1, some_value) < 0) { // save a reference to some_value in awaitable
Py_DECREF(awaitable):
return NULL;
}
PyObject* coro = PyObject_CallNoArgs(awaitable_func);
if (!coro) {
Py_DECREF(awaitable);
return NULL;
}
if (PyAwaitable_AddAwait(awaitable, coro, my_callback, NULL) < 0) { // submit coro to the awaitable
// error occurred, decrement our references
Py_DECREF(coro);
Py_DECREF(awaitable);
return NULL;
}
return awaitable; // the awaitable must get returned to make the function usable via `await`
}
Oh boy, this is gonna be a big one.
We need a fast, ASGI server that is written entirely in C.