{"id":13741743,"url":"https://github.com/rsepassi/zigcoro","last_synced_at":"2025-05-08T22:31:58.252Z","repository":{"id":190481595,"uuid":"682723132","full_name":"rsepassi/zigcoro","owner":"rsepassi","description":"A Zig coroutine library","archived":false,"fork":false,"pushed_at":"2025-04-19T07:31:02.000Z","size":152,"stargazers_count":260,"open_issues_count":7,"forks_count":20,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-05-06T03:43:46.068Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"0bsd","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rsepassi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-24T19:33:19.000Z","updated_at":"2025-05-05T11:34:27.000Z","dependencies_parsed_at":null,"dependency_job_id":"bd3393af-54bc-4074-9792-53717a085b56","html_url":"https://github.com/rsepassi/zigcoro","commit_stats":null,"previous_names":["rsepassi/zigcoro"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsepassi%2Fzigcoro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsepassi%2Fzigcoro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsepassi%2Fzigcoro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsepassi%2Fzigcoro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rsepassi","download_url":"https://codeload.github.com/rsepassi/zigcoro/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253157985,"owners_count":21863205,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T04:01:02.265Z","updated_at":"2025-05-08T22:31:58.202Z","avatar_url":"https://github.com/rsepassi.png","language":"Zig","funding_links":[],"categories":["Libraries","Zig"],"sub_categories":[],"readme":"# zigcoro\n\nAsync Zig as a library using stackful asymmetric coroutines.\n\nSupports async IO via [`libxev`][libxev].\n\n---\n\n[![test][ci-badge]][ci]\n\nCI tests against zig 0.14.0\n\nCoroutines supported on Windows `x86_64`, Linux {`x86_64`, `aarch64`, `riscv64`}, and Mac {`x86_64`, `aarch64`}.\n\nAsync IO supported on Linux {`x86_64`, `aarch64`}, and Mac {`x86_64`, `aarch64`}.\n\n## Depend\n\n`build.zig.zon`\n```zig\n.zigcoro = .{\n  .url = \"git+https://github.com/rsepassi/zigcoro#\u003ccommit hash\u003e\",\n  .hash = \"\u003chash\u003e\",\n},\n```\n\n`build.zig`\n```zig\nconst libcoro = b.dependency(\"zigcoro\", .{}).module(\"libcoro\");\nmy_lib.addImport(\"libcoro\", libcoro);\n```\n\n## Current status\n\n*Updated 2024/04/23*\n\nAlpha.\n\nAsync/await, suspend/resume, Channels, and async IO are all functional and (CI) tested.\n\nSee [future work](#future-work) for more.\n\n## Coroutine API\n\n```\n// High-level API\nxasync(func, args, stack)-\u003eFrameT\nxawait(FrameT)-\u003eT\nxframe()-\u003eFrame\nxresume(frame)\nxsuspend()\nxsuspendBlock(func, ptr)\n\nChannel(T, .{.capacity = n})\n  init(Executor)\n  send(T)\n  recv-\u003e?T\n  close()\nExecutor\n  init()\n  runSoon(Func)\n  tick()-\u003ebool\n\n// Optional thread-local environment\ninitEnv\n\n// Low-level API\n// Note: Frame = *Coro, FrameT = CoroT\nCoro\n  getStorage(T)-\u003e*T\nCoroT(func, opts)\n  frame()-\u003eFrame\n  xnextStart(frame)-\u003eYieldT\n  xnext(frame, inject)-\u003eYieldT\n  xnextEnd(frame, inject)-\u003eReturnT\n  xyield(yield)-\u003eInjectT\n  xreturned(frame)-\u003eReturnT\n\n// Stack utilities\nstackAlloc(allocator, size)-\u003e[]u8\nremainingStackSize()-\u003eusize\n```\n\n## Async IO API\n\n[`libcoro.asyncio`][aio] provides coroutine-based async IO functionality\nbuilding upon the event loop from [`libxev`][libxev],\nproviding coroutine-friendly wrappers to all the [high-level\nasync APIs][libxev-watchers].\n\nSee [`test_aio.zig`][test-aio] for usage examples.\n\n```\n// Executor\nExecutor\n  init(loop)\n\n// Top-level coroutine execution\nrun\n\n// Optional thread-local environment\ninitEnv\n\n// IO\nsleep\nTCP\n  accept\n  connect\n  read\n  write\n  close\n  shutdown\nUDP\n  read\n  write\n  close\nProcess\n  wait\nFile\n  read\n  pread\n  write\n  pwrite\n  close\nAsyncNotification\n  wait\n```\n\n## Switching to Zig's async/await\n\nIt's trivial to switch to Zig's async/await whenever it's ready.\n\nTranslation (zigcoro then Zig):\n\n```zig\n// async\nvar frame = try xasync(func, args, stack);\nvar frame = async func(args);\n\n// await\nconst res = xawait(frame);\nconst res = await frame;\n\n// @frame\nvar frame = xframe();\nvar frame = @frame();\n\n// suspend\nxsuspend();\nsuspend {}\n\n// suspend block\nxsuspendBlock(func, args);\nsuspend { func(args); }\n\n// resume\nxresume(frame);\nresume frame;\n\n// nosuspend\nasyncio.run(loop, func, args, stack)\nnosuspend asyncio.run(loop, func, args, null)\n\n// xev IO\n// No changes needed to the calls\ntry asyncio.sleep(loop, 10);\n```\n\nThe above assumes the Zig async API that was available in [Zig\n0.10.1][zig-async], which I expect (but do not know) to be similar in 0.12.0.\n\n## Performance\n\nI've done some simple benchmarking on the cost of context\nswitching and on pushing the number of coroutines. Further\ninvestigations on performance would be most welcome, as well\nas more realistic benchmarks.\n\n## Context switching\n\nThis benchmark measures the cost of a context switch from\none coroutine to another by bouncing back and forth between\n2 coroutines millions of times.\n\nFrom a run on an AMD Ryzen Threadripper PRO 5995WX:\n\n```\n\u003e zig env | grep target\n \"target\": \"x86_64-linux.5.19...5.19-gnu.2.19\"\n\n\u003e zig build benchmark -- --context_switch\nns/ctxswitch: 7\n```\n\nFrom a run on an M1 Mac Mini:\n\n```\n\u003e zig env | grep target\n \"target\": \"aarch64-macos.13.5...13.5-none\"\n\n\u003e zig build benchmark -- --context_switch\nns/ctxswitch: 17\n```\n\n## Coroutine count\n\nThis benchmark spawns a number of coroutines and iterates\nthrough them bouncing control back and forth, periodically\nlogging the cost of context switching. As you increase the\nnumber of coroutines, you'll notice a cliff in performance\nor OOM. This will be highly dependent on the amount of free\nmemory on the system.\n\nNote also that zigcoro's default stack size is 4096B, which\nis the typical size of a single page on many systems.\n\nFrom a run on an AMD Ryzen Threadripper PRO 5995WX:\n\n```\n\u003e zig env | grep target\n \"target\": \"x86_64-linux.5.19...5.19-gnu.2.19\"\n\n\u003e cat /proc/meminfo | head -n3\nMemTotal:       527970488 kB\nMemFree:        462149848 kB\nMemAvailable:   515031792 kB\n\n\u003e zig build benchmark -- --ncoros 1_000_000\nRunning benchmark ncoros\nRunning 1000000 coroutines for 1000 rounds\nns/ctxswitch: 57\n...\n\n\u003e zig build benchmark -- --ncoros 100_000_000\nRunning benchmark ncoros\nRunning 100000000 coroutines for 1000 rounds\nns/ctxswitch: 57\n...\n\n\u003e zig build benchmark -- --ncoros 200_000_000\nRunning benchmark ncoros\nRunning 200000000 coroutines for 1000 rounds\nerror: OutOfMemory\n```\n\nFrom a run on an M1 Mac Mini:\n```\n\u003e zig env | grep target\n \"target\": \"aarch64-macos.13.5...13.5-none\"\n\n\u003e system_profiler SPHardwareDataType | grep Memory\n  Memory: 8 GB\n\n\u003e zig build benchmark -- --ncoros 800_000\nRunning benchmark ncoros\nRunning 800000 coroutines for 1000 rounds\nns/ctxswitch: 26\n...\n\n\u003e zig build benchmark -- --ncoros 900_000\nRunning benchmark ncoros\nRunning 900000 coroutines for 1000 rounds\nns/ctxswitch: 233\n...\n```\n\n## Stackful asymmetric coroutines\n\n* Stackful: each coroutine has an explicitly allocated stack and\n  suspends/yields preserve the entire call stack of the coroutine. An\n  ergonomic \"stackless\" implementation would require language support and\n  that's what we expect to see with Zig's async functionality.\n* Asymmetric: coroutines are nested such that there is a \"caller\"/\"callee\"\n  relationship, starting with a root coroutine per thread. The caller coroutine\n  is the parent such that upon completion of the callee (the child coroutine),\n  control will transfer to the caller. Intermediate yields/suspends transfer\n  control to the last resuming coroutine.\n\nThe wonderful 2009 paper [\"Revisiting Coroutines\"][coropaper] describes the\npower of stackful asymmetric coroutines in particular and their various\napplications, including nonblocking IO.\n\n## Future work\n\nContributions welcome.\n\n* Documentation, code comments, examples\n* Improve/add allocators for reusable stacks (e.g. Buddy allocator)\n* Concurrent execution helpers (e.g. xawaitAsReady)\n* Add support for cancellation and timeouts\n* More aggressive stack reclamation\n* Libraries\n  * TLS, HTTP, WebSocket\n  * Actors\n  * Recursive data structure iterators\n  * Parsers\n* Multi-threading support\n* Alternative async IO loops (e.g. [libuv][libuv])\n* Debugging\n    * Coro names\n    * Tracing tools\n    * Verbose logging\n    * Dependency graphs\n    * Detect incomplete coroutines\n    * ASAN, TSAN, Valgrind support\n* C API\n* Broader architecture support\n  * risc-v\n  * 32-bit\n  * WASM (Asyncify?)\n  * comptime?\n\n## Inspirations\n\n* [\"Revisiting Coroutines\"][coropaper] by de Moura \u0026 Ierusalimschy\n* [Lua coroutines][lua-coro]\n* [\"Structured Concurrency\"][struccon] by Eric Niebler\n* https://github.com/edubart/minicoro\n* https://github.com/kurocha/coroutine\n* https://github.com/kprotty/zefi\n\n[coropaper]: https://dl.acm.org/doi/pdf/10.1145/1462166.1462167\n[ci]: https://github.com/rsepassi/zigcoro/actions/workflows/zig.yml?query=branch%3Amain\n[ci-badge]: https://github.com/rsepassi/zigcoro/actions/workflows/zig.yml/badge.svg?query=branch%3Amain\n[libxev]: https://github.com/mitchellh/libxev\n[libxev-watchers]: https://github.com/mitchellh/libxev/tree/main/src/watcher\n[libuv]: https://libuv.org\n[struccon]: https://ericniebler.com/2020/11/08/structured-concurrency\n[aio]: https://github.com/rsepassi/zigcoro/blob/main/src/aio_xev.zig\n[test-aio]: https://github.com/rsepassi/zigcoro/blob/main/src/test_aio.zig\n[lua-coro]: https://www.lua.org/pil/9.1.html\n[zig-async]: https://ziglang.org/documentation/0.10.1/#Async-Functions\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsepassi%2Fzigcoro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frsepassi%2Fzigcoro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsepassi%2Fzigcoro/lists"}