From 0bc6bf5d33b8198253954d7f04558270de45c925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 17 Mar 2023 13:53:57 -0400 Subject: [PATCH] chore: add tests for node:async_hooks (#18004) Closes https://github.com/denoland/deno/issues/17878 --------- Co-authored-by: crowlkats --- cli/tests/unit_node/async_hooks_test.ts | 83 +++++++++++++++++++++++++ ext/node/polyfills/async_hooks.ts | 6 +- 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 cli/tests/unit_node/async_hooks_test.ts diff --git a/cli/tests/unit_node/async_hooks_test.ts b/cli/tests/unit_node/async_hooks_test.ts new file mode 100644 index 0000000000..73d6a99bc8 --- /dev/null +++ b/cli/tests/unit_node/async_hooks_test.ts @@ -0,0 +1,83 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { AsyncLocalStorage, AsyncResource } from "node:async_hooks"; +import { + assert, + assertEquals, +} from "../../../test_util/std/testing/asserts.ts"; +import { deferred } from "../../../test_util/std/async/deferred.ts"; + +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(() => { + 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" + }); + }, { + signal: ac.signal, + port: 4000, + }); + + const res = await fetch("http://localhost:4000"); + assertEquals(await res.text(), "Hello World"); + ac.abort(); + await server; + assert(differentScopeDone); +}); + +Deno.test(async function nested() { + const als = new AsyncLocalStorage(); + const promise = deferred(); + const promise1 = deferred(); + + als.run(null, () => { + als.run({ x: 1 }, () => { + promise.resolve(als.getStore()); + }); + promise1.resolve(als.getStore()); + }); + + assertEquals(await promise, { x: 1 }); + assertEquals(await promise1, null); +}); diff --git a/ext/node/polyfills/async_hooks.ts b/ext/node/polyfills/async_hooks.ts index 899e578cc7..8111af73b2 100644 --- a/ext/node/polyfills/async_hooks.ts +++ b/ext/node/polyfills/async_hooks.ts @@ -84,7 +84,7 @@ class AsyncContextFrame { const propagate = (parent: AsyncContextFrame) => { parent.storage = parent.storage.filter((entry) => !entry.key.isDead()); - parent.storage.forEach((entry) => this.storage.push(entry)); + parent.storage.forEach((entry) => this.storage.push(entry.clone())); if (maybeStorageEntry) { const existingEntry = this.storage.find((entry) => @@ -250,6 +250,10 @@ class StorageEntry { this.key = key; this.value = value; } + + clone() { + return new StorageEntry(this.key, this.value); + } } class StorageKey {