Skip to content

macOS: report a view that can never paint again (replaced GPU process, hung renderer) - #39

Merged
wenkaifan0720 merged 1 commit into
mainfrom
fix/liveness-wedge-recovery
Sep 23, 2026
Merged

wenkaifan0720 merged 1 commit into
mainfrom
fix/liveness-wedge-recovery

Conversation

@wenkaifan0720

Copy link
Copy Markdown
Collaborator

Under memory pressure, a CEF view could freeze for good: it stopped painting and no event ever fired, so the embedder never recreated it. In the FlutterFlow desktop app the only way out was to relaunch.

Causes (reproduced by signalling the helper processes)

Cause What happened before
GPU process killed Chromium relaunches it, but off-screen frames never come back. JS still answers, and nothing is reported.
Renderer hung (SIGSTOP) No frames and no JS replies. The repaint nudge still re-presents the last frame, so the sweep never declared a stall.
Renderer killed Reloads and recovers. This already worked.

Fix (macOS plugin only, no new cef_host prebuilt)

The steady-state liveness sweep now ends a host that can never paint again. Its sessions get processGone("crashed"), and the consumer recreates them through the same path it uses after a real crash.

  • Replaced GPU process: the sweep finds the host's GPU process by its --type=gpu-process argument, since it runs as the generic helper. If that process started after the host's first frame, it is a replacement.
  • Hung renderer: a visible browser that stops painting gets a JS ping along with the nudge. The ping uses eval id UInt32.max and is consumed by the plugin, so it never reaches Dart. If the ping goes unanswered for 15 s (FLUTTER_CEF_HANG_MS), the renderer counts as hung. A static page answers the ping, and is pinged at most once per staleness window. A navigation, or hiding the view, drops an outstanding ping.

Probe: example/lib/wedge_recovery_probe.dart

Case main (124ca92) this branch
An idle static page is left alone (35 s) PASS PASS
Killing the GPU process reports crashed FAIL (never) PASS (~2 s)
SIGSTOP on the renderer reports crashed FAIL (never) PASS (~27 s)

./test/run_liveness_probe_tests.sh passes, including new tests for the ping and GPU start-time policies.

Windows has no steady-state liveness sweep, so it is not covered by this change.

🤖 Generated with Claude Code

A browser that painted and then could never paint again was taken for an
idle static page, so no event fired and the view stayed frozen with no way
back. Two causes, both seen under memory pressure:

- The GPU process dies. Chromium relaunches it, but off-screen frames never
  come back. JS still answers, so nothing looked wrong.
- The renderer hangs. The repaint nudge still produced a frame, because the
  browser re-presents its last one, so the sweep never got as far as
  declaring a stall.

The liveness sweep now ends such a host, so its sessions get processGone
("crashed") and the consumer recreates them the way it recovers from a
crash:

- A GPU process that started after the host's first frame replaced one that
  died. The sweep finds it by its --type=gpu-process argument (it runs as
  the generic helper) and reads its start time.
- A visible browser that stops painting is sent a JS ping with the repaint
  nudge (eval id UInt32.max, consumed by the plugin). Left unanswered for
  15 s (FLUTTER_CEF_HANG_MS), the renderer is hung. A static page answers,
  and is pinged at most once per staleness window. A navigation or hiding
  the view drops an outstanding ping.

example/lib/wedge_recovery_probe.dart kills the GPU process and stops the
renderer, and checks that an idle static page is left alone. On main both
kills go unreported; with this change, processGone("crashed") arrives in
about 2 s and 27 s.

Windows has no steady-state liveness sweep, so it still lacks this.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@wenkaifan0720
wenkaifan0720 merged commit 7a7b252 into main Sep 23, 2026
2 checks passed
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