mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-24 06:14:01 +08:00
A postMessage or workerData graph whose objects and arrays are all frozen, ordinary and hold only primitives, strings and such containers is now shared by pointer: every node and string becomes immortal (rc == SIZE_MAX, so retains, releases and the cycle collector skip it and nothing frees it), strings cache their key hash first, and the nodes leave the sender's cycle buffers. Posting an already published root is O(1). Anything else is cloned as before; SCRIPTC_PUBLISH=0 turns publication off and SCRIPTC_PUBLISH_TRACE=1 reports each choice. With the receiver freezing what it receives (Node's clone is not frozen) and no identity comparison across messages, the behavior matches Node's structured clone. Published nodes leave the RC audit's live counts, since they are never freed, and the small-integer dyn cache becomes process-wide (filled before main): a published graph can outlive the thread that created its nodes, and the thread-local table died with its thread. The new corpus program pins that contract both ways, including a worker-published graph read after the worker exited and a shallowly frozen graph that must still be cloned.
83 lines
3.0 KiB
TypeScript
83 lines
3.0 KiB
TypeScript
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
|
|
|
|
// A deeply frozen plain-data graph posted to a worker. scriptc may share it
|
|
// by pointer instead of cloning it; with both sides frozen (the receiver
|
|
// freezes its copy, which Node's clone is not) the observable behavior is
|
|
// the same. Identity is only compared within one message.
|
|
function deepFreeze(value: any): any {
|
|
if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
|
|
Object.freeze(value);
|
|
for (const key of Object.keys(value)) deepFreeze(value[key]);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// Only the main thread prints: the worker posts its observations as text.
|
|
function describe(label: string, graph: any): string {
|
|
return [
|
|
label,
|
|
JSON.stringify(graph.nested),
|
|
graph.left === graph.right,
|
|
graph.items[0] === graph.left,
|
|
graph.items.length,
|
|
Object.isFrozen(graph),
|
|
Object.isFrozen(graph.left.values),
|
|
].join(" ");
|
|
}
|
|
|
|
function tryWrite(label: string, graph: any): string {
|
|
try {
|
|
graph.title = "changed";
|
|
return label + " write allowed " + graph.title;
|
|
} catch (error) {
|
|
return label + " write rejected " + (error as Error).name;
|
|
}
|
|
}
|
|
|
|
if (isMainThread) {
|
|
const shared = JSON.parse('{"name":"leaf","values":[1,2,3],"text":"\\u00e9\\u0000x"}');
|
|
const root = JSON.parse('{"title":"graph","count":3,"nested":{"deep":{"flag":true,"none":null}}}');
|
|
root.left = shared;
|
|
root.right = shared;
|
|
root.items = [shared, "text", 42, null, -0];
|
|
deepFreeze(root);
|
|
const loose = JSON.parse('{"a":{"b":1}}');
|
|
Object.freeze(loose);
|
|
let reply: any = undefined;
|
|
const worker = new Worker(new URL(import.meta.url), { workerData: root });
|
|
worker.on("message", (value: any) => {
|
|
if (typeof value === "string") console.log(value);
|
|
else if (value.from === "worker") reply = deepFreeze(value);
|
|
else console.log("main got", JSON.stringify(value));
|
|
});
|
|
worker.on("exit", (code: number) => {
|
|
console.log("exit", code);
|
|
console.log(describe("main after exit", root));
|
|
console.log("reply after exit", JSON.stringify(reply), Object.isFrozen(reply.list[1]));
|
|
});
|
|
worker.postMessage(root);
|
|
worker.postMessage(root);
|
|
worker.postMessage(loose);
|
|
loose.a.b = 2;
|
|
console.log(tryWrite("main", root));
|
|
console.log(describe("main", root));
|
|
} else {
|
|
const port = parentPort!;
|
|
const data = deepFreeze(workerData);
|
|
port.postMessage(describe("worker data", data));
|
|
port.postMessage(tryWrite("worker data", data));
|
|
let received = 0;
|
|
port.on("message", (value: any) => {
|
|
const graph = deepFreeze(value);
|
|
received++;
|
|
if (received <= 2) {
|
|
port.postMessage(describe("worker message " + received, graph));
|
|
port.postMessage({ round: received, count: graph.count, text: graph.left.text, zero: Object.is(graph.items[4], -0) });
|
|
} else {
|
|
port.postMessage({ round: received, loose: graph.a.b });
|
|
port.postMessage(deepFreeze(JSON.parse('{"from":"worker","list":[1,{"x":"y"}]}')));
|
|
port.close();
|
|
}
|
|
});
|
|
}
|