Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 116 additions & 81 deletions lib/diagnostics_channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,40 +88,51 @@ class RunStoresScope {
#stack;

constructor(activeChannel, data) {
// eslint-disable-next-line no-restricted-globals
using stack = new DisposableStack();

// Enter stores using withScope
if (activeChannel._stores) {
for (const entry of activeChannel._stores.entries()) {
const store = entry[0];
const transform = entry[1];

let newContext = data;
if (transform) {
try {
newContext = transform(data);
} catch (err) {
process.nextTick(() => {
triggerUncaughtException(err, false);
});
continue;
const stack = [];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const stack = [];
// TODO: use native DisposableStack once supported in core
const stack = [];

try {
// Enter stores using withScope
if (activeChannel._stores) {
for (const entry of activeChannel._stores.entries()) {
const store = entry[0];
const transform = entry[1];

let newContext = data;
if (transform) {
try {
newContext = transform(data);
} catch (err) {
process.nextTick(() => {
triggerUncaughtException(err, false);
});
continue;
}
}
}

stack.use(store.withScope(newContext));
ArrayPrototypePush(stack, store.withScope(newContext));
}
}
}

// Publish data
activeChannel.publish(data);
// Publish data
activeChannel.publish(data);

// Transfer ownership of the stack
this.#stack = stack.move();
// Transfer ownership of the stack
this.#stack = stack;
} finally {
if (this.#stack === undefined) {
for (let i = stack.length - 1; i >= 0; i--) {
stack[i][SymbolDispose]();
}
}
}
Comment on lines +120 to +126

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#stack is not disposed by the constructor. This code should be removed, and the try ... finally block in the constructor is not needed.

}

[SymbolDispose]() {
this.#stack[SymbolDispose]();
if (this.#stack !== undefined) {
for (let i = this.#stack.length - 1; i >= 0; i--) {
this.#stack[i][SymbolDispose]();
}
this.#stack = undefined;
Comment on lines +131 to +134

@Renegade334 Renegade334 Jul 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (let i = this.#stack.length - 1; i >= 0; i--) {
this.#stack[i][SymbolDispose]();
}
this.#stack = undefined;
const stack = this.#stack;
this.#stack = undefined;
for (let i = stack.length - 1; i >= 0; i--) {
stack[i][SymbolDispose]();
}

Matches the order of operations of DisposableStack.prototype.dispose(), in case anything touches the stack during disposal.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can any of the store disposers ever throw? If so, we need error handling here as well.

}
}
}

Expand Down Expand Up @@ -197,9 +208,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]();
}
}
}

Expand Down Expand Up @@ -396,9 +410,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]();
}
}
}

Expand Down Expand Up @@ -521,16 +538,19 @@ class TracingChannel {

const { error } = this;

// 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);
context.result = result;
return result;
} catch (err) {
context.error = err;
error.publish(context);
throw err;
try {
const result = ReflectApply(fn, thisArg, args);
Comment thread
ayush23chaudhary marked this conversation as resolved.
context.result = result;
return result;
} catch (err) {
context.error = err;
error.publish(context);
throw err;
}
} finally {
scope[SymbolDispose]();
}
}

Expand All @@ -550,9 +570,12 @@ 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);
// TODO: Is there a way to have asyncEnd _after_ the continuation?
const scope = continuationWindow.withScope(context);
try {
// TODO: Is there a way to have asyncEnd _after_ the continuation?
} finally {
scope[SymbolDispose]();
}
}

function onRejectWithRethrow(err) {
Expand All @@ -563,38 +586,44 @@ 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);
// TODO: Is there a way to have asyncEnd _after_ the continuation?
return result;
const scope = continuationWindow.withScope(context);
try {
// TODO: Is there a way to have asyncEnd _after_ the continuation?
return result;
} finally {
scope[SymbolDispose]();
}
}

// 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.
// Do not publish to asyncStart/asyncEnd.
if (typeof result?.then !== 'function') {
emitNonThenableWarning(fn);
context.result = result;
try {
const result = ReflectApply(fn, thisArg, args);
// If the return value is not a thenable, return it directly with a warning.
Comment thread
ayush23chaudhary marked this conversation as resolved.
// Do not publish to asyncStart/asyncEnd.
if (typeof result?.then !== 'function') {
emitNonThenableWarning(fn);
context.result = result;
return result;
}
// isPromise() matches sub-classes, but we need to match only direct
// instances of the native Promise type to safely use PromisePrototypeThen.
if (isPromise(result) && ObjectGetPrototypeOf(result) === PromisePrototype) {
return PromisePrototypeThen(result, onResolve, onRejectWithRethrow);
}
// For non-native thenables, subscribe to the result but return the
// original thenable so the consumer can continue handling it directly.
// Non-native thenables don't have unhandledRejection tracking, so
// swallowing the rejection here doesn't change existing behaviour.
result.then(onResolve, onReject);
return result;
} catch (err) {
context.error = err;
error.publish(context);
throw err;
}
// isPromise() matches sub-classes, but we need to match only direct
// instances of the native Promise type to safely use PromisePrototypeThen.
if (isPromise(result) && ObjectGetPrototypeOf(result) === PromisePrototype) {
return PromisePrototypeThen(result, onResolve, onRejectWithRethrow);
}
// For non-native thenables, subscribe to the result but return the
// original thenable so the consumer can continue handling it directly.
// Non-native thenables don't have unhandledRejection tracking, so
// swallowing the rejection here doesn't change existing behaviour.
result.then(onResolve, onReject);
return result;
} catch (err) {
context.error = err;
error.publish(context);
throw err;
} finally {
scope[SymbolDispose]();
}
}

Expand All @@ -615,23 +644,29 @@ 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;
try {
return ReflectApply(fn, thisArg, args);
} catch (err) {
Comment thread
ayush23chaudhary marked this conversation as resolved.
context.error = err;
error.publish(context);
throw err;
}
} finally {
scope[SymbolDispose]();
}
}
}
Expand Down
Loading