Bug 289686

Summary: WebAssembly worker spin loop after `worker.terminate()` or page reload
Product: WebKit Reporter: Laurenz Mädje <laurmaedje>
Component: WebAssemblyAssignee: Nobody <webkit-unassigned>
Status: NEW    
Severity: Normal CC: cdumez, d_degazio, keith_miller, mark.lam, nham, webkit-bug-importer
Priority: P2 Keywords: HasReduction, InRadar
Version: Safari 18   
Hardware: Mac (Apple Silicon)   
OS: macOS 15   
Attachments:
Description Flags
A minimal reproduction of the bug. none

Laurenz Mädje
Reported 2025-03-13 02:48:06 PDT
Created attachment 474546 [details] A minimal reproduction of the bug. Calling `worker.terminate()` or reloading the page when there's a web worker that runs non-yielding WebAssembly does not properly terminate it. The WebAssembly continues running until the tab is fully closed. Moreover, atomic operations stop working correctly, which may cause workers relying on atomic operations for correct operation (e.g. a mutex implementation) to go into a spin loop, consuming tons of CPU and battery. I've attached a complete minimal reproduction as a ZIP file. It can also be found here: https://github.com/laurmaedje/safari-worker-bug The reproduction page starts a web worker, which loads WebAssembly with shared memory, and runs an export from the WebAssembly. This export never yields back to the event loop. Rather, it goes into a loop where it `memory.atomic.wait32`s for a memory location to change (which never changes). During normal worker operation, this wait simply suspends and does not cause any CPU usage. However, as soon as the worker is killed via `worker.terminate()`, CPU usage goes all the way to 100%. I can't say for sure what happens, but my best guess is that `worker.terminate()` doesn't correctly shut down the non-yielding WebAssembly, but _does_ release enough resources for the memory wait operation to not work correctly anymore, making it return immediately instead, effectively making the WASM go into a spin loop. In my view, the bug here is two-layered: The fact that the atomic instruction does not work correctly anymore is the symptom that causes high CPU usage, but the core problem is that neither `worker.terminate()` nor a page reload are able to kill a non-yielding WebAssembly function.
Attachments
A minimal reproduction of the bug. (46.41 KB, application/zip)
2025-03-13 02:48 PDT, Laurenz Mädje
no flags
Laurenz Mädje
Comment 1 2025-03-13 12:12:37 PDT
I tested in Safari Technology Preview and the atomics spin loop appears to be fixed! Probably by https://github.com/WebKit/WebKit/commit/5b0655d476fe2a67bbee13c722a08bd0393cc063 (at least the diff looks very plausible), which fixed https://bugs.webkit.org/show_bug.cgi?id=281657#c1. What still remains though is the fact that a WebAssembly function that actively spins prevents a worker from being terminated by `worker.terminate()`.
Radar WebKit Bug Importer
Comment 2 2025-03-20 02:49:17 PDT
Note You need to log in before you can comment on or make changes to this bug.