diff --git a/src/packages/core/package.json b/src/packages/core/package.json index 7a29e8f8d..f2d3b92e0 100644 --- a/src/packages/core/package.json +++ b/src/packages/core/package.json @@ -31,6 +31,7 @@ "@opentelemetry/resources": "1.25.1", "@opentelemetry/sdk-node": "0.52.1", "@opentelemetry/sdk-trace-base": "1.25.1", + "async-mutex": "0.5.0", "class-validator": "0.14.1", "dataloader": "2.2.2", "graphql": "16.9.0", diff --git a/src/packages/core/src/request-context.ts b/src/packages/core/src/request-context.ts index 4f90af30a..fe1ab3eed 100644 --- a/src/packages/core/src/request-context.ts +++ b/src/packages/core/src/request-context.ts @@ -1,6 +1,8 @@ -import { AsyncLocalStorage } from 'async_hooks'; -import { BaseLoader } from './base-loader'; +import { AsyncLocalStorage } from 'node:async_hooks'; import { logger } from '@exogee/logger'; +import { Mutex } from 'async-mutex'; + +import { BaseLoader } from './base-loader'; type Context = { BaseLoaders: BaseLoader; @@ -10,6 +12,10 @@ export class RequestContext { private static storage = new AsyncLocalStorage(); private static counter = 1; + // Workaround for environments that don't support AsyncLocalStorage + private static workaroundStorage: RequestContext | undefined; + private static mutex = new Mutex(); + readonly id = RequestContext.counter++; constructor(readonly context: Context) {} @@ -27,11 +33,32 @@ export class RequestContext { static async create(next: (...args: any[]) => T): Promise { const ctx = RequestContext.createContext(); logger.trace(`Creating RequestContext with ID: ${ctx.id}`); - return RequestContext.storage.run(ctx, next); + + // WebContainers don't support AsyncLocalStorage: + // https://github.com/stackblitz/webcontainer-core/issues/1169 + // + // To support use cases like this, if you want us to, we'll just use a singleton + // scratch pad that we mutex access to, which will enforce that only one request + // can happen at a time, but will not rely on the AsyncLocalStorage API. + // + // In standard Node this is not an issue, but if you need to turn off support for + // isolated async contexts, you can do so with this environment variable. + if (process.env.GRAPHWEAVER_DISABLE_ASYNC_LOCAL_STORAGE === 'true') { + logger.trace('AsyncLocalStorage is disabled, using workaround storage, creating context.'); + RequestContext.workaroundStorage = ctx; + return await RequestContext.mutex.runExclusive(next); + } else { + logger.trace('Creating new AsyncLocalStorage context.'); + return RequestContext.storage.run(ctx, next); + } } static currentRequestContext(): RequestContext | undefined { - return RequestContext.storage.getStore(); + if (process.env.GRAPHWEAVER_DISABLE_ASYNC_LOCAL_STORAGE === 'true') { + return RequestContext.workaroundStorage; + } else { + return RequestContext.storage.getStore(); + } } private static createContext(): RequestContext { diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index c0f518f0c..f75e891d7 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -1120,6 +1120,9 @@ importers: '@opentelemetry/sdk-trace-base': specifier: 1.25.1 version: 1.25.1(@opentelemetry/api@1.9.0) + async-mutex: + specifier: 0.5.0 + version: 0.5.0 class-validator: specifier: 0.14.1 version: 0.14.1