Skip to content

Instantly share code, notes, and snippets.

@bartlomieju
Created March 9, 2026 22:45
Show Gist options
  • Select an option

  • Save bartlomieju/17ccaa5b6619662ee036e434a10f80bc to your computer and use it in GitHub Desktop.

Select an option

Save bartlomieju/17ccaa5b6619662ee036e434a10f80bc to your computer and use it in GitHub Desktop.

Issue #26142: Worker blob URL race condition — Analysis & Fix Plan

The Bug

URL.revokeObjectURL() after new Worker(blobURL) causes intermittent "Module not found" errors because blob content is fetched asynchronously on the worker thread, not during construction.

Works in Chrome/Firefox. Fails in Deno.

Race Condition

MAIN THREAD (synchronous)
  new Worker(blobUrl)
    → op_create_worker (parses URL only, spawns thread)
    → returns immediately
  URL.revokeObjectURL(blobUrl)  ← removes blob from store

WORKER THREAD (asynchronous)
  run_web_worker()
    → preload_main_module()
      → blob_store.get_object_url()  ← returns None! blob was revoked

Key Files

File Role
runtime/js/11_workers.js:94-138 JS Worker constructor, calls op_create_worker
runtime/ops/worker_host.rs:159-293 op_create_worker — parses URL, spawns thread, does NOT fetch blob
runtime/web_worker.rs:1120-1157 Worker startup — preload_main_module() fetches module here (too late)
cli/file_fetcher.rs:113-121 BlobStoreAdapter — async blob fetch happens here
ext/web/blob.rs:68-96 BlobStoreget_object_url() is a sync HashMap lookup, read_all() is async

Fix Options

Option A: Sync op to snapshot blob content (JS-side)

In runtime/js/11_workers.js, detect blob: URLs and read the content before calling op_create_worker, passing it as sourceCode (path already exists for classic workers).

Requires a new sync op op_worker_read_blob_url in Rust. The BlobStore.get_object_url() is already sync (HashMap get), and createObjectURL blobs are in-memory Vec<u8>, so a sync read is trivial.

Option B: Hold Arc<Blob> reference in worker args (Rust-side, preferred)

In op_create_worker, if the specifier is a blob: URL, clone the Arc<Blob> from the BlobStore before returning. Pass it through to the worker thread's module loader so the data outlives revokeObjectURL.

~5-10 lines changed in runtime/ops/worker_host.rs, plus threading the cloned blob to the module loader.

Why Option B is cleaner

  • No new ops needed
  • No changes to JS layer
  • Doesn't duplicate blob data (just an Arc clone = ref count bump)
  • Matches how browsers handle it (spec says blob content is captured at construction)

Does this affect node:worker_threads?

No. Node.js worker_threads doesn't support blob URLs at all — it requires absolute file paths or relative paths starting with ./ or ../. Both real Node.js and Deno's compat layer reject blob URLs at construction time. This is a Web Worker API-only issue.

Reproduction

let workerBlob = new Blob([`
  self.onmessage = async function(e) {
    console.log('Worker got message');
  }
`], {type:"application/javascript"});

let workerURL = URL.createObjectURL(workerBlob);
let worker = new Worker(workerURL, {type:"module"});
URL.revokeObjectURL(workerURL);  // ← causes race condition
worker.postMessage('');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment