UNPKG

@whatwg-node/server

Version:

Fetch API compliant HTTP Server adapter

87 lines (86 loc) 3.82 kB
"use strict"; /** * ⚠️ straight up copy from @graphql-hive/signal@2.0.0 * * copied because of circular dependencies, where hive gateway monorepo uses whatwg server and also * hosts the signal package. importing that package from whatwg-server will fail with tsx. it was * thought that this was fixed with a package patch in hive gateway - but apparently not... */ Object.defineProperty(exports, "__esModule", { value: true }); exports.abortSignalAny = abortSignalAny; // https://github.com/unjs/std-env/blob/ab15595debec9e9115a9c1d31bc7597a8e71dbfd/src/runtimes.ts const isNode = !globalThis.Bun && globalThis.process?.release?.name === 'node'; const anySignalRegistry = isNode ? new FinalizationRegistry(cb => cb()) : null; const controllerInSignalSy = Symbol('CONTROLLER_IN_SIGNAL'); /** * Memory safe ponyfill of `AbortSignal.any`. In Node environments, the native * `AbortSignal.any` seems to be leaky and can lead to subtle memory leaks over * a larger period of time. * * This ponyfill is a custom implementation that makes sure AbortSignals get properly * GC-ed as well as aborted. */ function abortSignalAny(signals) { if (signals.length === 0) { // if no signals are passed, return undefined because the abortcontroller // wouldnt ever be aborted (should be when GCd, but it's only a waste of memory) // furthermore, the native AbortSignal.any will also never abort if receiving no signals return undefined; } if (signals.length === 1) { // no need to waste resources by wrapping a single signal, simply return it return signals[0]; } if (!isNode) { // AbortSignal.any seems to be leaky only in Node env // TODO: should we ponyfill other envs, will they always have AbortSignal.any? return AbortSignal.any(signals); } for (const signal of signals) { if (signal.aborted) { // if any of the signals has already been aborted, return it immediately no need to continue at all return signal; } } // we use weak refs for both the root controller and the passed signals // because we want to make sure that signals are aborted and disposed of // in both cases when GC-ed and actually aborted const ctrl = new AbortController(); const ctrlRef = new WeakRef(ctrl); const eventListenerPairs = []; let retainedSignalsCount = signals.length; for (const signal of signals) { const signalRef = new WeakRef(signal); function abort() { ctrlRef.deref()?.abort(signalRef.deref()?.reason); } signal.addEventListener('abort', abort); eventListenerPairs.push([signalRef, abort]); anySignalRegistry.register(signal, () => // dispose when all of the signals have been GCed !--retainedSignalsCount && dispose(), signal); } function dispose() { for (const [signalRef, abort] of eventListenerPairs) { const signal = signalRef.deref(); if (signal) { signal.removeEventListener('abort', abort); anySignalRegistry.unregister(signal); } const ctrl = ctrlRef.deref(); if (ctrl) { anySignalRegistry.unregister(ctrl.signal); // @ts-expect-error property will exist delete ctrl.signal[controllerInSignalSy]; } } } // cleanup when aborted ctrl.signal.addEventListener('abort', dispose); // cleanup when GCed anySignalRegistry.register(ctrl.signal, dispose, ctrl.signal); // keeping a strong reference of the cotroller binding it to the lifecycle of its signal // @ts-expect-error property will exist ctrl.signal[controllerInSignalSy] = ctrl; return ctrl.signal; }