fix(viewer): respawn the webview when it dies mid-D-Bus call instead of crashing - #3012
Conversation
…of crashing - The armv7 Qt5 heap-corruption crash that load_browser() already retries past can also strike after the D-Bus handshake; the death then surfaces as a GError (NoReply) out of the in-flight loadImage/loadPage call, escapes main(), and turns one process crash into a container restart loop (Sentry 58040ab3) - Wrap loadImage/loadPage in _send_to_webview(): on a webview-gone D-Bus error, reap the dead process, respawn with the inline budget, and retry the call once; anything else still raises - Reset current_browser_url in load_browser() so a respawned webview always gets its asset re-sent, even when the URL is unchanged (a crashed single-asset playlist previously respawned to a blank screen forever) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR hardens the viewer’s interaction with the AnthiasViewer D-Bus webview so that if the webview process dies during an in-flight D-Bus call (e.g., NoReply), the viewer respawns the webview and retries the call once instead of crashing into a container restart loop.
Changes:
- Added
_send_to_webview()+_is_webview_gone_error()to detect “webview gone” D-Bus failures and perform a single respawn-and-retry forloadImage/loadPage. - Updated
load_browser()to resetcurrent_browser_urlon every spawn so a fresh webview won’t remain blank due to URL short-circuiting. - Added unit tests covering respawn/retry behavior, selective error handling, retry failure propagation, and the URL reset.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/anthias_viewer/__init__.py |
Adds respawn-and-retry wrapper for mid-call D-Bus failures and resets cached URL on webview respawn. |
tests/test_viewer.py |
Adds test coverage for the new respawn/retry logic and current_browser_url reset behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Resolves SonarCloud S5332 security hotspots on the PR Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
On-device validation (real Pi 3 Model B+, armhf Qt5 image)Ran the full integrated stack on a Raspberry Pi 3 Model B+ (788 MB, aarch64 kernel, armhf viewer image, server/redis from the running compose project) with this branch's Scenario A — webview dies mid-D-Bus-call (the Sentry crash): SIGSTOP the webview so the next On 2026.6.2 this GError escapes Scenario B — blank-screen fix (unchanged-URL resend): single-asset playlist, clean SIGKILL between rotations. In-process respawn on attempt 2/3, and the fresh webview's stdout proves the unchanged URL was re-sent: Without the Also observed across the session: several natural post-handshake webview deaths were absorbed by the new path (in-process respawns and designed budget-exhaustion container restarts), with the playlist resuming every time. Caveat: the unit is headless (no HDMI connected → no 🤖 Generated with Claude Code |
…3020) - The armv7 Qt5 init crash can strike in the gap between the D-Bus handshake (which made load_browser() return) and setup()'s bus.get — the anthias.viewer name is released again, pydbus raises ServiceUnknown, and the GError escaped main() into a container restart loop (Sentry ANTHIAS-3) - Apply the same webview-gone detection and respawn-then-retry-once contract as _send_to_webview (#3012), with the generous startup budget since nothing is on screen yet - Unrelated D-Bus errors (e.g. Disconnected) still propagate so the container restart handles what a respawn can't fix - Add regression tests for both paths Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…+stop (#3031) - MPVMediaPlayer.play/stop called playVideo/stopVideo directly and, on a webview-gone D-Bus error (NoReply — the webview crashed mid-call), logged ERROR and gave up: a Sentry event for a self-healing condition, and the video stayed dead until the next rotation respawned the webview (Sentry ANTHIAS-1A) - Route both through the same _send_to_webview wrapper the image/page paths use (#3012): reap + respawn + retry once on webview death, injected via set_send_to_webview() in setup() alongside the bus - Genuinely unexpected errors still propagate to play/stop's existing log+clear-state handling; with no wrapper injected (tests/standalone) calls run directly, preserving prior behaviour - Add tests for the routing, respawn-recovery, re-raise, and the no-injection fallback Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>



Issues Fixed
Sentry event
58040ab3c2844b07a5d0707a77ba4122(release 2026.6.2):GError: GDBus.Error:org.freedesktop.DBus.Error.NoReply: Message recipient disconnected from message bus without replyingraised fromview_image(STANDBY_SCREEN)at viewer startup, crashing the viewer into a container restart loop.Description
The flaky armv7 Qt5 init crash (
malloc(): unaligned tcache chunk detected) thatload_browser()already retries past can also strike after the D-Bus handshake. In the Sentry event's breadcrumbs the webview came up on spawn attempt 3/30, then died ~0.4 s later whileloadImagewas in flight — the death surfaced as aGErrorout ofcall_syncinstead ofbrowser.is_alive() == False, escapedmain(), and defeated the whole spawn-retry machinery._send_to_webview()wrapper aroundloadImage/loadPage: on a webview-gone D-Bus error (NoReply,ServiceUnknown,NameHasNoOwner), reap the dead process, respawn with the existing inline budget, and retry the call once. Other errors (includingDisconnected, which means our session bus died) still propagate so the container restart remains the last resort.load_browser()now resetscurrent_browser_url: a fresh webview displays nothing, so the previous process's URL must not short-circuit the value comparison inview_image/view_webpage. Previously a webview that crashed mid-asset with an unchanged URL (e.g. a single-asset playlist) respawned to a permanently blank screen.Checklist
🤖 Generated with Claude Code