Skip to content

fix: Survive a failed WebView2 init and a stale adopted routing entry - #30

Merged
ResurrectedTrader merged 1 commit into
mainfrom
fix/update-recovery
Aug 24, 2026
Merged

ResurrectedTrader merged 1 commit into
mainfrom
fix/update-recovery

Conversation

@ResurrectedTrader

Copy link
Copy Markdown
Owner

Two faults that surfaced together when a manager running 100+ profiles took an update: the successor came back with a grey window behind a "Failed to initialize WebView2 ... RPC_E_DISCONNECTED" dialog, and then relaunched every profile that was already running.

WebView2 serves one user data folder from one browser process, and a client whose browser process goes away mid-initialization gets RPC_E_DISCONNECTED. The folder was a fixed path under %LOCALAPPDATA%, so every manager on the machine shared a browser process and whichever instance owned it could take the others' windows down when it exited. Handoff adds a second overlap the port cannot separate, since a successor deliberately inherits its predecessor's port. On top of that a single failure was terminal: OnFormLoad showed a MessageBox and gave up, leaving a permanently blank window over a server that was running fine.

  • Partition the user data folder by server port, so managers running side by side no longer share a browser process. Two managers cannot hold the same port concurrently, so it is a sound instance key.
  • Retry initialization four times with linear backoff, then offer Retry rather than only OK, and say plainly that the profiles are unaffected and the web UI is still reachable. Each retry runs on a fresh WebView2 control: a control binds the environment from its first EnsureCoreWebView2Async call and rejects a different one later, which is exactly what a retry needs to supply.

The relaunches were a routing entry inherited across the handoff. A successor restores each profile's entry from the predecessor's manifest, so it inherits whatever the predecessor believed — and a predecessor built before the handle was tracked on the instance reverse-looked it up from a map that leaked a dead row per game exit, returning an arbitrary one. The adopted games then sent from a window the successor was not listening on, no heartbeats arrived, and the watchdog killed and relaunched all of them about a minute later. Fixing the lookup does not help here because the predecessor is the old build by definition, so the successor has to be able to recover on its own.

  • On a missed heartbeat, check the profile's routing entry against the window its game actually owns and re-register if they disagree, granting one interval before counting the miss. A wrong entry and a dead bot are indistinguishable to the watchdog, and killing a healthy game over one is the worst available response.

Two faults that surfaced together when a manager running 100+ profiles took an
update: the successor came back with a grey window behind a "Failed to
initialize WebView2 ... RPC_E_DISCONNECTED" dialog, and then relaunched every
profile that was already running.

WebView2 serves one user data folder from one browser process, and a client
whose browser process goes away mid-initialization gets RPC_E_DISCONNECTED. The
folder was a fixed path under %LOCALAPPDATA%, so every manager on the machine
shared a browser process and whichever instance owned it could take the others'
windows down when it exited. Handoff adds a second overlap the port cannot
separate, since a successor deliberately inherits its predecessor's port. On top
of that a single failure was terminal: OnFormLoad showed a MessageBox and gave
up, leaving a permanently blank window over a server that was running fine.

- Partition the user data folder by server port, so managers running side by
  side no longer share a browser process. Two managers cannot hold the same port
  concurrently, so it is a sound instance key.
- Retry initialization four times with linear backoff, then offer Retry rather
  than only OK, and say plainly that the profiles are unaffected and the web UI
  is still reachable. Each retry runs on a fresh WebView2 control: a control
  binds the environment from its first EnsureCoreWebView2Async call and rejects a
  different one later, which is exactly what a retry needs to supply.

The relaunches were a routing entry inherited across the handoff. A successor
restores each profile's entry from the predecessor's manifest, so it inherits
whatever the predecessor believed — and a predecessor built before the handle
was tracked on the instance reverse-looked it up from a map that leaked a dead
row per game exit, returning an arbitrary one. The adopted games then sent from
a window the successor was not listening on, no heartbeats arrived, and the
watchdog killed and relaunched all of them about a minute later. Fixing the
lookup does not help here because the predecessor is the old build by
definition, so the successor has to be able to recover on its own.

- On a missed heartbeat, check the profile's routing entry against the window
  its game actually owns and re-register if they disagree, granting one interval
  before counting the miss. A wrong entry and a dead bot are indistinguishable
  to the watchdog, and killing a healthy game over one is the worst available
  response.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ResurrectedTrader
ResurrectedTrader merged commit 267012f into main Aug 24, 2026
7 checks passed
@ResurrectedTrader
ResurrectedTrader deleted the fix/update-recovery branch August 24, 2026 20:52
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