writeonce/docs/active-slice-2026-08-23-chat-ws-lifecycle.md
shoney.arickathil 60414a1754 feat(runtime): the shutdown drain guarantee — iteration 40
A message sent before the stop flag is observed must be delivered and run
before the engine stops. One rule; a spin count could never express it.

- root cause in `shard_main` (runtime/src/vm.c): NEXT_RUNNABLE() already
  stated the contract — "a WORKER on stop keeps DRAINING ... so queued
  shutdown messages (close frames!) still run" — but the IDLE branch
  contradicted it, calling fib_reap_all and breaking on WO_IO_STOP,
  abandoning its inbox for wo_engine_stop() to free wholesale
- an actor between messages is exactly that idle case, which is why a WARM
  soak server hid it: warm shards held live fibers and took the right path
- fix: while the primary's drain window is open, an idle worker adopts its
  inbox and runs what arrives; sched_yield on an empty poll so a drain
  cannot burn a core per shard and starve the actors it exists to let run
- unreachable at WO_SHARDS=1: wo_engine_stop returns early at nshards <= 1

Measured:

- fresh-server SIGTERM drain: 5 of 16 failing before, 20 of 20 clean after
- `just chat` at the FULL 1000-client soak: 11 checks, 0 failures, both
  WO_IO backends, ASan clean with zero leaks
- the fd leg settled at scale too: 1000 connections left the count at 44,
  unchanged after 20 more — lazy per-shard init, not a leak
- runtime battery 36 suites (18 x both dispatch flavors) 0 fail;
  compiler 556 checks 0 fail

- story: docs/stories/language-runtime-database/40-shutdown-drain-guarantee.md
  (chain 3 with 31, status done), board row, slice marker updated
- outstanding and named: a pin below the gate needs new multithreaded test
  infrastructure — nothing in runtime/test/ drives wo_engine_start/stop and
  no corpus fixture can trigger a stop

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 23:43:45 +02:00

4.8 KiB
Raw Blame History

slice status
24 in-progress

Active slice — chat + actor lifecycle (iteration 24, absorbing 31 + 34)

Branch chat-ws-lifecycle. Spec: superpowers/specs/2026-08-23-chat-websocket-actor-lifecycle-design.md · plan: superpowers/plans/2026-08-23-chat-ws-lifecycle.md · board: stories/00-status.md.

Progress (2026-08-23)

  • ✅ T1 crypto (d14fa9f): sha1/sha256/hmac_sha256, ids 85–87, RFC vectors 18/0, corpus pin. Story 34's C-builtin resolution delivered.

  • ✅ T2 bounded mailboxes (92754a8): cap 1024 + WO_MAILBOX, sender-side atomic reserve, WO_T_ACTOR (trap 13) catchable. Plus a pre-existing compiler fix: try-arm Text places (bare e.msg) now copy before the arm's scope dies (was ASan use-after-free + SEGV).

  • ✅ T6 WS upgrade (79cfa01): ws_accept + accept-key + the 101 hijack sentinel; plain HTTP byte-identical (web-app 26/26).

  • ✅ T7 frame codec (7ad2ced): pure-.wo RFC 6455 parse/serialize, probe-verified against the RFC's own bytes.

  • ✅ T3 call/reply (ed69841): call parks + typed scalar reply (WO-E226 through actor-M erasure); actor DEATH landed with it — callers never hang (mid-call + to-dead both trap catchably). Fixed TRAPF's fiber-death leak/dangle en route.

  • ✅ T4 monitor + T5 time.after (56fe41a, ids 89/90): the lifecycle core. Corpus fixtures monitor-death, timer-delivery, timer-generation.

  • 🔄 T8 chat sample + T9 gate (6d729cc, then bbe0216): the sample and all five gate legs exist and run.

Every landed task: full battery 12/12, fresh-built.

Verified 2026-08-27 (branch merged up to master)

Merged master in (clean; the porch rename means chat now says use porch/... and its [deps] key is porch). Baseline on this branch: 18 runtime suites × both dispatch flavors, 0 fail, cli_smoke: OK.

just chat at CHAT_SOAK=20 — 11 of 12 legs green, including the two the plan required and the gate was missing (WO_SHARDS=1, WO_MAILBOX=8).

Three of the four failures found on 2026-08-27 were stale build artifacts, not code. Switching branches leaves compiler/_build/ and runtime/build/ holding the other branch's binaries: a woc emitting .wob v7 against a runtime expecting v6 reports only wovm: unsupported version 7, which the gate surfaces as "no listener". runtime/build/wovm_asan bit the same way. Rebuild both after any branch switch (just woc-build, make -C runtime wovm-asan) before believing a gate failure.

The remaining failure was a real bug and is now FIXED (iteration 40) — 2026-08-27-chat-drain-finding.md. On a fresh server the SIGTERM drain leaves a client at EOF with no close frame in 5 of 16 runs. Traced: main → Registry → Room → Writer; the Registry runs but the Room never processes its shutdown message, so the Writer's close branch never runs. Ruled out: the spin budget (a 1 s wall-clock deadline still failed 2 of 12), dummy_writer() spawning during shutdown, and write failure. The gate had been hiding it by draining a server the soak had already warmed.

Pending

  • ✅ The drain guarantee — FIXED, and split into its own iteration (40, chain 3 with 31). It was a runtime semantic, not a task in a sample's gate. Root cause: NEXT_RUNNABLE() already stated the contract — "a WORKER on stop keeps DRAINING … so queued shutdown messages (close frames!) still run" — but shard_main's IDLE branch contradicted it, reaping and breaking on WO_IO_STOP and abandoning its inbox for teardown to free. An actor between messages is exactly that idle case, which is why a warm soak server hid it. One branch now honours the primary's drain window, yielding on an empty poll so the drain cannot starve the actors it exists to let run. 20 of 20 fresh server drains clean, from 5 in 16 failing.
  • ⬜ T9 remainder — the 1k soak has only been run trimmed (CHAT_SOAK=20); run it at the default 1000 once the drain is fixed.
  • ⬜ T10 closeout — stories 24/31/34 → status: done with banners (note the scalar-reply v1 narrowing + three-argument monitor deviations), board standup entry, graph nodes, framework README ledger rows, runtime + chat CODE-LOGIC sections, delete this marker. Final battery.

This file is deleted when the slice lands (board convention). It lives flat in docs/ rather than a status folder — since 2026-08-26 no directory in this repo encodes state; status: above is the only place it is recorded.