Skip to content

ariadne: async (http-get-async) state_exec intrinsic (§13.8) - #473

Merged
transfix merged 1 commit into
masterfrom
feat/async-http-get-intrinsic
Sep 29, 2026
Merged

transfix merged 1 commit into
masterfrom
feat/async-http-get-intrinsic

Conversation

@transfix

Copy link
Copy Markdown
Owner

Adds register_net_intrinsics(cvc::app&) binding a program-lane verb (http-get-async URL [HEADERS]) that fetches over the cvc::net facade (#468) off the scheduler thread and hands the result to a DSL program through the async runtime — so a program can await an HTTP response without blocking the evaluator/host thread.

(get-attr (msg-recv (http-get-async "https://host/x")) "status")   ; => e.g. 200

Mechanism (the nav_compute pattern)

The verb runs the blocking cvc::net::send on app.computePool() via compute_async, returns immediately with a unique #-suffixed reply channel, and the worker posts the marshalled response to that channel through the one thread-safe seam, exec_scheduler().post_message. A program awaits with the existing (msg-recv <chan>), which parks the process (not the OS thread) and, when drain_ingress delivers, resumes with the value threaded into the enclosing expression. The reply is a dict { ok status body(bytes) url headers(list) error }; the body is bytes (the #469 bridge), so it round-trips byte-exact. The worker always posts a dict (an error dict on failure), so a parked recv never hangs.

Why two-step, not transparent (http-get url)

A host verb is a native_fn(span<value_t>) with no intrinsics_context at call time, so it cannot read the calling pid or self-park — only a core intrinsic like msg-recv can. So the verb submits + returns the channel and msg-recv does the parking. A transparent single-verb (http-get url) returning the body needs a new evaluator park-token hook (a follow-up). Registered host-level via register_action_intrinsics (it needs cvc::net + the app) — never a core state_exec builtin, since core must not depend on net/app. Guarded on CVC_STATE_EXEC + cvc::net::have_http_backend().

Tests

ariadne_runtime_test → AriadneNetIntrinsics, all offline via PR1's set_http_client fake:

0/20 flaky; the full ariadne_runtime_test (102) stays green. No platform-specific code, but the background-worker model is native-only until the wasm worker/-sASYNCIFY fetch path is confirmed.

Deferred (roadmap §13.8)

A transparent (http-get url) verb (new evaluator hook), chunked/streaming bodies, and the dedicated libcurl-multi I/O thread (today each in-flight fetch blocks one compute-pool worker). Independent of the §13.9 cache PRs (#468/#469/#472, all merged) — this is the async transport slice.

Add register_net_intrinsics(cvc::app&) (inc/cvc/ariadne/net_intrinsics.h),
binding a program-lane verb (http-get-async URL [HEADERS]) that fetches over the
cvc::net facade OFF the scheduler thread and hands the result back to a DSL
program via the async runtime -- so a program can await an HTTP response without
blocking the evaluator/host thread.

Mechanism (the nav_compute pattern): the verb runs the BLOCKING cvc::net::send on
app.computePool() via compute_async, returns immediately with a UNIQUE '#'-suffixed
reply channel, and the worker posts the marshalled response to that channel through
the one thread-safe seam, exec_scheduler().post_message. A program awaits with the
existing (msg-recv <chan>), which parks the process (not the OS thread) and, when
drain_ingress delivers, resumes with the value threaded into the enclosing
expression. The reply is a dict { ok status body(bytes) url headers(list) error };
the body is bytes (the state_exec bytes bridge), so it round-trips byte-exact. The
worker ALWAYS posts a dict (an error dict on a transport/arg failure), so a parked
recv never hangs.

  (get-attr (msg-recv (http-get-async "https://host/x")) "status")   ; => e.g. 200

Two-step (verb + msg-recv), not a transparent (http-get url) returning the body: a
host verb is a native_fn(span<value_t>) with no intrinsics_context at call time, so
it cannot read the calling pid or self-park -- only a core intrinsic like msg-recv
can. Transparent single-verb await needs a new evaluator hook (a follow-up). It is
registered host-level via register_action_intrinsics (it needs cvc::net + the app),
never a core state_exec builtin -- core must not depend on net/app. Guarded on
CVC_STATE_EXEC and cvc::net::have_http_backend().

Tests (ariadne_runtime_test, AriadneNetIntrinsics, offline via the set_http_client
fake): full-dict await (status/ok/error + the bytes body round-tripping through
state-data-set), error-path resume (a transport failure still resumes with an error
dict), and a BlockingHttpClient proof that drain() returns while the fetch is still
in flight (the scheduler thread is not blocked). 0/20 flaky; the full
ariadne_runtime_test (102) stays green.

Deferred (roadmap §13.8): a transparent (http-get url) verb, chunked/streaming
bodies, and the dedicated libcurl-multi I/O thread (today each in-flight fetch
blocks one compute-pool worker). Native only until the wasm worker/-sASYNCIFY fetch
path is confirmed.
@transfix
transfix merged commit ed71934 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