Skip to content

fix: Stop renderer ANR watchdogs leaking and re-map protocol renderers after reload - #1473

Open
timfish wants to merge 4 commits into
masterfrom
fix/renderer-anr-lifecycle
Open

timfish wants to merge 4 commits into
masterfrom
fix/renderer-anr-lifecycle

Conversation

@timfish

@timfish timfish commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Each renderer with the event loop block integration left a 20 ms setInterval running for the life of the app, because the core watchdog has no stop. Once the renderer stopped polling, the leaked watchdog fired and, without stack capture, reported a false ANR and marked the session abnormal. blur/focus were registered with once, so hidden windows stayed watched after the first cycle. The debugger script map grew with every eval. In protocol mode a reload gives the renderer a new id that main never mapped, so status messages were dropped, ANR detection died and events lost their renderer name.

createWatchdog in src/main/watchdog.ts adds stop(), called with full listener cleanup on destroyed. blur/focus use on. render-process-gone only disables the watchdog, since the debugger session survives a crash and the reloaded page re-enables it. The script map is cleared on did-navigate and capped. newProtocolRenderer takes the id from the start request and probes every webContents for it, replacing a stale mapping.

Unit tests cover the watchdog. A scratch app in protocol mode against master vs this branch: an event after reload is tagged with the renderer name instead of renderer, a block after reload yields an ANR with filenames instead of nothing, and closing the window leaves 0 watchdog intervals instead of 1. The "ANR Renderer Event" e2e times out locally on master too, so it needs CI.

…s after reload

The core watchdog has no stop, so every renderer left a 20 ms
interval running for the life of the app and fired a false ANR once
the renderer stopped polling. blur/focus used once, the debugger
script map grew with every eval, and in protocol mode a reload gave
the renderer a new id that main never mapped.

Adds a stoppable watchdog with full listener cleanup on destroyed,
disables it on render-process-gone, bounds the script map, and makes
newProtocolRenderer resolve the id sent with the start request.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread src/main/integrations/renderer-anr.ts Outdated
In protocol mode the reloaded page's visibility status can be dropped
while its new id is still being mapped, and alive polls never change
the enabled flag, so a disabled watchdog stayed disabled. suspend()
holds the callback only until the next poll.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread src/main/watchdog.ts Outdated
In protocol mode the request body is read asynchronously, so one
status message from the crashed renderer can arrive after
render-process-gone. A single poll must not lift the hold, or the
watchdog fires a false ANR once that stale poll is the last one.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f31ecd8. Configure here.

Comment thread src/main/watchdog.ts
…page

A reloaded page sends its visibility once at start and the next poll
only after a poll interval, so a block inside that interval was not
reported. A visibility status cannot be a stale message from the
crashed process, so it lifts the hold at once. The second-poll rule
stays for protocol mode, where that status can be dropped.
@timfish
timfish requested a review from JPeer264 October 15, 2026 14:21
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