feat: Add host-driven WASI event loop experiment - #8
Conversation
A poke now only records pending work. It never drains on the submitting stack. Pending work runs at a pump point: a top-level blocking wait, dispatch_main(), or a host turn through the registered scheduler. This gives every queue the semantics the Darwin main queue has. It is the direction akmorrison asked for on swiftlang#953. A poke never runs client code under a caller-held lock now, so the poke-defer hooks have no purpose. This removes the eight hook sites, the sync funnel split, the hook macros, and the defer depth counter. The shared files return to their upstream shape at those sites. Registration no longer has to happen before the first Dispatch use. Work recorded before registration is handed to the scheduler at registration. A pump that stops with runnable work left, for example a satisfied top-level wait, hands that work to a registered host too. Pokes from inside a drain or a harvest still do not request a turn: the running pump picks the work up, and perform decides after its last step. Tests: eager-drain.c becomes deferred-submission.c and pins the new contract. barrier-order.c, qos-order.c, and root-fairness.c pump through dispatch_main() and exit from their last block. sync-nested-async.c, async-and-wait.c, specific-destructor.c, smoke.c, nested-wait.c, nested-timed-wait.c, unsupported-source.c, and signal-disposition.c add a pump where they relied on eager progress. The late-registration reactor test becomes a positive test: work submitted before registration requests exactly one turn at registration and runs on it. Verified with the WASI suite: 57 of 57 with the Swift 6.3.1 toolchain and SDK, including the two Swift consumer tests. The four host event loop tests pass 50 repeats. Co-authored-by: Krzysztof Rodak <krodak.konta@gmail.com>
Rewrite the WASI semantics section of the test README around the pump points and the Darwin main-queue comparison. Describe the host loop SPI as the model, not as an experiment. Update the wait-pumping audit: the set_specific finding is no longer a hazard, and the bracket hooks are gone. Update the design note and its test list. Replace the execution-models PNG with an inline Mermaid block. The PNG showed the removed eager mode, and GitHub renders Mermaid blocks in Markdown. The host-turn lifecycle PNG is still accurate and stays. Co-authored-by: Krzysztof Rodak <krodak.konta@gmail.com>
|
Hi @krodak, thanks for this. It maps well onto what akmorrison asked for, and it is the direction we are taking. I pushed two commits on top of yours (457a699 and c32b54a). They make your model the only submission model instead of an opt-in. Revert either one if you disagree with any part of it. What changed:
One design rule is worth stating: on one thread every queue behaves like the Darwin main queue. Work runs when control returns to the host or during a Dispatch blocking wait. Code that submits work and then spins, sleeps, or blocks in a plain My plan from here:
Not done yet: a Linux neutrality build on this branch. The shared-code change is a return to upstream text, so the risk is low, but I will run it before swiftlang#953 moves. |
scottmarchant
left a comment
There was a problem hiding this comment.
It's probably fine to merge these pngs and diagrams to #3, but I'll scrub them out when I update the upstream PR.
I pushed some updated, there is a comment by AI about the changes and my plan. Please feel free to look through and revise
|
@scottmarchant changes looks good, merged to #3 , let me know if you needed any help with propagating these to upstream PR and 🤞🏻 for further review process 😄 |
Overview
This draft tests the event-loop model suggested by akmorrison on upstream PR swiftlang#953. The existing WASI backend drains work from a queue poke. In the registered mode introduced here, a poke requests one later host callback,
dispatch_asyncreturns, and the host calls back into Dispatch to perform work.The experiment is opt-in and targets the combined WASI branch so we can compare both models without rewriting the existing suite. Unregistered builds keep the current eager behavior. The SPI names and callback shape are open for discussion.
Review focus
performwhether a scheduler callback or timer caused the turn, or should the SPI hide that distinction?Links
What changed
private/private.h- adds an experimental WASI-only SPI for scheduler registration, step-budgeted perform calls, and relative timer delays.src/event/event_wasi.c- coalesces host wakeups, routes registered pokes out to the host, preserves eager draining when unregistered, and distinguishes scheduler turns from timer turns so one cannot consume the other's latch.tests/wasm/host-event-loop.candtests/wasm/run-wasi-host-event-loop-test.mjs- add a Node-driven WASI reactor that proves later-turn execution, initial wakeup coalescing, timer-only progress, retained scheduler turns, and named failures for inline callbacks and late registration.docs/wasi-host-event-loop/- documents the experiment and includes the Mermaid sources and rendered PNG diagrams.Fd readiness remains outside this experiment. Perform can harvest readiness already visible to WASI, but an idle JavaScript host still needs runtime-specific notification when an fd becomes ready. Blocking waits also retain the combined branch's current cooperative behavior.
Tested
cmake -S . -B build-wasi -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/WASI.cmake -DSWIFT_WASI_TOOLCHAIN_PATH="$HOME/Library/Developer/Toolchains/swift-6.3.3-RELEASE.xctoolchain" -DSWIFT_WASI_SDK_PATH="$HOME/Library/org.swift.swiftpm/swift-sdks/swift-6.3.3-RELEASE_wasm.artifactbundle/swift-6.3.3-RELEASE_wasm/wasm32-unknown-wasip1" -DENABLE_SWIFT=YES -DBUILD_TESTING=ONcmake --build build-wasictest --test-dir build-wasi --output-on-failurectest --test-dir build-wasi -R '^dispatch_wasi_host_event_loop$' --repeat until-fail:200 --output-on-failure