You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
webasm: intermittent renderer process crash during a long run (1 of 3 attempts, not reproduced since) #870
Follows #866. Filing this deliberately thin, because it happened once and I have not reproduced it — but a renderer crash seemed worth recording even without a repro, in case it matches something known.
What happened
Driving the gpu.rocks benchmark suite (30 workloads, mode: 'webasm' plus a plain-JS baseline, all inside one Web Worker) headless on Chrome 150, Apple M1 Max, branch build of #866 (11b4c0e).
Three consecutive attempts at the identical pass:
attempt
outcome
1
completed all 30 rows
2
renderer died around row 6–7 (~90 s in)
3
completed all 30 rows
On attempt 2 the driver's next page.evaluate threw Attempted to use detached Frame, which is puppeteer's symptom for the renderer process going away. Attempt 2 was the same build, same rows, same order as the two that passed.
What I can rule out
After it happened I instrumented the driver to sample performance.memory.usedJSHeapSize alongside the status on every poll. Across the full successful pass afterwards the main-thread heap stayed flat at 11–14 MB from row 1 to row 30, so it is not a monotonic main-thread leak. That measurement is from a passing run, though — I have no heap trace from the crash itself.
The work runs in a Worker with OffscreenCanvas, and the wasm backend under mode: 'webasm' (not the async/threaded contract), so no SharedArrayBuffer or worker pool is involved on this path as far as I can tell.
What I cannot say
Whether it is attributable to this backend at all. It is suggestive that two prior full passes of the same suite on published 2.21.0, with every other backend and no webasm column, completed without incident — but that is three data points against two, not evidence.
Which workload was executing. My driver only knew the last polled status, which is why I added crash-time status capture afterwards; if it recurs I will have the workload name and can update this.
If the shape of this rings a bell — a wasm instance or memory being freed while a call is in flight, say, or a rebuild after destroy() racing something — that would be more use than anything further I can measure from outside. Otherwise I will add a note here if it recurs with the workload named.
The shape does ring a bell, and the flat heap trace is the tell rather than the alibi: performance.memory.usedJSHeapSize cannot see WebAssembly memories. Each webasm kernel caches one WebAssembly.Memory per size signature ([args | constants | output], so tens of MB per kernel at these workload sizes), and in Chrome each Memory also pins a multi-GB virtual guard reservation for bounds-check elision. The bench destroys per workload, but destruction only drops JS references — the wrapper objects are tiny, so a pressure-blind GC has no urgency, and dead memories from a 30-workload sweep can transiently accumulate until the renderer hits its commit or reservation limit. Intermittency by GC timing fits the 1-of-3 pattern; "around row 6–7" fits the first rows being the large ones.
Hardened on feature/webasm-backend (b79dfb9), on the reasoning that even an unattributed crash deserves the one memory-shaped defect the audit found fixed:
the per-kernel module cache is LRU-bounded (moduleCacheLimit, default 8) — previously a size-sweeping kernel (dynamicOutput) grew it without bound;
evicted and destroyed entries are scrubbed: instance, module, memory, views and bytecode all dropped, so a kernel object retained anywhere (the run-shortcut closure, user code) no longer keeps every cached memory alive with it;
for threaded entries the worker pool now releases the worker-side instantiations, which were holding the shared buffer forever; deferred past the threaded tail so an in-flight dispatch keeps what it captured.
Sync and threaded eviction-then-revisit are pinned by test/features/webasm/module-cache.js.
What cannot be claimed: proof this was the crash. If it recurs on a build with these changes, the useful captures are the workload name (now instrumented), performance.measureUserAgentSpecificMemory() (sees wasm, unlike performance.memory), and the exit reason from chrome://crashes — an OOM kill there would confirm the mechanism even without a repro.
Activity
fix(webasm): bound the module cache; scrub released wasm memories (#870)
fuzzie360 commentedon Aug 3, 2026
The shape does ring a bell, and the flat heap trace is the tell rather than the alibi:
performance.memory.usedJSHeapSizecannot see WebAssembly memories. Each webasm kernel caches oneWebAssembly.Memoryper size signature ([args | constants | output], so tens of MB per kernel at these workload sizes), and in Chrome each Memory also pins a multi-GB virtual guard reservation for bounds-check elision. The bench destroys per workload, but destruction only drops JS references — the wrapper objects are tiny, so a pressure-blind GC has no urgency, and dead memories from a 30-workload sweep can transiently accumulate until the renderer hits its commit or reservation limit. Intermittency by GC timing fits the 1-of-3 pattern; "around row 6–7" fits the first rows being the large ones.Hardened on
feature/webasm-backend(b79dfb9), on the reasoning that even an unattributed crash deserves the one memory-shaped defect the audit found fixed:moduleCacheLimit, default 8) — previously a size-sweeping kernel (dynamicOutput) grew it without bound;Sync and threaded eviction-then-revisit are pinned by
test/features/webasm/module-cache.js.What cannot be claimed: proof this was the crash. If it recurs on a build with these changes, the useful captures are the workload name (now instrumented),
performance.measureUserAgentSpecificMemory()(sees wasm, unlikeperformance.memory), and the exit reason fromchrome://crashes— an OOM kill there would confirm the mechanism even without a repro.