Surface and bound live view connect failures - #403
robertjamesprior wants to merge 1 commit into
Conversation
|
Converted to draft while it is under review. The narrowed connect bound is stacked on top as #415 (draft), so the retry in this PR is not left as the only change on the connect path. No code change here. |
A live view that could not start reported nothing to the parent frame until a 15s watchdog fired, and a throw from peer construction was discarded outright, so an embedder could not tell a failed connect from a slow one. Bound the connect stages separately -- transport 15s, signaling 3s, media 2s -- and report the failure to the parent frame with a reason an embedder can branch on: KERNEL_CONNECTION_TIMEOUT when a bound expires, KERNEL_CONNECTION_FAILED when the connect fails outright. One event per failure, both carrying the ICE, signaling and socket state at the moment it happened. The client does not retry. A second attempt against the same peer costs the viewer time an embedder can spend on a new session, and the reason is what lets it make that call.
ed29f17 to
3397bfa
Compare
|
Superseded by #417. Renaming this branch closed the PR, which was not intended — GitHub does not carry an open PR across a head-branch rename. The content is not lost: #417 is this change and the stacked staged-bounds change (#415, now closed) collapsed into one commit on main. What changed in the collapse, beyond squashing:
The bugbot finding on this PR — a rethrow from |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3397bfa. Configure here.
| if (event === EVENT.SIGNAL.OFFER) { | ||
| const { sdp } = payload as SignalOfferPayload | ||
| await this.setRemoteOffer(sdp) | ||
| this.armStage('media') |
There was a problem hiding this comment.
Re-offer rearms terminal media timeout
High Severity
signal/offer is a follow-up on an existing peer (createPeer only runs on signal/provide), but this path always calls armStage('media'). That starts a 2s timeout whose expiry is terminal: reportFailure sets _gaveUp and onDisconnected tears the session down. After ICE is already connected or checking, the state change that would clear the bound often never fires again, so a later offer — renegotiation, a resolution change — can kill a healthy live view and block any further connect() on this instance.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 3397bfa. Configure here.


Summary
The live view failed silently, and nothing downstream could see it.
onMessageisassigned straight to
ws.onmessage, so a throw fromcreatePeerorsetRemoteOfferwas discarded: no
emit('error'), noonDisconnected, no parent-frame message. Thesignaling socket stayed open and healthy, so the client could not tell "peer
construction failed" from "still connecting."
onMessagewraps the handler, so a rejection reachesonDisconnected. Covers the whole handler, not justsignal/provide.where it happens and re-attempted up to
MAX_CONNECT_ATTEMPTS. An unclassifiedfailure — a peer-construction throw, an ICE failure, a server-side disconnect — is
terminal on the first attempt, because a retry that reproduces it is not recovery.
KERNEL_CONNECTION_FAILEDto theparent with the reason, attempt count, and ICE/signaling/socket state.
An unsupported browser is terminal too; it used to loop through a login that could never
succeed. Retried attempts post nothing, so an embedder that remounts on
KERNEL_CONNECTION_TIMEOUTdoes not retry on top of us — that event now marks the lastattempt rather than every attempt.
Validation on a real image
Hotpatch A/B in one live session: the deployed image
(the currently deployed headful image, serving
app.416afd11.js) against a localbuild of this branch (
app.47e216f9.js, which also carries the stacked staged-boundschange). The live view was embedded in an iframe on a local parent page so the
parent-frame messages are observable — the client only posts them when embedded. The
fault was injected before any page script ran:
RTCPeerConnectionconstruction throws,which is the failing boundary from the field.
fb437e9)RTCPeerConnectionthrowsKERNEL_CONNECTION_TIMEOUT15373ms, reasonconnection timeoutKERNEL_CONNECTION_FAILED432ms, reasoninjected: RTCPeerConnection blocked, attempts 1Healthy connects post nothing, so the change does not touch viewers that work. The
swallowed throw is reported at 0.4s with its cause, where the deployed client waited
15.4s and reported a generic timeout that gave no indication it was terminal.
A hotpatch validates runtime behavior of the bundle, not image construction or release
packaging, and the failure is injected at the
RTCPeerConnectionboundary rather than bya real network fault.
Not in this PR
Terminal UI with one-click resume, one shared recovery heuristic with the dashboard
viewer, a frames-based gate, retrying ICE
failed, and awaitingaddIceCandidate.Testing
32 pass, 0 fail in
images/chromium-headful/client, including 10 new cases intests/connect-bound.test.ts.vue-cli-service lintclean on changed files. The stagedconnect bounds this branch enables are validated separately in #415.
Note
Medium Risk
Changes embedded live-view WebRTC connect lifecycle and parent
postMessagecontract, which embedders may rely on for recovery—scoped to the headful client, not auth or data stores.Overview
Replaces the single 15s connect watchdog with per-stage bounds (
transport/signaling/media) so timeouts name where the handshake stalled instead of a generic “connection timeout.” Stage expirations postKERNEL_CONNECTION_TIMEOUTwith that stage asreason; other terminal pre-connect failures postKERNEL_CONNECTION_FAILEDwith reasons likeunsupported,transport,peer, orserver, plus ICE/signaling/socket snapshot fields for embedders.onMessagenow catches handler rejections (e.g.RTCPeerConnectionconstruction throws) so failures reachonDisconnectedand the parent frame instead of hanging with an open signaling socket. Pre-connect disconnects are treated as a failed connect (one parent message,giveUpblocks furtherconnectattempts); post-connect drops stay ordinary disconnects with no kernel failure event.Server kicks set
_failure = 'server'before disconnect. Newconnect-bound.test.tscovers stage transitions, single-event reporting, and give-up behavior.Reviewed by Cursor Bugbot for commit 3397bfa. Bugbot is set up for automated code reviews on this repo. Configure here.