fix(viewer): wait for a connected display on eglfs so headless pi4 doesn't crash-loop - #2962
Merged
Merged
Conversation
…esn't crash-loop Pi 4 renders under QT_QPA_PLATFORM=eglfs (since #2904), but the headless-display guard added in #2959 only covers the linuxfb /dev/fb0 path (pi2/pi3). On a headless eglfs board Qt's eglfs plugin finds no connected DRM connector, logs "no screens available", and exits before the viewer emits its D-Bus handshake — so load_browser() raises and the viewer container crash-loops. Confirmed on 4 live pi4 devices running 2026.5.2. - Add wait_for_eglfs_display(): before the KMS-card detection, wait until a DRM connector reads "connected" (or "unknown"); a genuinely headless board idles quietly and self-heals on hotplug, mirroring the linuxfb /dev/fb0 wait. - Correct stale comments that still claimed Pi 4 runs under linuxfb. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
There was a problem hiding this comment.
Copilot wasn't able to review any files in this pull request.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
5 tasks
vpetersson
added a commit
that referenced
this pull request
Jun 1, 2026
…#2968) #2962 added wait_for_eglfs_display so a screenless eglfs (Pi 4) board waits for a display instead of crash-looping on Qt's "no screens available". eglfs_has_display() treated any connector status other than "disconnected" as a present display (to tolerate bridges that report "unknown"). balenaOS 2026.x exposes a KMS `card0-Writeback-1` virtual connector that ALWAYS reports "unknown". On a headless Pi 4 (both HDMI ports "disconnected") the writeback connector's "unknown" satisfied the guard, so it skipped the wait, launched eglfs, and the viewer crash-looped on "no screens available" / "AnthiasViewer exited before emitting D-Bus handshake" — exactly the failure #2962 was meant to prevent. Confirmed on multiple live pi4 on 2026.1.0 (card0-HDMI-A-1/-2 = disconnected, card0-Writeback-1 = unknown). Skip `*Writeback*` connectors so only real display outputs (HDMI/DSI/DP/…) count. A genuinely headless board now waits gracefully; the bridge-"unknown" hedge is preserved for real connectors. Verified locally for headless, connected, and bridge-unknown layouts. Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
vpetersson
added a commit
that referenced
this pull request
Jun 2, 2026
…self-heals (#2969) * fix(viewer): skip the Writeback connector in the eglfs headless guard #2962 added wait_for_eglfs_display so a screenless eglfs (Pi 4) board waits for a display instead of crash-looping on Qt's "no screens available". eglfs_has_display() treated any connector status other than "disconnected" as a present display (to tolerate bridges that report "unknown"). balenaOS 2026.x exposes a KMS `card0-Writeback-1` virtual connector that ALWAYS reports "unknown". On a headless Pi 4 (both HDMI ports "disconnected") the writeback connector's "unknown" satisfied the guard, so it skipped the wait, launched eglfs, and the viewer crash-looped on "no screens available" / "AnthiasViewer exited before emitting D-Bus handshake" — exactly the failure #2962 was meant to prevent. Confirmed on multiple live pi4 on 2026.1.0 (card0-HDMI-A-1/-2 = disconnected, card0-Writeback-1 = unknown). Skip `*Writeback*` connectors so only real display outputs (HDMI/DSI/DP/…) count. A genuinely headless board now waits gracefully; the bridge-"unknown" hedge is preserved for real connectors. Verified locally for headless, connected, and bridge-unknown layouts. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(viewer): retry AnthiasViewer spawn so armv7 WebEngine-init crash self-heals - Wrap the AnthiasViewer launch in a capped exponential-backoff retry loop (BROWSER_SPAWN_MAX_ATTEMPTS) instead of raising on the first failed handshake - Convert the tight container restart loop on Pi 2/Pi 3 into an in-process retry that self-heals on a later launch - Publish viewer:webview_status to Redis (retrying/failed) so a stuck board is distinguishable from an empty playlist - Add WebviewLaunchError + _spawn_webview_once helper; throttle repeat warnings to avoid flooding journald - Cover retry-then-succeed and exhaust-then-raise paths in tests - Document the armv7 WebEngine-init crash + retry stop-gap in docs/board-enablement.md The 32-bit Qt5 viewer intermittently aborts during Chromium/WebEngine init (malloc(): unaligned tcache chunk detected) ~75-90% of launches; reproduced on a 64-bit Pi 3B+. No userspace mitigation fixes the corruption, but a fresh launch clears it ~10-25% of the time, so retrying catches a good launch within a few attempts (validated on-device: handshake on attempt 6). Clean fix is arm64/Qt6 on 64-bit OS. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(viewer): address review — bound status-beacon Redis, funnel CommandNotFound - Give the webview health beacon a dedicated Redis client with short socket timeouts (connect_to_redis gains opt-in timeout params, defaulting to the historical blocking behaviour) so a Redis stall can't hang viewer startup inside the spawn-retry loop - Wrap sh.CommandNotFound into WebviewLaunchError in _spawn_webview_once so a missing binary is reported + handled on the same path as every other launch failure instead of escaping the retry loop - Reword the board-enablement note so it describes the WebEngine-init observation without referencing a --no-sandbox flag the viewer doesn't receive - conftest: accept the new connect_to_redis kwargs in the fake factory Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(viewer): chain final launch error; harden second redis test patch - Chain the exhausted-retries WebviewLaunchError from last_error so the traceback preserves the underlying failure (timeout / early-exit / wrapped CommandNotFound) - conftest: the autouse _mock_redis fixture's connect_to_redis patch now accepts *args, **kwargs too (matches the import-time patch) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(viewer): scope spawn-retry by call site; drop write-only status beacon Addresses self-review findings on the retry mechanism: - Mid-playback respawn (view_image/view_webpage, on the asset_loop thread) now uses a small, short budget (BROWSER_SPAWN_INLINE_*) so a persistent crash can't freeze the loop (no rotations/skips/standby, watchdog starved) for minutes; startup keeps the generous budget. A persistent mid-run failure raises and the container restart re-rolls. - Permanent failures (missing binary) raise WebviewBinaryMissingError and short-circuit the retry instead of burning the full backoff budget. - _spawn_webview_once now reaps the terminated process (SIGTERM, wait, SIGKILL) on the handshake-timeout path so a retry can't overlap two AnthiasViewers contending for the framebuffer / D-Bus name. - Reset the stale `browser` global before re-spawning. - Poll spawned process every 0.25s (was 1s) so a fast init crash is noticed promptly in the retry loop. - Drop the write-only viewer:webview_status Redis beacon (no reader existed) and revert the connect_to_redis timeout-param widening + conftest churn; operator-visible status is the throttled log output. - Tests: cover early-exit, terminate-on-timeout, missing-binary short-circuit, backoff growth, and the inline budget cap. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(viewer): clamp max_attempts to >=1; correct retry-logging comment - Guard load_browser against a non-positive max_attempts (would skip the loop and raise a confusing "0 attempts; last error: None") - Reword the comment: the first failure logs its reason AND a retry line, so it's not literally "one log line per attempt" Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * fix(viewer): clamp backoff_cap and startup_timeout in load_browser - A backoff_cap below 1s would devolve into a tight retry loop; a negative one would make sleep() raise ValueError mid-retry and mask the real launch error - Clamp a negative startup_timeout to 0 (immediate-timeout attempt) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Issues Fixed
Headless Pi 4 devices crash-loop the viewer on 2026.5.2. The headless-display guard from #2959 only covers the linuxfb
/dev/fb0path (pi2/pi3); Pi 4 renders underQT_QPA_PLATFORM=eglfs(since #2904), which has no/dev/fb0and so was never guarded. With no panel attached, Qt's eglfs plugin finds no connected DRM connector, logsno screens available, and exits before the viewer emits its D-Bus handshake —load_browser()then raisesRuntimeErrorand the container restart-loops. Observed live on 4 pi4 devices.Description
wait_for_eglfs_display(): before the KMS-card detection, wait until a DRM connector readsconnected(or the occasional bridge that reportsunknown); only an all-disconnectedboard waits. A genuinely headless board idles quietly and self-heals on hotplug, mirroring the existing/dev/fb0wait.linuxfb.No change for boards with a display attached at boot (the check returns immediately); a board whose TV is slow to negotiate HDMI now waits a few seconds instead of crash-looping.
Checklist
🤖 Generated with Claude Code