Skip to content

jco: scheduler stops delivering waitable events once a detached task holds in-flight imports across export calls #10

Description

@lann

Performance cost while this stands: the portable workaround (bounded clock polling instead of cross-task wakers, 5 ms method quantum / 10 ms pump tick) puts the composed endpoint echo handshake at ~21 ms wasmtime↔wasmtime versus ~4 ms for the spike's event-driven pump on the same wire and relay — roughly a 5× handshake latency cost paid on every host to keep behavior identical, reversible when this and the inter-task-wakeup gap below are fixed.

Found implementing the endpoint surface (#3); this is the defect that blocks the lann:iroh/endpoint jco target. Diagnosed on jco-transpile 0.5.2 (composed-call symptom also reproduced on 0.6.1).

The architecture that triggers it. endpoint.bind spawns a detached pump task (wit_bindgen::spawn_local) that permanently holds in-flight async imports (a websocket receive plus a clock tick), then returns. Any later export call — an async resource method, or even a second bind — never executes its first wasm slice: with JCO_DEBUG the new task's enter() runs and its bookkeeping completes, but no waitable events are delivered afterward; every task ends suspended and the instance is dead. Wasmtime interleaves the same component correctly (just matrix's endpoint-relay-wasmtime-wasmtime row).

What was ruled out along the way, each reproducing the hang independently until removed:

  • Cross-task wakers: wit-bindgen's inter-task-wakeup feature signals through a guest-internal unit stream; wasmtime delivers the write, jco never does (parked tasks sleep forever). The endpoint now uses bounded clock polling instead of cross-task wakers, so this is dodged — but it is its own jco gap worth reporting.
  • Racing a clock future against a condition as a fallback would cancel an in-flight import subtask, which jco traps on (jco-transpile 0.5.2 traps on subtask.cancel: guests must resolve in-flight imports before returning #6) — so polling loops must await each wait-for to completion, which the implementation does.
  • After both of those, the residual hang is the scheduler itself, as described above.

Also hit, separately: the wac-composed endpoint+demo component fails on jco with unexpected callee param count [1], _asyncStartCall invocation expected [5] at the demo→endpoint async cross-component call, on 0.5.2 and 0.6.1 both. So neither route (composed, or JS driving the surface directly) currently runs on jco.

State in-tree: the endpoint's jco leg is recorded as blocked in scripts/matrix.sh (comment) and the README; host-jco/src/run-endpoint.mjs is a complete JS consumer of the surface, ready to become the jco matrix row when this works. The spike demo's single-export architecture (everything inside one run call) is unaffected and stays green on jco — which localizes the defect to multi-export concurrency with live background tasks.

To do:

Related: #6, #7, #9 — the async/streaming corner of jco now accounts for every workaround in this repository.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions