fix(render): release sleep landing mid-frame never cleared output - #294
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe render thread limits the late-sleep shortcut to static output behavior. Capture transition tests now tolerate an optional startup no-op before validating expected activation and deactivation transitions. ChangesRender thread behavior
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to The change restricts late sleep to static output and makes capture assertions tolerate only the documented startup ordering; no merge-blocking regression remains evident. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Comment |
Three render-thread tests asserted a demand-gated source sees exactly [false, true, false]. The leading false is a race, not a contract: start_all clears the input manager's cached demand, and the input publication worker reconciles capture demand on its first pass, before the first frame publishes the scene's authoritative demand. When the worker wins it applies "inactive" to an already-inactive source; when the first frame wins, the first application is already true. Fast Windows runners take the second path and failed with [true, false]. A shared helper now requires exactly one activation followed by the deactivation and tolerates the optional startup no-op. Twenty back to back runs of all three tests pass after the change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou
e7d29c0 to
6de2f25
Compare
When the session goes to sleep after a frame's power snapshot was taken, the frame executor switches to a static sleep frame late: it writes one off-color frame to every device, publishes populated zones, and latches the sleep frame as pushed. That shortcut only satisfies the static off behavior. Under the release behavior the throttle path is what clears the published zones and stops driving devices, and it never ran because the latch told it the sleep frame was already out. Devices stayed held at the off color instead of being released, and the output-sleep test waited the full deadline for zones that never emptied, which is how the Windows lane surfaced it. The late switch now applies only to static sleep. A release sleep lets the admitted frame finish and the next frame takes the release path, one frame later than the static case. The output-sleep test also adopts the startup-order helper for its transition assertions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou
What this changes
Two Windows CI failures on PR #292, neither caused by that branch. One was a test asserting scheduler luck; the other was a real daemon bug the loaded runner exposed.
Release sleep landing mid-frame never released devices. When the session goes to sleep after a frame's power snapshot was taken, the frame executor switches to a static sleep frame late: it writes one off-colour frame to every device, publishes populated zones, and latches the sleep frame as pushed. That shortcut only satisfies the static off behaviour. Under the release behaviour the throttle path is what clears the published zones and stops driving devices, and it returns early once the latch is set, so it never ran. The late switch now applies only to static sleep; a release sleep lets the admitted frame finish and the next frame takes the release path, one frame later than the static case.
Capture-gating tests asserted the startup race. Four render-thread tests required a demand-gated source to see exactly
[false, true, false]. The leadingfalseis a race:start_allclears the input manager's cached demand and the input publication worker reconciles on its first pass, before the first frame publishes the scene's authoritative demand. When the worker wins it applies "inactive" to an already-inactive source; when the first frame wins, the first application is alreadytrue. Both orderings are correct, so a shared helper now requires one activation followed by deactivation and tolerates the optional startup no-op.Why
The first symptom was
output_sleep_keeps_reactive_input_capture_livetiming out at the full ten-second deadline waiting for the published zones to empty after a release sleep. Wider frames on a loaded runner widen the window in which the power flip lands mid-frame, so Windows CI hit it while Linux rarely does. Outside the test, a user whose session sleeps with the release behaviour could have devices held at the off colour instead of handed back.The second was
audio_capture_enabled_when_any_active_zone_is_reactivefailing with[true, false]; the same shape was recorded once on main on Aug 30 and re-run past.🛠️ How it works
should_switch_to_late_sleep_framenow takes the latest power state's effective off behaviour into account and answers true only for static sleep. Nothing else in the two late-sleep sites changes. The next frame seesoutput_power.sleeping()at the top, the sleep flag is still clear, andmaybe_sleep_throttleruns its release branch: clear zones, publish the cleared frame, latch, throttle.🧪 Validation
cargo test -p hypercolor-daemon --lib -- late_sleep: both predicate tests pass, including the new one asserting release sleep does not take the shortcut.cargo clippy -p hypercolor-daemon --features persistence-test-hooks --tests -- -D warnings: clean.The four gating tests (
output_sleep_keeps_reactive_input_capture_live,audio_capture_enabled_when_any_active_zone_is_reactive,render_thread_gates_audio_capture_to_audio_reactive_effects,render_thread_gates_screen_capture_to_screen_reactive_scene_groups) ran twenty times back to back after the change: 80 of 80 pass.Failing-before evidence is the PR feat(ci): ship releases without Apple signing credentials #292 Windows job: the release-sleep timeout on run 35020518159 and
[true, false]on the first attempt of run 35006142665. Neither reproduces on a lightly loaded Linux box, so the local proof is the predicate test plus the loop, not a reproduction of the CI timing.Added or updated tests
Added or updated docs (not applicable)
just verifypasses locally (daemon clippy with-D warningsand the affected test targets; the full recipe was not run)just denypasses (not applicable)just ui-testandjust ui-buildpass (not applicable)just sdk-lint,just sdk-check, andjust sdk-buildpass (not applicable)just python-verifypasses (not applicable)just compat-checkpasses (not applicable)just docs-buildpasses (not applicable)cd docs && zola checkpasses (not applicable)Packaging scripts were syntax-checked (not applicable)
just e2e-buildpasses with the normal Servo stack (not applicable)just e2e-build-cpupasses when validating the CPU smoke fallback (not applicable)just e2epasses against the Servo stack (not applicable)Tested on real hardware, simulator, or e2e harness (not exercised on a live rig; the sleep transition is covered by the integration test)
🔍 Review focus
Start with
should_switch_to_late_sleep_frameinframe_executor.rsand the two call sites that depend on it. The trade-off is one extra rendered frame reaching devices when a release sleep lands mid-frame, versus a device held at the off colour indefinitely. The startup no-op the test helper tolerates is a harmless idempotent call on a source that is not capturing; making the publication worker wait for the first authoritative demand would be a separate daemon change with its own trade-off.🤖 Generated with Claude Code
https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou
Summary by CodeRabbit
Bug Fixes
Tests