-
Notifications
You must be signed in to change notification settings - Fork 5k
webcore: revoke a Worker's blob URLs when the worker exits #35828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1251,6 +1251,9 @@ impl WebWorker { | |
| vm.on_exit(); | ||
| if let Some(hooks) = runtime_hooks() { | ||
| (hooks.cron_clear_all_teardown)(vm); | ||
| // Before `dispatchExit` so the parent never observes `exit` | ||
| // with this worker's blob URLs still live. | ||
|
Comment on lines
+1254
to
+1255
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code |
||
| (hooks.revoke_object_urls_for_context)(self.execution_context_id as i32); | ||
| // Drain `TimeoutObject`s from this worker's timer heap before | ||
| // `close_all_socket_groups` / `WebWorker__teardownJSCVM` so | ||
| // their heap nodes are unlinked while `runtime_state` and the | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| import { expect, test } from "bun:test"; | ||
| import { resolveObjectURL } from "node:buffer"; | ||
| import { Worker as NodeWorker } from "node:worker_threads"; | ||
|
|
||
| test("Worker from a Blob", async () => { | ||
| const worker = new Worker( | ||
|
|
@@ -124,3 +126,37 @@ test("Worker on a revoked blob still works", async () => { | |
|
|
||
| expect(revoked).toBe("revoked."); | ||
| }); | ||
|
|
||
| test("Blob URLs created inside a Worker are revoked when the worker exits", async () => { | ||
| const worker = new NodeWorker( | ||
| `const { parentPort } = require("node:worker_threads"); | ||
| const u8 = new Uint8Array(1024).fill(7); | ||
| parentPort.postMessage(URL.createObjectURL(new Blob([u8])));`, | ||
| { eval: true }, | ||
| ); | ||
| const url = await new Promise<string>(resolve => worker.once("message", resolve)); | ||
| await new Promise(resolve => worker.once("exit", resolve)); | ||
|
Comment on lines
+137
to
+138
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 nit: both new tests await Extended reasoning...What the issue isBoth new tests added in this PR wait for the worker's first message with a resolve-only promise: // line 137
const url = await new Promise<string>(resolve => worker.once("message", resolve));
// line 152
const workerUrl = await new Promise<string>(resolve => worker.once("message", resolve));Neither promise is wired to reject on the worker's
How it manifestsThe eval scripts here are trivial and pass today, so nothing breaks on merge. The concern is diagnosability under a future regression: if The Why existing code doesn't prevent itThere is no Pre-existing tests in this file (e.g. the Web Step-by-step proof
FixWire const url = await new Promise<string>((resolve, reject) => {
worker.once("message", resolve);
worker.once("error", reject);
});The subsequent Severitynit — this is test-diagnosability, not a correctness bug in the shipped runtime code. The happy path works and a regression would still fail (just less cleanly and more slowly). |
||
|
|
||
| expect(resolveObjectURL(url)).toBeUndefined(); | ||
| await expect(fetch(url)).rejects.toThrow(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Bare Extended reasoning...What the issue isREVIEW.md, under Every assertion must be able to fail, and assert the strongest invariant, states: "specific error class/code/message (never bare Code pathWhen Why the existing check on line 140 doesn't fully cover itLine 140 ( Step-by-step proof
ImpactLow. Nothing breaks if this merges as-is: line 140 already asserts the load-bearing invariant, and the runtime change is fully covered. This is a test-quality tightening per the repo's stated review rule, not a correctness bug. How to fixawait expect(fetch(url)).rejects.toThrow(TypeError);
// or, more precisely:
await expect(fetch(url)).rejects.toThrow(/Failed to resolve blob:/);Addressing the refutationOne verifier refuted this as a duplicate of another report on the same line. That objection is about deduplication bookkeeping, not validity — it explicitly does not dispute the finding itself. Synthesis has consolidated the reports into this single entry, so the duplication concern is moot; only one comment will be posted. |
||
| }); | ||
|
|
||
| test("Worker exit does not revoke Blob URLs created by other threads", async () => { | ||
| const parentUrl = URL.createObjectURL(new Blob([new Uint8Array(64).fill(1)])); | ||
| try { | ||
| const worker = new NodeWorker( | ||
| `const { parentPort } = require("node:worker_threads"); | ||
| parentPort.postMessage(URL.createObjectURL(new Blob([new Uint8Array(64)])));`, | ||
| { eval: true }, | ||
| ); | ||
| const workerUrl = await new Promise<string>(resolve => worker.once("message", resolve)); | ||
| await new Promise(resolve => worker.once("exit", resolve)); | ||
|
|
||
| expect(resolveObjectURL(workerUrl)).toBeUndefined(); | ||
| const blob = resolveObjectURL(parentUrl); | ||
| expect(blob).toBeInstanceOf(Blob); | ||
| expect(new Uint8Array(await blob!.arrayBuffer())).toEqual(new Uint8Array(64).fill(1)); | ||
| } finally { | ||
| URL.revokeObjectURL(parentUrl); | ||
| } | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code