Skip to content

state_exec/ariadne: async/await core — transparent (http-get) + proper await - #474

Merged
transfix merged 1 commit into
masterfrom
feat/ari-async-await-core
Sep 29, 2026
Merged

transfix merged 1 commit into
masterfrom
feat/ari-async-await-core

Conversation

@transfix

Copy link
Copy Markdown
Owner

Delivers a transparent (http-get URL) verb and a value-carrying (await …) — with zero evaluator edits. The suspend/resume-with-value mechanism already existed end to end (msg-recv proves it), so this factors it into one primitive and layers the DSL surface, rather than extending the stackless evaluator. First slice of the §13.8 async arc; builds on #468/#469/#472/#473 (all merged).

park_on_channel — the shared primitive

park_on_channel(sched, proc, pid, ch) (intrinsics.{h,cpp}) is msg-recv's body factored out (inbox → pending → receive_message → nil placeholder), which deliver_to_receivers later patches with the delivered value in the enclosing expression. Added guard: a park needs an enclosing frame, so a stepped top-level call (state.stack.size()==1) throws instead of leaving a done-and-waiting zombie (size 0 = a direct unit-test call, allowed). msg-recv becomes a thin caller — behaviour unchanged for every existing (nested) use.

Futures + proper await

A future handle is a tagged dict {"__future__": "<channel>"} — no new value_t alternative, so no codec/serialization ripple. (await x): a future parks value-carrying and resumes with the resolved value; an already-settled value keeps its one-frame cooperative yield (unchanged, so state_exec_async_test stays green). (http-get-async) now returns a future, so (await (http-get-async url)) works; msg-recv accepts a future or a channel string, so the two-step (msg-recv (http-get-async url)) from #473 still works verbatim.

Transparent (http-get URL [HEADERS])

The one-shot ergonomic verb (net_intrinsics.cpp): kicks the blocking cvc::net::send on the compute pool and self-parks via park_on_channel, reaching the scheduler through its captured &app (current_pid()/current_process() are valid around the evaluator step). So (get-attr (http-get url) "status") reads as one expression — no channel, no msg-recv. The send stays off the scheduler thread; the worker always posts a dict (an error dict on a throw), so a waiter never hangs. http-get-async is kept as the low-level fan-out primitive.

Tests

ariadne_runtime_test (AriadneNetIntrinsics) gains: transparent (http-get) → the dict + the bytes body round-tripping; (await (http-get-async …)) → resolves the future. The msg-recv/await refactor is regression-clean: ariadne_runtime_test 104, state_exec intrinsics 154 / async 71 / integration 85 / scheduler 108 / multiprocess 5; 0/20 flaky on the async cases. All offline via the set_http_client fake — no network. No platform code (native-only worker model until the wasm fetch path is confirmed).

Next in the arc

Async URI resolver for ari-generated requests (resolve_async, hybrid — http through the §13.9 cache), then render-pass park budget/safety (residents currently have no time budget).

…r await

Deliver a transparent (http-get URL) verb and a value-carrying (await …) with
ZERO evaluator edits: the suspend/resume-with-value mechanism already existed
(msg-recv proves it), so this factors it into one audited primitive and layers the
DSL surface on top.

park_on_channel(sched, proc, pid, ch) (intrinsics.{h,cpp}) is the shared "suspend
the running process, return a nil placeholder, resume with the value the scheduler
delivers into the enclosing expression" primitive. It is msg-recv's body factored
out (inbox -> pending -> receive_message -> nil), plus a top-level-call guard: a
park needs an enclosing frame to receive the patched value, so a stepped top-level
call (state.stack.size()==1) throws instead of leaving a done-and-waiting zombie
(size 0 is a direct unit-test call, allowed). msg-recv becomes a thin caller;
behavior is unchanged for every existing (nested) use.

Futures: a future handle is a tagged dict {"__future__": "<channel>"} (no new
value_t alternative, so no codec/serialization ripple). make_future/future_channel_of
are the shared convention. (await x): a future handle parks value-carrying and
resumes with the resolved value; an already-settled value keeps its one-frame
cooperative yield (unchanged). (http-get-async) now returns a future, so
(await (http-get-async url)) works; msg-recv accepts a future OR a channel string,
so the two-step (msg-recv (http-get-async url)) still works verbatim.

(http-get URL [HEADERS]) is the transparent one-shot (net_intrinsics.cpp): it kicks
the fetch on the compute pool and SELF-PARKS via park_on_channel, reaching the
scheduler through its captured &app (current_pid()/current_process() are valid
around the evaluator step) -- so (get-attr (http-get url) "status") reads as one
expression, no msg-recv. The blocking send stays off the scheduler thread and the
worker always posts a dict (an error dict on a throw), so a waiter never hangs.
http-get-async is kept as the low-level fan-out primitive.

Tests (ariadne_runtime_test AriadneNetIntrinsics): transparent (http-get) yields
the dict + the bytes body round-trips; (await (http-get-async …)) resolves the
future. The msg-recv/await refactor is regression-clean: ariadne_runtime 104,
state_exec intrinsics 154, async 71, integration 85, scheduler 108, multiprocess 5;
0/20 flaky on the async cases. Offline via the set_http_client fake.

First slice of the async arc (§13.8). Next: async URI resolver for ari-generated
requests, then render-pass park budget/safety.
@transfix
transfix merged commit f240a38 into master Sep 29, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant