Skip to content

fix: the SSE heartbeat tick stall — bounded fan-out + tick telemetry + FIN-closing frontdoor (#1798) - #1800

Merged
aarontrowbridge merged 14 commits into
live-test/stack-e2efrom
amico/issue-1798-tick-stall
Oct 15, 2026
Merged

aarontrowbridge merged 14 commits into
live-test/stack-e2efrom
amico/issue-1798-tick-stall

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

Closes #1798.

The stall, root-caused from a live capture: the frontdoor recycler killed upstream sockets FIN-lessly (#1757's Linux close-with-concurrent-reader hazard); the engine's SSE fan-out blocked writing to the corpse; every SSE route on the engine wedged while HTTP served at 2ms; the dead-man fired and manufactured MORE corpses (19,391 recycles in one window) — causal, fleet-propagated.

Both ends, either breaks the loop, both make it structural:

  • Engine (bounded fan-out): per-connection bounded write queues (64 chunks, offer bound = watchdogInterval/2); a stalled subscriber is DROPPED (SSE-DROP telemetry line + registry unregister) — a dead subscriber wedges nothing. RED-verified: the corpse test fails on the base (drops=0), greens with the fix.
  • Frontdoor (FIN closes): all 3 corpse-class close sites (the recycler's upstream kill — the corpse factory — member waiter teardown, _drop refit) now shutdown-then-close; 18 handler-owned sites inventoried and left as-is with reasons; the bare-close corpse class is pinned by a permanent control test (parked recv + bare close = no EOF ≥5s; shutdown+close = instant EOF).
  • Phase A telemetry (merged first): per-connection SSE-TICK lines (route/conn/fiber/queue depth/lag/RSS) + the out-of-tree SELF-STALL watchdog (raw Bun timer, proven to fire under a simulated Effect-tree stall and after runtime death) — the engine names its own death; the next occurrence is a diagnosis, not a mystery.

Director gates (own runs): scoped corpse/propagation/telemetry 15/0; engine session suite 599/5 (exact known baseline); frontdoor suite 51 OK (+4 tests, the bare-close control among them); drift PASS.

Stays DRAFT pending: the deploy decision (HITL — both surfaces: engine binary via the provenance ritual + the live frontdoor), then the 72h soak gate (zero stall windows from the cadence logs) per the approved spec.

…ibers (#1798 phase C)

The named mechanism: the platform response writer parks forever on a
FIN-less corpse (nodeResponse.write -> false, then a drain event the
corpse never produces) and the connection's whole recurring emission
wedges behind it while HTTP stays healthy. Bound the fan-out instead:
each connection emits through a bounded write queue with an offer
timeout (half a watchdog window, so the engine heals itself before the
SELF-STALL watchdog could declare the tick source silent). When the
offer bound trips, the subscriber is dead: noteDrop names it in the
Phase A telemetry registry (SSE-DROP line) and unregisters it, the
write queue is shut down, and the raw response + socket are destroyed
engine-side. A dead subscriber wedges nothing — every other connection
keeps its heartbeats and events.
…ut (#1798 phase C)

The corpse test simulates the frontdoor's #1757 FIN-less kill exactly:
an external socket with a 4KB kernel receive buffer, held open, blocked
in recv, never closed (bun's own sockets absorb multi-MB into userland
buffers before kernel backpressure reaches the engine's write path, so
the corpse must be external). It pins that a wedged corpse is dropped
(SSE-DROP line with the write queue pinned full, registry teardown)
while a healthy subscriber keeps its heartbeats and the SELF-STALL
watchdog never fires. The propagation test pins the wedge window
itself: with the corpse's tick source silent, the bus fan-out still
delivers events to the healthy subscriber and its Stream.tick keeps
cadence through the wedge and past the drop.
…ills the corpse class (#1798)

The #1757 hazard, fixed at the source: a bare close() on a socket with a
concurrent reader parked in recv (or a writer in sendall) sends no FIN on
Linux — the parked syscall holds the description open, the peer never sees
EOF, and the engine keeps writing into the corpse until its SSE fan-out
wedges (the #1798 fleet-wide heartbeat-stall propagation; 19,391 FIN-less
recycles in the observed window). The engine-side half cannot fix this at
the wire (BUN's node:http sends no FIN), so the frontdoor's shutdown is the
engine's only EOF signal.

- _fin_close: shutdown(SHUT_RDWR) then close — FIN out immediately, parked
  syscall woken; every lifecycle close that can race a concurrent socket
  user goes through it
- wired: the dead-man recycler's upstream kill (was a bare s.close()),
  the per-member waiter teardown (was a bare c.close()), and _drop refit
  onto the shared helper (behavior unchanged from its #1757 shutdown)
- the #1757 comment in _drop now records the fix, not just the hazard

Tests: the runner test drives the real module's _fin_close under the exact
#1757 condition (blocked reader, EOF at the peer; RED pre-fix); the
bare-close control pins the corpse as real under plain close; the wedge
fixture now probes for the frontdoor's FIN end-to-end (recycle -> engine
sees EOF); the member-teardown test proves the FIN is additive (normal
drain keeps the group healthy). Suite: 51 tests, OK (skipped=1).
@coderabbitai

coderabbitai Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aarontrowbridge
aarontrowbridge merged commit 0e490f7 into live-test/stack-e2e Oct 15, 2026
10 of 12 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