diff --git a/lib/diagnostics_channel.js b/lib/diagnostics_channel.js index 7b78851208df..8aef9ed62dc7 100644 --- a/lib/diagnostics_channel.js +++ b/lib/diagnostics_channel.js @@ -20,6 +20,7 @@ const { } = primordials; const { + aggregateTwoErrors, codes: { ERR_INVALID_ARG_TYPE, }, @@ -84,12 +85,25 @@ function maybeMarkInactive(channel) { } } +function disposeStoreScopes(stack, error, hasError = false) { + for (let i = stack.length - 1; i >= 0; i--) { + try { + stack[i][SymbolDispose](); + } catch (err) { + error = hasError ? aggregateTwoErrors(err, error) : err; + hasError = true; + } + } + + if (hasError) throw error; +} + class RunStoresScope { #stack; constructor(activeChannel, data) { - // eslint-disable-next-line no-restricted-globals - using stack = new DisposableStack(); + // TODO: use native DisposableStack once supported in core + const stack = []; // Enter stores using withScope if (activeChannel._stores) { @@ -109,7 +123,11 @@ class RunStoresScope { } } - stack.use(store.withScope(newContext)); + try { + ArrayPrototypePush(stack, store.withScope(newContext)); + } catch (err) { + disposeStoreScopes(stack, err, true); + } } } @@ -117,11 +135,17 @@ class RunStoresScope { activeChannel.publish(data); // Transfer ownership of the stack - this.#stack = stack.move(); + // TODO: Restore stack.move() when DisposableStack is restored in core to avoid broken behavior. + this.#stack = stack; } [SymbolDispose]() { - this.#stack[SymbolDispose](); + if (this.#stack !== undefined) { + const stack = this.#stack; + this.#stack = undefined; + + disposeStoreScopes(stack); + } } } @@ -197,9 +221,12 @@ class ActiveChannel { } runStores(data, fn, thisArg, ...args) { - // eslint-disable-next-line no-unused-vars - using scope = this.withStoreScope(data); - return ReflectApply(fn, thisArg, args); + const scope = this.withStoreScope(data); + try { + return ReflectApply(fn, thisArg, args); + } finally { + scope[SymbolDispose](); + } } } @@ -396,9 +423,12 @@ class BoundedChannel { run(context, fn, thisArg, ...args) { context ??= {}; - // eslint-disable-next-line no-unused-vars - using scope = this.withScope(context); - return ReflectApply(fn, thisArg, args); + const scope = this.withScope(context); + try { + return ReflectApply(fn, thisArg, args); + } finally { + scope[SymbolDispose](); + } } } @@ -520,9 +550,8 @@ class TracingChannel { } const { error } = this; + const scope = this.#callWindow.withScope(context); - // eslint-disable-next-line no-unused-vars - using scope = this.#callWindow.withScope(context); try { const result = ReflectApply(fn, thisArg, args); context.result = result; @@ -531,6 +560,8 @@ class TracingChannel { context.error = err; error.publish(context); throw err; + } finally { + scope[SymbolDispose](); } } @@ -550,9 +581,9 @@ class TracingChannel { context.error = err; error.publish(context); // Use continuation window for asyncStart/asyncEnd - // eslint-disable-next-line no-unused-vars - using scope = continuationWindow.withScope(context); + const scope = continuationWindow.withScope(context); // TODO: Is there a way to have asyncEnd _after_ the continuation? + scope[SymbolDispose](); } function onRejectWithRethrow(err) { @@ -563,14 +594,14 @@ class TracingChannel { function onResolve(result) { context.result = result; // Use continuation window for asyncStart/asyncEnd - // eslint-disable-next-line no-unused-vars - using scope = continuationWindow.withScope(context); + const scope = continuationWindow.withScope(context); // TODO: Is there a way to have asyncEnd _after_ the continuation? + scope[SymbolDispose](); return result; } - // eslint-disable-next-line no-unused-vars - using scope = this.#callWindow.withScope(context); + const scope = this.#callWindow.withScope(context); + try { const result = ReflectApply(fn, thisArg, args); // If the return value is not a thenable, return it directly with a warning. @@ -595,6 +626,8 @@ class TracingChannel { context.error = err; error.publish(context); throw err; + } finally { + scope[SymbolDispose](); } } @@ -615,23 +648,28 @@ class TracingChannel { } // Use continuation window for asyncStart/asyncEnd around callback - // eslint-disable-next-line no-unused-vars - using scope = continuationWindow.withScope(context); - return ReflectApply(callback, this, arguments); + const scope = continuationWindow.withScope(context); + try { + return ReflectApply(callback, this, arguments); + } finally { + scope[SymbolDispose](); + } } const callback = ArrayPrototypeAt(args, position); validateFunction(callback, 'callback'); ArrayPrototypeSplice(args, position, 1, wrappedCallback); - // eslint-disable-next-line no-unused-vars - using scope = this.#callWindow.withScope(context); + const scope = this.#callWindow.withScope(context); + try { return ReflectApply(fn, thisArg, args); } catch (err) { context.error = err; error.publish(context); throw err; + } finally { + scope[SymbolDispose](); } } } @@ -651,4 +689,4 @@ module.exports = { boundedChannel, Channel, BoundedChannel, -}; +}; \ No newline at end of file