Skip to content

Fix end-of-dream busyloop and harden resume/settings against bad values#666

Open
scottdraves wants to merge 1 commit into
masterfrom
fix-resume-busyloop-streaming-fallback
Open

Fix end-of-dream busyloop and harden resume/settings against bad values#666
scottdraves wants to merge 1 commit into
masterfrom
fix-resume-busyloop-streaming-fallback

Conversation

@scottdraves

Copy link
Copy Markdown
Contributor

Problem

When a dream ended with an empty cache and streaming disallowed (0 quota, fresh install), preflightNextDream(false) returned nullopt every frame and the player re-preflighted at ~60fps with no way to advance — a CPU/log busyloop.

The field trigger was a saved last_played_frame sitting on the clip's last frame: the resumed dream decoded ~1 frame, "finished" on arrival, and dropped straight into the non-streaming natural transition, which then spun.

Changes

  • PlaylistManager::preflightNextDream — the actual fix. When nothing is cached and streaming was disallowed, stream the next dream as a last resort instead of returning nullopt. Cached content is still preferred above this; it only fires when the cache is empty.
  • Player::SetPlaylistAtDream — clamp the resume frame to [0, frames-1] using the dream's metadata frame count, replacing the bogus 24h@60fps ceiling that let past-end values through. A far-past-end seek otherwise decodes zero frames and wedges the decoder (no frames available spin).
  • JSONStorage::GetOrSetValue — wrap the boost::json as_*() extraction in try/catch so a malformed or out-of-range setting (an integer too large for uint64 is parsed as a double) falls back to the default instead of throwing and terminating the process.

Verification

Tested against the live Windows client with the exact field repro and a robustness battery on last_played_frame:

last_played_frame Before After
7225 (last frame, 0 quota, empty cache) 60fps busyloop streams next dream, 1 preflight
-100 safe (→0) safe (→0)
50000 (past 7226-frame end) decoder hang (no frames available ×655) clamp→7225, instant-finish, streaming fallback
~1e18 safe (fake guard) clamp→last frame
~1e23 (exceeds uint64) crash 0xC0000409 settings falls back to default

Confirmed end-to-end (empty cache): resume past end → clamping to last frameDecoder ended with only 1 framesPreflight : no cached dreams available; streaming next dream at position 1 → next dream streams. No spin, no crash.

🤖 Generated with Claude Code

When a dream ended with an empty cache and streaming disallowed (0 quota,
fresh install), preflightNextDream(false) returned nullopt every frame and
the player re-preflighted at 60fps with no way to advance — a CPU/log
busyloop. The field trigger was a saved last_played_frame sitting on the
clip's last frame, so the resumed dream "finished" on arrival and dropped
straight into that non-streaming transition.

- PlaylistManager::preflightNextDream: when nothing is cached and streaming
  was disallowed, stream the next dream as a last resort instead of returning
  nullopt. Cached content is still preferred; this only fires when the cache
  is empty.
- Player::SetPlaylistAtDream: clamp the resume frame to [0, frames-1] using
  the dream's metadata frame count, replacing the bogus 24h@60fps ceiling that
  let past-end values through. A far-past-end seek otherwise decodes zero
  frames and wedges the decoder ("no frames available" spin); a value too
  large is parsed as a double and crashes the settings read (see below).
- JSONStorage::GetOrSetValue: wrap the boost::json as_*() extraction in
  try/catch so a malformed or out-of-range setting (an integer too large for
  uint64 is parsed as a double) falls back to the default instead of throwing
  and terminating the process.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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