mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-25 06:24:27 +08:00
stackless-async-context.ts woke two concurrent workers from 2 ms and 1 ms timers. The deadlines were taken when each worker scheduled its timer, so on a slow build both could be due on the same tick in either order. Those workers now wake from gates that chained timer callbacks open one per turn in a fixed order. Each woken body still crosses a timer and nested awaits with its own AsyncLocalStorage context. The sequential chain workers keep one pending timer at a time. stackless-async-ordering.ts scheduled setTimeout(0) and setImmediate from the main module, where Node leaves their order to timing; the lines never printed only because main finished first. Main now awaits a timer whose callback schedules the immediate, a microtask and a tick, so the macrotask order is pinned and printed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
2.8 KiB
TypeScript
86 lines
2.8 KiB
TypeScript
// AsyncLocalStorage across stackless async bodies: a call inherits the
|
|
// caller's context when it starts, keeps it across every await even after
|
|
// the caller's run() window closed, nested runs inside the body shadow and
|
|
// restore around awaits, enterWith inside a body stays inside that body,
|
|
// and concurrent bodies keep their own contexts while their continuations
|
|
// interleave on the microtask queue and across timers. The concurrent
|
|
// bodies wake from gates that chained timer callbacks open one per turn in
|
|
// a fixed order, so their interleaving never depends on timer deadlines.
|
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
|
const store = new AsyncLocalStorage<string>();
|
|
const log: string[] = [];
|
|
const say = (s: string): void => {
|
|
log.push(s);
|
|
};
|
|
const current = (): string => store.getStore() ?? "none";
|
|
|
|
function sleep(ms: number): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
setTimeout(resolve, ms);
|
|
});
|
|
}
|
|
|
|
const opens: (() => void)[] = [];
|
|
function gate(): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
opens.push(resolve);
|
|
});
|
|
}
|
|
|
|
// Opens the gates in `order`, each from its own timer callback scheduled by
|
|
// the previous one, so each woken body runs before the next gate opens.
|
|
function openInOrder(order: number[], at: number): void {
|
|
if (at === order.length) return;
|
|
setTimeout(() => {
|
|
say("open gate " + order[at]!);
|
|
opens[order[at]!]!();
|
|
openInOrder(order, at + 1);
|
|
}, 1);
|
|
}
|
|
|
|
async function worker(id: number, ready: Promise<void>): Promise<string> {
|
|
say("worker " + id + " start " + current());
|
|
await null;
|
|
say("worker " + id + " after hop " + current());
|
|
await ready;
|
|
say("worker " + id + " after timer " + current());
|
|
const inner = await store.run("inner" + id, async () => {
|
|
await null;
|
|
return current();
|
|
});
|
|
say("worker " + id + " inner " + inner + " back " + current());
|
|
return current();
|
|
}
|
|
|
|
async function entering(): Promise<string> {
|
|
say("entering start " + current());
|
|
store.enterWith("entered");
|
|
await Promise.resolve();
|
|
say("entering after await " + current());
|
|
return current();
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const a = store.run("a", () => worker(1, gate()));
|
|
const b = store.run("b", () => worker(2, gate()));
|
|
const c = worker(3, gate());
|
|
say("main after spawns " + current());
|
|
openInOrder([1, 2, 0], 0);
|
|
const all: Promise<string>[] = [a, b, c];
|
|
const results = await Promise.all(all);
|
|
say("results " + results.join(" "));
|
|
const e = await store.run("outer", () => entering());
|
|
say("entering returned " + e + " main sees " + current());
|
|
await store.run("chain", async () => {
|
|
const first = await worker(4, sleep(1));
|
|
const second = await worker(5, sleep(2));
|
|
say("chain " + first + " " + second);
|
|
});
|
|
say("main end " + current());
|
|
}
|
|
|
|
main().then(() => {
|
|
for (const line of log) console.log(line);
|
|
});
|