/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/unit_node/async_hooks_test.ts
306 строк
8 KB
Bartek Iwańczuk
fix(ext/node): preserve AsyncLocalStorage context across node:net callbacks (#35237)
15 июн 2026, 20:59
Не верифицирован
15 июн 2026, 20:59
e602dfe
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. import { AsyncLocalStorage, AsyncResource, createHook } from "node:async_hooks"; import process from "node:process"; import { clearImmediate, setImmediate } from "node:timers"; import { assert, assertEquals } from "@std/assert"; Deno.test(async function foo() { const asyncLocalStorage = new AsyncLocalStorage(); const out: string[] = []; function logWithId(msg: string) { const id = asyncLocalStorage.getStore(); out.push(`${id !== undefined ? id : "-"}: ${msg}`); } async function exec() { logWithId("start"); await new Promise((resolve) => setTimeout(resolve, 100)); logWithId("finish"); } for (const foo of [1, 2, 3]) { asyncLocalStorage.run(foo, exec); } await new Promise((resolve) => setTimeout(resolve, 500)); assertEquals(out, [ "1: start", "2: start", "3: start", "1: finish", "2: finish", "3: finish", ]); }); Deno.test(async function bar() { let differentScopeDone = false; const als = new AsyncLocalStorage(); const ac = new AbortController(); const server = Deno.serve({ signal: ac.signal, port: 4000, }, () => { const differentScope = als.run(123, () => AsyncResource.bind(() => { differentScopeDone = true; })); return als.run("Hello World", async () => { // differentScope is attached to a different async context, so // it will see a different value for als.getStore() (123) setTimeout(differentScope, 5); // Some simulated async delay. await new Promise((res) => setTimeout(res, 10)); return new Response(als.getStore() as string); // "Hello World" }); }); const res = await fetch("http://localhost:4000"); assertEquals(await res.text(), "Hello World"); ac.abort(); await server.finished; assert(differentScopeDone); }); Deno.test(async function nested() { const als = new AsyncLocalStorage(); const deferred = Promise.withResolvers(); const deferred1 = Promise.withResolvers(); als.run(null, () => { als.run({ x: 1 }, () => { deferred.resolve(als.getStore()); }); deferred1.resolve(als.getStore()); }); assertEquals(await deferred.promise, { x: 1 }); assertEquals(await deferred1.promise, null); }); Deno.test(async function enterWith() { const als = new AsyncLocalStorage(); const deferred = Promise.withResolvers(); const deferred1 = Promise.withResolvers(); als.run(null, () => { als.run({ x: 1 }, () => { als.enterWith({ x: 2 }); deferred.resolve(als.getStore()); }); deferred1.resolve(als.getStore()); }); assertEquals(await deferred.promise, { x: 2 }); assertEquals(await deferred1.promise, null); }); Deno.test(async function snapshot() { const als = new AsyncLocalStorage(); const deferred = Promise.withResolvers(); als.run(null, () => { const snapshot = AsyncLocalStorage.snapshot(); als.run({ x: 1 }, () => { deferred.resolve(snapshot(() => als.getStore())); }); }); assertEquals(await deferred.promise, null); }); Deno.test(async function bind() { const als = new AsyncLocalStorage(); const deferred = Promise.withResolvers(); const bound = als.run(null, () => { return AsyncLocalStorage.bind(() => { deferred.resolve(als.getStore()); }); }); als.run({ x: 1 }, () => { bound(); }); assertEquals(await deferred.promise, null); }); Deno.test(function asyncResourceStub() { const resource = new AsyncResource("dbquery"); assert(typeof resource.asyncId() === "number"); }); Deno.test(function emitDestroyStub() { const resource = new AsyncResource("foo"); assert(typeof resource.emitDestroy === "function"); }); Deno.test(function runInAsyncScopeFiresBeforeAndAfter() { const events: string[] = []; const resource = new AsyncResource("MYRES"); const targetId = resource.asyncId(); const hook = createHook({ before(asyncId) { if (asyncId === targetId) events.push("before"); }, after(asyncId) { if (asyncId === targetId) events.push("after"); }, }); hook.enable(); try { resource.runInAsyncScope(() => { events.push("fn"); }, null); } finally { hook.disable(); } assertEquals(events, ["before", "fn", "after"]); }); Deno.test(function clearImmediateEmitsDestroy() { let immediateAsyncId: number | undefined; const destroyed: number[] = []; const hook = createHook({ init(asyncId, type) { if (type === "Immediate") { immediateAsyncId = asyncId; } }, destroy(asyncId) { destroyed.push(asyncId); }, }); hook.enable(); try { const immediate = setImmediate(() => {}); assert(typeof immediateAsyncId === "number"); clearImmediate(immediate); assert(destroyed.includes(immediateAsyncId!)); } finally { hook.disable(); } }); Deno.test(async function worksWithAsyncAPIs() { const store = new AsyncLocalStorage(); const test = () => assertEquals(store.getStore(), "data"); await store.run("data", async () => { test(); queueMicrotask(() => test()); process.nextTick(() => test()); setImmediate(() => test()); setTimeout(() => test(), 0); const intervalId = setInterval(() => { test(); clearInterval(intervalId); }, 0); store.run("data2", () => { assertEquals(store.getStore(), "data2"); }); await new Promise((r) => setTimeout(r, 50)); test(); }); }); Deno.test(async function worksWithDynamicImports() { const store = new AsyncLocalStorage(); // @ts-expect-error implicit any globalThis.alsDynamicImport = () => store.getStore(); const dataUrl = `data:application/javascript,export const data = alsDynamicImport()`; await store.run("data", async () => { const { data } = await import(dataUrl); assertEquals(data, "data"); }); }); Deno.test(async function asyncLocalStoragePreservedInStreamFinished() { const http = await import("node:http"); const { finished } = await import("node:stream"); const als = new AsyncLocalStorage(); const store = { foo: "bar" }; const { promise, resolve, reject } = Promise.withResolvers<void>(); const server = http.createServer((_req, res) => { als.run(store, () => { finished(res, () => { try { assertEquals(als.getStore(), store); resolve(); } catch (e) { reject(e); } }); }); setTimeout(() => res.end(), 0); }); server.listen(0, () => { const addr = server.address()!; const port = typeof addr === "string" ? addr : addr.port; http.get(`http://127.0.0.1:${port}`, (res) => { res.resume(); res.on("end", () => { server.close(); http.globalAgent.destroy(); }); }); }); await promise; }); // Regression test for https://github.com/denoland/deno/issues/35154. The // native libuv callbacks for node:net connect/accept invoke their completion // callbacks directly, so the async context that was active when the operation // was started has to be captured and restored, otherwise it is lost inside the // `connect` and `connection` handlers. Deno.test(async function asyncLocalStoragePreservedInNetCallbacks() { const net = await import("node:net"); const als = new AsyncLocalStorage(); const observed: { serverConnection: unknown; clientConnect: unknown } = { serverConnection: null, clientConnect: null, }; await new Promise<void>((resolve, reject) => { als.run("server-context", () => { const server = net.createServer((socket) => { observed.serverConnection = als.getStore() ?? null; socket.end("ok"); server.close(); }); server.once("error", reject); server.listen(0, () => { const { port } = server.address() as { port: number }; als.run("client-context", () => { const client = net.connect({ port }, () => { observed.clientConnect = als.getStore() ?? null; }); client.once("error", reject); client.once("data", () => { client.destroy(); resolve(); }); }); }); }); }); assertEquals(observed, { serverConnection: "server-context", clientConnect: "client-context", }); });