Adds crates/weft-appd/src/compositor_client.rs: async Tokio client that connects to the
compositor's Unix socket (/weft/compositor.sock or WEFT_COMPOSITOR_SOCKET),
retrying every 2s on failure and 500ms on write error. Incoming CompositorToAppd frames are
decoded and logged (SurfaceReady, ClientDisconnected).
Wires compositor_tx into SessionRegistry. The supervise task now sends AppSurfaceCreated
(with child PID) immediately after process spawn, and AppSurfaceDestroyed when the process
exits. All three existing supervisor tests updated to pass None for compositor_tx.
Before this fix, TerminateApp sent while a process was waiting for its
READY signal was not acted on until the 30-second timeout fired.
abort_rx is now included in the tokio::select! that wraps wait_for_ready,
so the child is killed and AppState::Stopped broadcast as soon as the
abort is received, regardless of where in the startup sequence it fires.
test: supervisor_abort_during_startup_broadcasts_stopped
If WEFT_RUNTIME_BIN is set but the binary cannot be spawned (missing,
not executable, etc.), supervise() now transitions the session to
Stopped and broadcasts AppState::Stopped instead of returning an error
that left the session permanently stuck in Starting.
runtime.rs: the READY-timeout early-return path now broadcasts
AppState::Stopped before returning so WebSocket clients see the
session disappear when a module fails to signal readiness within 30s.
runtime.rs: after the child exits (natural exit or abort), supervise()
now broadcasts AppState { session_id, state: Stopped } in the same
lock scope as set_state. WebSocket clients receive the notification
without needing to poll QueryAppState or call TerminateApp.
wait_for_ready() now returns the BufReader<ChildStdout> with the READY
line already consumed. supervise() spawns drain_stdout() on that reader
so any subsequent module output is forwarded to the trace log and the
pipe buffer never fills up.
Without this, a long-running Wasm module that writes to stdout after
printing READY would eventually block waiting on a full pipe.
ipc.rs: AppReady { session_id, app_id: String }.
runtime.rs: supervise() passes app_id (already in scope as parameter)
when building the AppReady broadcast message.
main.rs: supervisor integration test updated to use .. to ignore
app_id in the AppReady pattern match.
SessionRegistry now tracks a oneshot abort sender per active session:
- abort_senders: HashMap<u64, oneshot::Sender<()>> field added.
- register_abort(session_id): creates the channel, stores the sender,
returns the receiver to the supervise task.
- terminate(): removes the session state AND drops the abort sender,
closing the channel and triggering the receiver in supervise.
runtime::supervise() now accepts abort_rx: oneshot::Receiver<()>:
- After the READY signal is received, the process-wait loop uses
tokio::select! on child.wait() vs abort_rx.
- On abort: logs intent, calls child.kill(), then sets state Stopped.
- On natural exit: logs exit status, sets state Stopped.
dispatch::LaunchApp: calls register_abort immediately after launch,
passes the receiver to the spawned supervise task.
Integration test updated to pass the abort receiver.
runtime.rs — process lifecycle manager:
- supervise(session_id, app_id, registry): spawns the weft-runtime child
process identified by WEFT_RUNTIME_BIN env var. If unset, logs debug
and returns immediately (no-op until runtime binary is available).
- Child process invoked as: <WEFT_RUNTIME_BIN> <app_id> <session_id>
with stdout/stderr piped, stdin closed.
- wait_for_ready(): reads stdout line-by-line; returns Ok(()) on first
line matching 'READY'; returns Err if stdout closes without it.
- 30-second READY_TIMEOUT via tokio::time::timeout; on expiry, kills
the child and transitions session to Stopped.
- On success: sets session state to Running, broadcasts AppReady to all
connected WebSocket clients via registry broadcast channel.
- drain_stderr(): async task that forwards child stderr lines to tracing
at WARN level for observability.
- On process exit: sets session state to Stopped regardless of exit code.
main.rs — wiring:
- SessionRegistry now owns broadcast::Sender<Response>; Default creates
the channel internally. Added set_state(), subscribe(), broadcast()
methods. Removed standalone broadcast_tx from run(); WS handlers
subscribe via registry.lock().await.subscribe().
- dispatch::LaunchApp spawns a tokio task calling runtime::supervise
immediately after creating the session. supervise is a no-op when
WEFT_RUNTIME_BIN is unset, so existing tests are unaffected.
Cargo.toml: added tokio 'process' and 'time' features.