{"id":49278735,"url":"https://github.com/leostera/zart","last_synced_at":"2026-04-30T22:00:48.611Z","repository":{"id":353546319,"uuid":"1219859064","full_name":"leostera/zart","owner":"leostera","description":"zig actor runtime","archived":false,"fork":false,"pushed_at":"2026-04-25T21:29:57.000Z","size":326,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T19:32:27.203Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leostera.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":"leostera"}},"created_at":"2026-04-24T09:40:23.000Z","updated_at":"2026-04-27T13:55:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"21a7d4a1-c9a3-42b5-87f0-dc7735e54b82","html_url":"https://github.com/leostera/zart","commit_stats":null,"previous_names":["leostera/zart"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/leostera/zart","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leostera%2Fzart","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leostera%2Fzart/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leostera%2Fzart/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leostera%2Fzart/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leostera","download_url":"https://codeload.github.com/leostera/zart/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leostera%2Fzart/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32396781,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T19:38:08.556Z","status":"ssl_error","status_checked_at":"2026-04-28T19:37:55.688Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":"2026-04-25T17:32:45.647Z","updated_at":"2026-04-29T21:00:59.354Z","avatar_url":"https://github.com/leostera.png","language":"Zig","funding_links":["https://github.com/sponsors/leostera"],"categories":[],"sub_categories":[],"readme":"# zart\n\n`zart` is a small typed actor runtime for Zig.\n\nThe core idea is simple: spawning an actor returns an `Actor(Msg)` handle, and that handle is the typed capability used to send messages. Messages are structurally copied into the target actor's inbox; pointer fields remain references and keep their normal Zig ownership/lifetime rules.\n\n## Status\n\n`zart` is pre-release and under active development.\n\nImplemented today:\n\n- Typed `Actor(Msg)` handles.\n- Function actors and struct actors.\n- Message sends through `actor.send(msg)`.\n- Cooperative scheduling on stackful fibers.\n- `ctx.recv()` parking and `ctx.yield()` CPU checkpoints.\n- Configurable execution and I/O budgets.\n- Non-blocking actor I/O facade through runtime-provided `std.Io` drivers.\n- Default multicore scheduler with worker parking and work stealing.\n- kqueue-backed file and stream socket I/O on Apple/BSD targets through `zart.io.Default`.\n- POSIX `poll(2)` I/O backend available as `zart.io.PosixPoll`.\n- Runtime tracing hooks.\n- Stack slab pooling for actor fibers.\n- Explicit ABI-preserving fiber context switching on supported native targets.\n- Guard-page owning fiber stacks and opt-in stack high-water measurement.\n- Runnable examples under `examples/`.\n- Runtime tests and actor/fiber benchmarks.\n\nPlanned:\n\n- Monitors and links.\n- Lock-free SMP-ready mailboxes.\n- `spawn_blocking`.\n- Linux `io_uring` I/O backend.\n- Sanitizer/Valgrind fiber hooks.\n- WebAssembly backend via Asyncify/continuations.\n\n## Example\n\n```zig\nconst std = @import(\"std\");\nconst zart = @import(\"zart\");\n\nconst Reply = union(enum) {\n    value: u64,\n};\n\nconst CounterMsg = union(enum) {\n    inc: u64,\n    get: zart.Actor(Reply),\n    stop,\n};\n\nconst Counter = struct {\n    pub const Msg = CounterMsg;\n\n    initial: u64 = 0,\n\n    pub fn run(self: *@This(), ctx: *zart.Ctx(Msg)) !void {\n        var value = self.initial;\n\n        while (true) {\n            switch (try ctx.recv()) {\n                .inc =\u003e |n| value += n,\n                .get =\u003e |reply_to| try reply_to.send(.{ .value = value }),\n                .stop =\u003e return,\n            }\n        }\n    }\n};\n\nconst Collector = struct {\n    pub const Msg = Reply;\n\n    slot: *u64,\n\n    pub fn run(self: *@This(), ctx: *zart.Ctx(Msg)) !void {\n        switch (try ctx.recv()) {\n            .value =\u003e |n| self.slot.* = n,\n        }\n    }\n};\n\ntest \"counter actor\" {\n    var rt = try zart.Runtime.init(std.testing.allocator, .{});\n    defer rt.deinit();\n\n    var observed: u64 = 0;\n    const collector = try rt.spawn(Collector{ .slot = \u0026observed });\n    const counter = try rt.spawn(Counter{ .initial = 10 });\n\n    try counter.send(.{ .inc = 32 });\n    try counter.send(.{ .get = collector });\n    try counter.send(.stop);\n\n    try rt.run();\n\n    try std.testing.expectEqual(@as(u64, 42), observed);\n}\n```\n\nFunction actors are also supported when the parameter is typed as `*zart.Ctx(Msg)`:\n\n```zig\nfn worker(ctx: *zart.Ctx(CounterMsg)) !void {\n    while (true) {\n        switch (try ctx.recv()) {\n            .inc =\u003e {},\n            .get =\u003e {},\n            .stop =\u003e return,\n        }\n    }\n}\n```\n\n## Runtime Configuration\n\n`Runtime.Options` controls runtime policy and testability:\n\n```zig\nvar rt = try zart.Runtime.init(allocator, .{\n    .stack_size = 64 * 1024,\n    .stack_slab_size = 4 * 1024 * 1024,\n    .preallocate_stack_slab = true,\n    .preallocate_registry_slab = true,\n    .execution_budget = 64,\n    .io_budget = 64,\n    .worker_count = 0,\n    .internal_allocator = std.heap.smp_allocator,\n    .tracer = null,\n    .io = null,\n});\n```\n\nActors receive `ctx.allocator()` from the allocator passed to `Runtime.init`. Runtime internals use `internal_allocator`, defaulting to `std.heap.smp_allocator`.\n\n`worker_count = 0` uses the host logical CPU count. Set `worker_count = 1` for deterministic single-worker tests; the public execution API is still `rt.run()`.\n\nThe detailed runtime contract is documented in `docs/runtime-semantics.md`.\n\n## I/O\n\nActor I/O is non-blocking by runtime contract. `ctx.io()` returns a `std.Io` facade that preserves standard I/O call shapes while routing operations through a user-provided `zart.IoDriver`.\n\nThe driver must not block the scheduler. It can complete requests immediately or retain them and complete them later from a poller/event callback.\n\nThe default backend is selected at comptime through `zart.io.Default`:\n\n```zig\nvar actor_io = try zart.io.Default.init();\ndefer actor_io.deinit();\n\nvar rt = try zart.Runtime.init(allocator, .{\n    .io = actor_io.driver(),\n});\n```\n\nImplemented POSIX-style drivers retry non-blocking file and stream socket reads/writes internally. If an operation returns `WouldBlock`, the actor is parked until readiness is reported and then resumed with the completed result.\n\nBackend status:\n\n- Apple/BSD targets: `zart.io.Default` uses `kqueue`.\n- Linux: `zart.io.Uring` is reserved for the production backend but is not implemented yet.\n- Portable POSIX fallback: `zart.io.PosixPoll` is available explicitly.\n\nDo not route blocking filesystem or network calls through `ctx.io()`. `spawn_blocking` is planned for that class of work.\n\n## Fibers\n\nActors run on stackful fibers. `Fiber` is intentionally a low-level primitive: the fast path accepts caller-provided stack bytes and does not allocate. `Fiber.init` initializes a fiber in-place, and the `Fiber` object must stay at a stable address until `deinit`. This pinning requirement does not prevent scheduler-thread migration; it only means the fiber handle itself cannot be copied or moved after initialization because the prepared stack stores its address.\n\nSupported native backends are explicit:\n\n- `x86_64_sysv`: Linux, macOS, BSD-family, DragonFly.\n- `aarch64_aapcs64_basic`: Linux, macOS, BSD-family.\n\nThe context switch saves ABI-preserved state directly in an `extern struct`, through global assembly with a C ABI boundary. On x86_64 SysV it preserves `rsp`, `rip`, `rbx`, `rbp`, `r12`-`r15`, MXCSR control state, and the x87 control word. On AArch64 it preserves `x19`-`x29`, `sp`, `pc`, the switch function's `lr`, and the low 64-bit lanes of `d8`-`d15`; optional SVE/SME state is not supported.\n\nFor standalone fibers, prefer the guarded owning stack helper:\n\n```zig\nvar stack = try zart.Fiber.Stack.alloc(64 * 1024);\ndefer stack.deinit();\n\nvar fiber: zart.Fiber = undefined;\ntry stack.initFiber(\u0026fiber, entry, arg); // also enables stackHighWaterMark()\ndefer fiber.deinit();\n\n_ = try fiber.run();\n```\n\nCurrent Fiber limitations:\n\n- Raw caller-provided stack slices have no guard page. Use `Fiber.Stack.alloc` when a standalone fiber should trap on downward stack overflow.\n- No ASan/TSan fiber annotations or Valgrind stack registration yet.\n- No reliable unwinding/backtraces across fiber boundaries.\n- No CET shadow-stack integration on x86_64, and no explicit IBT/BTI/PAC landing-pad policy yet.\n- No C++ exception or `longjmp` crossing fiber boundaries.\n- No WebAssembly backend yet; Wasm needs an Asyncify/continuation backend, not native stack-pointer switching.\n\n`Fiber.deinit()` rejects suspended fibers and transitions valid fibers to `.deinitialized`; later `run()` returns `error.Deinitialized`. If a runtime intentionally discards a suspended stack without unwinding, it must call `abandonWithoutUnwind()` explicitly first; that transitions the fiber to `.abandoned`, and later `run()` returns `error.Abandoned`.\n\n## Tracing\n\nPass `Runtime.Options.tracer` to observe runtime events without changing actor code. When no tracer is configured, trace event construction is skipped at the call sites.\n\nEvents include actor spawned/resumed/waiting/yielded/completed/failed, message sent/received, and I/O submitted/completed.\n\n## Examples\n\nExamples live under `examples/` and are wired into the build:\n\n```sh\nzig build examples\nzig build example-counter\nzig build example-ping_pong\nzig build example-fan_out\nzig build example-cooperative_yield\nzig build example-tracing\nzig build example-file_io\nzig build example-http_server\n```\n\nThey cover typed request/reply actors, ping-pong messaging, fan-out aggregation, explicit `ctx.yield()` checkpoints, runtime tracing, `ctx.io()` file reads, and one-actor-per-request HTTP socket handling.\n\nThe HTTP server runs until interrupted by default:\n\n```sh\nzig build example-http_server -- --port 8080 --acceptors 4\ncurl http://127.0.0.1:8080/hello\n```\n\nFor smoke tests, limit the accept loop:\n\n```sh\nzig build example-http_server -- --port 8080 --max-requests 1\n```\n\n## Build\n\n```sh\nzig build\nzig build test\nzig build examples\nzig build bench -- --quick\nzig build bench -- --quick --warmup 5 --iterations 1000\nzig build bench -Doptimize=ReleaseFast -- --quick\n```\n\nThe benchmark runner reports min/max/avg/mean/median/stddev for each case using adaptive time units.\n\n## Package\n\nImport the module as `zart` from `src/root.zig`.\n\n```zig\nconst zart = @import(\"zart\");\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleostera%2Fzart","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleostera%2Fzart","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleostera%2Fzart/lists"}