Skip to content

v0.2.11 — Params keepalive: fix sessions dying right after ready

Choose a tag to compare

@hthillman hthillman released this 03 Jun 00:53
· 47 commits to main since this release

The big one. In-the-wild report: "it's audio reactive but I can't access the Daydream side… looks like it tries three times and then stops."

Symptom

The session connected, uploaded the source, received ready + the initial buffer + stem_assets — then the pod closed the WebSocket before streaming a single generation slice (binary_frames_recv never exceeded 1, across every pod and every failover). Looked server-side; it wasn't.

Root cause (found by comparing against demon-public-demo)

The web client's useParamSync sends a {type:"params"} message every 8 ms after ready, and that continuous stream is the only thing keeping the pod's WebSocket alive — there is no separate keepalive. If the pod receives nothing after ready, it idle-times-out and closes.

demonTD's param flush lives in OnTick, driven by the tick8ms Timer CHOP — which has been silent in practice (the same TD callback gremlin that kept OnHeartbeat silent; the v0.2.6 onTimerPulse rename didn't fix it). So after ready, demonTD went completely silent → the pod idle-timed-out → dropped the connection → zero generation slices. The heartbeat survived only because v0.2.6 added a frame_exec fallback for it; OnTick had no such fallback.

Fixes

  1. MaybeTickFromFrame — drives OnTick from frame_exec (the reliable, verified-firing hook) on a ~33 ms floor when the Timer CHOP is silent. Restores the post-ready param stream that keeps the session alive.
  2. OnTick sends params every tick, not just on change — accumulates into a running snapshot and re-sends it with an advancing playback_pos, matching the web client. (Send-on-change-only went silent the moment the user stopped touching params, re-triggering the timeout.)
  3. Send-failure teardown — after 3 consecutive WS send failures the connection is declared dead and torn down once, instead of retrying forever (kills the ~1,500-line SSL: BAD_LENGTH flood that pegged CPU and blocked failover when a stream got corrupted).
  4. Heartbeat tolerates status=unknown — a transient/unparseable status poll no longer disconnects a live session; the WS closing is the authoritative "ended" signal.

Built on top of #3 (the onCook AttributeError guard). Confirmed live: generation slices stream and the session stays up for the full lease.

Verification

  • After ready, binary_frames_recv climbs past 1 and the session holds.
  • Debug ON: [tick] Timer CHOP appears silent — driving OnTick from frame_exec fallback appears once; params flow continuously after.
  • A dropped connection tears down cleanly instead of flooding SSL: BAD_LENGTH.

Tests: 72 passed. Drift: clean against demon-public-demo @ f2be73b. BUILD_MARKER = v0.2.11-params-keepalive; UA → DaydreamDEMON-TD/0.2.11.