22
33Async Zig as a library using stackful asymmetric coroutines.
44
5- * Stackful: each coroutine has an explicitly allocated stack and
6- suspends/yields preserve the entire call stack of the coroutine. An
7- ergonomic "stackless" implementation would require language support and
8- that's what we expect to see with Zig's async functionality.
9- * Asymmetric: coroutines are nested such that there is a "caller"/"callee"
10- relationship, starting with a root coroutine per thread. The caller coroutine
11- is the parent such that upon completion of the callee (the child coroutine),
12- control will transfer to the caller. Intermediate yields/suspends transfer
13- control to the last resuming coroutine.
14-
15- Async IO is provided by [ ` libxev ` ] [ libxev ] .
5+ Supports async IO via [ ` libxev ` ] [ libxev ] .
166
177---
188
@@ -23,18 +13,15 @@ supports {Linux, Mac} `aarch64`.*
2313
2414## Current status
2515
26- * Updated 2023/09/06 *
16+ * Updated 2023/09/08 *
2717
2818Alpha, WIP.
2919
30- Further exploring (structured) concurrency and cooperative multitasking atop
31- ` libxev ` using coroutines.
20+ Currently fleshing out async io atop ` libxev ` . See [ TODOs] ( #TODO ) for current work.
3221
3322## Coroutine API
3423
3524```
36- stackAlloc(allocator, size)->[]u8
37- remainingStackSize()->usize
3825xcurrent()->*Coro
3926xcurrentStorage(T)->*T
4027xresume(*coro)
4330 init(*func, *stack, ?*storage)
4431 getStorage(T)
4532CoroFunc(Fn)
46- init(.{args} )
47- initPtr(&fn, .{args})
48- coro(* stack)->Coro
49- xresumeStart( )->YieldT
50- xresume( inject)->YieldT
51- xresumeEnd( inject)->ReturnT
33+ init()
34+ coro(args, stack)->Coro
35+ coroPtr(func, args, stack)->Coro
36+ xnextStart(coro )->YieldT
37+ xnext(coro, inject)->YieldT
38+ xnextEnd(coro, inject)->ReturnT
5239 xyield(yield)->InjectT
53- StackCoro
54- init(*func, .{args}, *stack)
55- frame(*func, coro)->CoroFunc(Fn)
40+ xreturned(coro)->ReturnT
41+
42+ # Stack utilities
43+ stackAlloc(allocator, size)->[]u8
44+ remainingStackSize()->usize
5645```
5746
5847## Async IO API
5948
60- ` libcoro.xev.aio ` provides coroutine-friendly wrappers to all the [ high-level
61- async APIs] [ libxev-watchers ] in [ ` libxev ` ] [ libxev ] .
49+ [ ` libcoro.asyncio ` ] [ aio ] provides coroutine-based async IO functionality
50+ building upon the evented IO system of [ ` libxev ` ] [ libxev ] . It provides
51+ coroutine-friendly wrappers to all the [ high-level async
52+ APIs] [ libxev-watchers ] in [ ` libxev ` ] [ libxev ] .
6253
63- See
64- [ ` aio_test.zig ` ] ( https://github.com/rsepassi/zigcoro/blob/main/aio_test.zig )
65- for usage examples.
54+ See [ ` test_aio.zig ` ] [ test-aio ] for usage examples.
6655
6756```
57+ # Run top-level coroutines in the event loop
58+ run
59+ runCoro
60+
61+ # Concurrently run N coroutines and wait for all to complete
62+ xawait
63+
64+ # IO
6865sleep
6966TCP
7067 accept
@@ -89,17 +86,11 @@ Async
8986 wait
9087```
9188
92- Stackful asymmetric coroutines provide a clean way of wrapping up async IO
93- functionality, providing a programming model akin to threads (where the lightweight
94- versions are variously called Coroutines, Green Threads, or Fibers). Calls to
95- IO functionality are blocking from the perspective of the coroutine, but many
96- coroutines can be running on the same thread.
89+ The IO functions are run from within a coroutine and appear as blocking, but
90+ internally they suspend so that other coroutines can progress.
9791
98- Under the hood, what's required is async IO functionality, such that a coroutine
99- can submit work to be done, suspend, and then be resumed when the work is complete.
100- Libraries like [ libuv] [ libuv ] and [ libxev] [ libxev ] provide cross-platform async IO,
101- and this is a new Zig project, so why not depend on another new Zig project like
102- libxev?
92+ To run several coroutines concurrently, create the coroutines and pass them
93+ to ` asyncio.xawait ` .
10394
10495## Depend
10596
@@ -116,15 +107,6 @@ const libcoro = b.dependency("zigcoro", .{}).module("libcoro");
116107my_lib.addModule("libcoro", libcoro);
117108```
118109
119- ## Coroutine Examples
120-
121- * TODO: Fill back in*
122-
123- * resume, suspend
124- * storage
125- * args, return
126- * yield, inject
127-
128110## Performance
129111
130112I've done some simple benchmarking on the cost of context switching and on
@@ -216,17 +198,38 @@ ns/ctxswitch: 233
216198...
217199```
218200
201+ ## Stackful asymmetric coroutines
202+
203+ * Stackful: each coroutine has an explicitly allocated stack and
204+ suspends/yields preserve the entire call stack of the coroutine. An
205+ ergonomic "stackless" implementation would require language support and
206+ that's what we expect to see with Zig's async functionality.
207+ * Asymmetric: coroutines are nested such that there is a "caller"/"callee"
208+ relationship, starting with a root coroutine per thread. The caller coroutine
209+ is the parent such that upon completion of the callee (the child coroutine),
210+ control will transfer to the caller. Intermediate yields/suspends transfer
211+ control to the last resuming coroutine.
212+
213+ The wonderful 2009 paper [ "Revisiting Coroutines"] [ coropaper ] describes the
214+ power of stackful asymmetric coroutines in particular and their various
215+ applications, including nonblocking IO.
216+
219217## Future work
220218
221219Contributions welcome.
222220
221+ * Multi-threading support
222+ * Simple coro stack allocator, reusing stacks
223223* Libraries
224- * (WIP) Task library: schedulers, futures, cancellation
224+ * TLS, HTTP, WebSocket
225+ * Actors
225226 * Recursive data structure iterators
226227 * Parsers
228+ * Alternative async IO loops (e.g. libuv)
227229* Debugging
228230 * Coro names
229231 * Tracing tools
232+ * Dependency graphs
230233 * Detect incomplete coroutines
231234 * ASAN, TSAN, Valgrind support
232235* Make it so that it's as easy as possible to switch to Zig's async when it's
@@ -240,7 +243,11 @@ Contributions welcome.
240243
241244### TODO
242245
243- * Revisit CoroFunc state machine:
246+ * Concurrent execution with async/await-like semantics and helpers
247+ (waitAll, waitFirst, asReady, ...).
248+ * Cancellation and timeouts
249+ * Async iterators
250+ * Better coroutine error propagation
244251 * If a coroutine errors, it will be in the Done state and retval will be set
245252 to the error. The caller in that situation will have to test whether the
246253 coro is Done to call xreturned instead of xnext. The YieldT should probably
@@ -249,7 +256,8 @@ Contributions welcome.
249256## Inspirations
250257
251258* [ "Revisiting Coroutines"] [ coropaper ] by de Moura & Ierusalimschy
252- * [ Lua coroutines] ( https://www.lua.org/pil/9.1.html )
259+ * [ Lua coroutines] [ lua-coro ]
260+ * [ "Structured Concurrency"] [ struccon ] by Eric Niebler
253261* https://github.com/edubart/minicoro
254262* https://github.com/kurocha/coroutine
255263* https://github.com/kprotty/zefi
@@ -260,3 +268,7 @@ Contributions welcome.
260268[ libxev ] : https://github.com/mitchellh/libxev
261269[ libxev-watchers ] : https://github.com/mitchellh/libxev/tree/main/src/watcher
262270[ libuv ] : https://libuv.org
271+ [ struccon ] : https://ericniebler.com/2020/11/08/structured-concurrency
272+ [ aio ] : https://github.com/rsepassi/zigcoro/blob/main/src/asyncio.zig
273+ [ test-aio ] : https://github.com/rsepassi/zigcoro/blob/main/src/test_aio.zig
274+ [ lua-coro ] : https://www.lua.org/pil/9.1.html
0 commit comments