Skip to content

Reconnect the live view after a drop, and only say shut down when it is - #488

Draft
tnsardesai wants to merge 1 commit into
mainfrom
hypeship/live-view-reconnect
Draft

tnsardesai wants to merge 1 commit into
mainfrom
hypeship/live-view-reconnect

Conversation

@tnsardesai

@tnsardesai tnsardesai commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The live view showed "Browser has been shut down and is no longer available" whenever its connection dropped, whether or not the browser was gone. app.vue renders that overlay for any !connected && wasConnected, and once a connected session dropped, nothing reconnected it: ICE reaching failed, the data channel closing, or the socket closing all ended in disconnect().

So a network change or a TURN relay that stops answering told the viewer the browser had shut down while it was still running. Because the viewer no longer held a connection, the session then stopped counting as active and could idle out shortly after, which made the message come true.

Changes

  • Reconnect after a drop. When a session that had connected drops, the client reconnects with the same staged connect as the first, after 1s, 2s, 4s, 8s and 10s. A reconnect that connects resets the count. The first connect is unchanged and still isn't retried.
  • Only say shut down when it is. The live-view proxy closes the socket with 4410 when the browser session no longer exists. A reconnect that gets that code stops and shows the shut-down message. The overlay now has three states:
    • reconnecting: "Connection to the browser was lost. Reconnecting..."
    • lost (attempts ran out, or the server ended the viewer): "Lost connection to the browser. Reload to try again."
    • gone (4410): "Browser has been shut down and is no longer available"
  • neko connection shutdown isn't a kick. neko sends system/disconnect with connection shutdown as it stops, both on a restart and on browser teardown. That message is now left to the socket close that follows, so it reconnects, or ends as gone, without a modal. Other server reasons (kicked, connection replaced, ...) stay terminal, so two viewers can't keep replacing each other.
  • Parent frame events. Failed reconnect attempts post nothing. When the attempts run out, the client posts one KERNEL_CONNECTION_FAILED or, if the last attempt hit a stage bound, KERNEL_CONNECTION_TIMEOUT, with the last attempt's reason. That keeps "a failed connect posts one of them, never both". A dropped session that comes back posts KERNEL_CONNECTED and KERNEL_PLAYING again. A reconnect that ends as gone posts no failure, since it isn't a connect failure.
  • The "Disconnected" toast is skipped while a reconnect is pending, so retries don't stack notifications.

Read-only mode survives a reconnect: remote.reset() keeps locked = readOnly. A reconnect also re-runs the existing KERNEL_CONNECTED handling in app.vue, which reapplies ?width/height/rate query params, the same as a page reload.

The proxy appends the session token by rewriting ${e}?password=${encodeURIComponent(t)}&username=${encodeURIComponent(n)} in the bundle. I checked that the production build still contains that string exactly once, so reconnect sockets carry the token.

Relation to #417's no-retry decision

#417 removed retries because retrying a connect that never succeeded delays the reason the embedder needs. That still holds here: a first connect is not retried, and reports its first failure exactly as before.

This PR reconnects only after a session that had connected drops. On main that path posts no reason at all. The parent's only signal is KERNEL_PAUSED, which still arrives in the same tick as the drop, because cleanup() runs before the reconnect is scheduled. So the reconnect delays nothing the parent receives. It adds one terminal event when the attempts run out, where main posted none.

What the reconnect does delay is the in-viewer message when the browser really is gone:

Case Viewer sees Time to the final message
Browser deleted while connected "Reconnecting...", then "Browser has been shut down" when the first attempt gets 4410 ~1s (measured 1.3s from the drop)
Wake fails transiently, or restore and start disagree on not-found mid-teardown retries; "shut down" once the instance is gone, otherwise "Lost connection... Reload" up to the 25s of backoff, plus a few seconds of attempts
Metro host or the viewer's own network unreachable "Lost connection... Reload" ~25s of backoff, up to ~100s if every attempt hangs to the 15s transport bound

On main all three show "Browser has been shut down" immediately, which in the last two is usually wrong.

A reconnect that ends as gone posts nothing to the parent, the same as main. The 4410 path was verified on a hypeman metro. The proxy has the same handling for the unikraft backend, but it was not run there.

Testing

  • bun test tests: 262 pass. New tests:

    • a dropped session reconnects after the first delay and posts nothing;
    • a reconnect that connects starts the next drop from the first delay;
    • failed attempts back off through every delay, then post one KERNEL_CONNECTION_FAILED;
    • attempts that hit a stage bound are retried, and only the last one posts KERNEL_CONNECTION_TIMEOUT;
    • a 4410 close stops reconnecting without posting;
    • a server-initiated disconnect and a first connect that never succeeded don't reconnect;
    • the overlay state for a pending reconnect, a lost viewer and a gone session;
    • connection shutdown versus other server disconnect reasons.

    The 7 reconnect tests fail against the previous base.ts.

  • npm run build passes, with the same 17 lint warnings as main. Prettier is clean.

  • End to end on a production headful image, with this branch's client and a local main build swapped into the same browser and driven from headless Chromium 155:

    Trigger main This branch
    Outbound UDP dropped on the viewer's machine for 45s (ICE fails about 5s in) "Browser has been shut down"; no reconnect, still showing it about 145s later, after UDP had been restored, with the browser running "Reconnecting...", reconnected on the first attempt 1s later while UDP was still blocked, video resumed
    supervisorctl restart neko "Browser has been shut down"; no reconnect retried after 1s, 2s and 4s while neko was down, and connected on the attempt after 8s; video resumed; readOnly=true kept
    Browser deleted while viewing not run; any drop already shows the message "Reconnecting...", then the first attempt got 4410 and showed "Browser has been shut down"; no further attempts

Not covered: the image build and a fresh boot with this client, and Safari or Firefox.

Follow-up

The live-view docs say "The client does not retry a connect itself" and that a viewer whose frames stop after connecting never posts a terminal event. Both need updating once this ships. The documented embedder pattern (arm a watchdog on KERNEL_PAUSED, clear it on KERNEL_PLAYING) keeps working unchanged.

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