Bug 322883

Summary: Concurrent dedicated module worker script loads through a service worker fail with a detail-free error event
Product: WebKit Reporter: chris.tham
Component: Service WorkersAssignee: Nobody <webkit-unassigned>
Status: NEW    
Severity: Normal CC: basuke, cdumez, jambalaya.pyoncafe, karlcow, steve, webkit-bug-importer, youennf
Priority: P2 Keywords: InRadar
Version: Safari 26   
Hardware: Mac (Apple Silicon)   
OS: macOS 26   
Attachments:
Description Flags
Reduced test: nested module workers under a service worker that adds COEP (zip) none

chris.tham
Reported 2026-08-28 20:04:04 PDT
On a page controlled by a service worker whose fetch handler calls respondWith(fetch(request)) (with or without header rewriting, with or without CacheStorage), creating a dedicated MODULE worker while another module worker's script load is still in flight makes the later load fail: the Worker object fires a detail-free `error` event (no message, no filename, no lineno - a plain Event, not an ErrorEvent), and nothing inside the worker global ever runs (an error/unhandledrejection listener injected at the very top of the worker script never fires). Sequential creations are 100% reliable. The failure needs only concurrency >= 2 and is deterministic at 2. ENVIRONMENT - Safari 26.6.2, macOS 26.6.2 (build 25G83), Apple Silicon - Reproduces on http://localhost from a plain static file server (HTTP/1.1, correct MIME types) - Not reproducible in Chromium (Chrome 148: 300/300 in every mode) STEPS TO REPRODUCE Minimal repro (7 small files, no wasm, no SharedArrayBuffer): https://github.com/ChristineTham/ipnx-v12/tree/main/demo/webkit-repro 1. Serve that directory statically (python3 -m http.server works). 2. Open matrix.html?conc=1&n=300 - the first visit registers sw.js and reloads under its control; the page then creates 300 module workers with the given concurrency, each terminated after its first message. 3. Repeat with ?conc=2&n=300 and ?conc=8&n=300. 4. Control: ?nosw=1&conc=8&n=300 unregisters the SW and repeats. RESULTS (measured 2026-08-29) - conc=1, via SW: 300/300 ok - conc=2, via SW: 150/300 ok - EVERY odd-indexed load fails (exactly the one that starts while another is in flight) - conc=8, via SW: 92/300 ok - conc=8, no SW: 300/300 ok EXPECTED Concurrent module-worker script loads on a controlled page succeed, as they do without the service worker and as they do in other engines. ACTUAL Every module-worker script load that begins while another is in flight through the service worker fails with a detail-free error event at the Worker object; the worker global never starts. NOTES - The service worker in the repro only adds Cross-Origin-Embedder-Policy / Cross-Origin-Opener-Policy headers (the common "COOP/COEP via SW" pattern used to get SharedArrayBuffer on static hosts such as GitHub Pages), but header rewriting is not required: the failure also occurs with a pass-through respondWith(fetch(request)). - A CacheStorage-backed SW reduces the frequency but does not eliminate it. - Workaround we ship: serialize module-worker creation (one script load in flight at a time, released on the worker's first message/error). - Found while deploying https://christham.net/ipnx-v12/ (a hosted-kernel demo that spawns one module worker per process).
Attachments
Reduced test: nested module workers under a service worker that adds COEP (zip) (6.96 KB, application/octet-stream)
2026-11-01 09:05 PST, jambalaya.pyoncafe
no flags
Radar WebKit Bug Importer
Comment 1 2026-09-04 20:04:13 PDT
jambalaya.pyoncafe
Comment 2 2026-11-01 09:05:10 PST
Created attachment 482100 [details] Reduced test: nested module workers under a service worker that adds COEP (zip) This still reproduces on macOS 27.2 with Safari 27.2 and Safari Technology Preview 255, but the pattern differs from the report, and the conditions are narrower. I used an AI assistant (Claude Code) to write the test pages, run them through safaridriver on my Mac, and draft this comment. Attached is a reduced test (nested-module-workers.zip, 13 small files, README inside). index.html starts n module workers, conc at a time, from the page or from inside a module worker. The worker script imports one module. sw.js adds COOP/COEP to every response, as coi-serviceworker does. Results with n=300 and a fresh WebDriver session per run. Safari 27.2 and STP 255 gave the same numbers. With the original repro (matrix.html): - conc=1: 300/300. conc=2: 299/300. conc=8: 293/300. No SW: 300/300. - Only the loads that start together at the very beginning fail (loads 1 to conc-1). Later loads in the same run pass. A second and third run in the same session pass 300/300. On 26.6.2, the report saw every overlapping load fail. With the attached test: - Starting the workers from inside a module worker fails the same way (conc=2: 299/300, conc=8: 293/300). - A different URL for every worker (?i=N) gives 300/300. Which service worker triggers it (STP 255, workers started from a worker, conc=8): - The SW adds COEP that the network response does not have: 293/300. The result is the same whether the body is streamed or read fully first. - The network response already has COEP, and the SW passes it through, rebuilds it, or adds the headers again: 300/300. - A pass-through respondWith(fetch(request)) on a server that sends no COEP: 300/300 (the page is then not isolated). So on 27.2, a pass-through service worker did not reproduce it for me, unlike in the report. Console of a failing run (Playwright's WebKit 27.2): Refused to load 'http://localhost:8812/dep.mjs' worker because of Cross-Origin-Embedder-Policy. Worker load was blocked by Cross-Origin-Embedder-Policy Importing a module script failed. The refused request is the worker's static import, not the worker script. Workers without an import, started from a worker, did not fail (300/300). A guess, not verified: the concurrent loads of the import seem to be checked against a response that lacks the COEP header the service worker added. We hit this in formicarium (https://github.com/aletheia-works/formicarium), a WebAssembly emulator whose workers start one module worker per guest thread, on pages isolated by coi-serviceworker. We work around it as the report does, by starting the workers one at a time (https://github.com/aletheia-works/formicarium/pull/229).
Steve Vigneau
Comment 3 2026-11-12 17:46:43 PST

Hey there, wanted to add a bit more info as I found this in parallel while working on trailmaps.app-map-generator (https://github.com/c0nsumer/trailmaps.app-map-genrator) and then found this bug report.

Here's a concise repro: https://nuxx.net/files/webkit_bug_322883_repro/

Doesn't seem to need COEP.

And while I haven't tested this myself, a coworker was unable to repro it on Safari 27.3 on iOS, so maybe a fix is coming soon?

Note You need to log in before you can comment on or make changes to this bug.