mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
diagnostics_channel: lazily create tracing context
Default parameters are evaluated before the function body, so the tracing context is allocated before the no-subscriber fast path. Initialize it only for subscribed traces. Preserve method arity with an undefined default and preserve explicit null behavior. Add focused regression coverage and reusable TracingChannel benchmarks. Signed-off-by: Romain Lanz <romain.lanz@pm.me> PR-URL: https://github.com/nodejs/node/pull/65513 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br>
This commit is contained in:
1 parent
8e8ada7f6b
commit
2befec51ae
4 files changed
+191
-2
No files matched your search
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common.js');
|
||||
const dc = require('node:diagnostics_channel');
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
n: [1e7],
|
||||
context: ['omitted', 'undefined', 'provided'],
|
||||
subscribers: [0, 1],
|
||||
});
|
||||
|
||||
function noop() {}
|
||||
|
||||
const thenable = {
|
||||
then(onResolve) {
|
||||
onResolve(undefined);
|
||||
},
|
||||
};
|
||||
|
||||
function returnThenable() {
|
||||
return thenable;
|
||||
}
|
||||
|
||||
function main({ n, context, subscribers }) {
|
||||
const channel = dc.tracingChannel('test');
|
||||
const providedContext = { __proto__: null };
|
||||
|
||||
if (subscribers) {
|
||||
channel.subscribe({ start: noop });
|
||||
}
|
||||
|
||||
bench.start();
|
||||
switch (context) {
|
||||
case 'omitted':
|
||||
for (let i = 0; i < n; i++) {
|
||||
channel.tracePromise(returnThenable);
|
||||
}
|
||||
break;
|
||||
case 'undefined':
|
||||
for (let i = 0; i < n; i++) {
|
||||
channel.tracePromise(returnThenable, undefined);
|
||||
}
|
||||
break;
|
||||
case 'provided':
|
||||
for (let i = 0; i < n; i++) {
|
||||
channel.tracePromise(returnThenable, providedContext);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported context value: ${context}`);
|
||||
}
|
||||
bench.end(n);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common.js');
|
||||
const dc = require('node:diagnostics_channel');
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
n: [1e7],
|
||||
context: ['omitted', 'undefined', 'provided'],
|
||||
subscribers: [0, 1],
|
||||
});
|
||||
|
||||
function noop() {}
|
||||
|
||||
function main({ n, context, subscribers }) {
|
||||
const channel = dc.tracingChannel('test');
|
||||
const providedContext = { __proto__: null };
|
||||
|
||||
if (subscribers) {
|
||||
channel.subscribe({ start: noop });
|
||||
}
|
||||
|
||||
bench.start();
|
||||
switch (context) {
|
||||
case 'omitted':
|
||||
for (let i = 0; i < n; i++) {
|
||||
channel.traceSync(noop);
|
||||
}
|
||||
break;
|
||||
case 'undefined':
|
||||
for (let i = 0; i < n; i++) {
|
||||
channel.traceSync(noop, undefined);
|
||||
}
|
||||
break;
|
||||
case 'provided':
|
||||
for (let i = 0; i < n; i++) {
|
||||
channel.traceSync(noop, providedContext);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported context value: ${context}`);
|
||||
}
|
||||
bench.end(n);
|
||||
}
|
||||
@@ -522,11 +522,15 @@ class TracingChannel {
|
||||
return done;
|
||||
}
|
||||
|
||||
traceSync(fn, context = { __proto__: null }, thisArg, ...args) {
|
||||
traceSync(fn, context = undefined, thisArg, ...args) {
|
||||
if (!this.hasSubscribers) {
|
||||
return ReflectApply(fn, thisArg, args);
|
||||
}
|
||||
|
||||
if (context === undefined) {
|
||||
context = { __proto__: null };
|
||||
}
|
||||
|
||||
const { error } = this;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
@@ -542,7 +546,7 @@ class TracingChannel {
|
||||
}
|
||||
}
|
||||
|
||||
tracePromise(fn, context = { __proto__: null }, thisArg, ...args) {
|
||||
tracePromise(fn, context = undefined, thisArg, ...args) {
|
||||
if (!this.hasSubscribers) {
|
||||
const result = ReflectApply(fn, thisArg, args);
|
||||
if (typeof result?.then !== 'function') {
|
||||
@@ -551,6 +555,10 @@ class TracingChannel {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (context === undefined) {
|
||||
context = { __proto__: null };
|
||||
}
|
||||
|
||||
const { error } = this;
|
||||
const continuationWindow = this.#continuationWindow;
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
// This test ensures that tracing channels create an independent mutable
|
||||
// context for each call while preserving explicitly provided contexts.
|
||||
|
||||
const assert = require('node:assert');
|
||||
const dc = require('node:diagnostics_channel');
|
||||
|
||||
const lengthChannel = dc.tracingChannel('test:length');
|
||||
assert.strictEqual(lengthChannel.traceSync.length, 1);
|
||||
assert.strictEqual(lengthChannel.tracePromise.length, 1);
|
||||
|
||||
function subscribe(channel, contexts, isPromise = false) {
|
||||
let endCalls = 0;
|
||||
let asyncStartCalls = 0;
|
||||
let asyncEndCalls = 0;
|
||||
|
||||
channel.subscribe({
|
||||
start: common.mustCall((context) => {
|
||||
context.invocation = contexts.length;
|
||||
contexts.push(context);
|
||||
}, 4),
|
||||
end: common.mustCall((context) => {
|
||||
assert.strictEqual(context.invocation, endCalls++);
|
||||
}, 4),
|
||||
asyncStart: common.mustCall((context) => {
|
||||
assert.strictEqual(context.invocation, asyncStartCalls++);
|
||||
}, isPromise ? 4 : 0),
|
||||
asyncEnd: common.mustCall((context) => {
|
||||
assert.strictEqual(context.invocation, asyncEndCalls++);
|
||||
}, isPromise ? 4 : 0),
|
||||
});
|
||||
}
|
||||
|
||||
const syncChannel = dc.tracingChannel('test:sync-context');
|
||||
const syncContexts = [];
|
||||
subscribe(syncChannel, syncContexts);
|
||||
|
||||
const syncProvided = { provided: true };
|
||||
assert.strictEqual(syncChannel.traceSync(() => 'omitted'), 'omitted');
|
||||
assert.strictEqual(syncChannel.traceSync(() => 'undefined', undefined),
|
||||
'undefined');
|
||||
assert.strictEqual(syncChannel.traceSync(() => 'provided', syncProvided),
|
||||
'provided');
|
||||
|
||||
assert.strictEqual(Object.getPrototypeOf(syncContexts[0]), null);
|
||||
assert.strictEqual(Object.getPrototypeOf(syncContexts[1]), null);
|
||||
assert.notStrictEqual(syncContexts[0], syncContexts[1]);
|
||||
assert.strictEqual(syncContexts[2], syncProvided);
|
||||
assert.deepStrictEqual(syncContexts.slice(0, 3).map(({ result }) => result),
|
||||
['omitted', 'undefined', 'provided']);
|
||||
|
||||
const syncError = new Error('sync');
|
||||
assert.throws(() => syncChannel.traceSync(() => {
|
||||
throw syncError;
|
||||
}), (error) => error === syncError);
|
||||
assert.strictEqual(syncContexts[3].error, syncError);
|
||||
|
||||
const promiseChannel = dc.tracingChannel('test:promise-context');
|
||||
const promiseContexts = [];
|
||||
subscribe(promiseChannel, promiseContexts, true);
|
||||
|
||||
const promiseProvided = { provided: true };
|
||||
const promiseError = new Error('promise');
|
||||
Promise.resolve()
|
||||
.then(() => promiseChannel.tracePromise(() => Promise.resolve('omitted')))
|
||||
.then(() => promiseChannel.tracePromise(
|
||||
() => Promise.resolve('undefined'), undefined))
|
||||
.then(() => promiseChannel.tracePromise(
|
||||
() => Promise.resolve('provided'), promiseProvided))
|
||||
.then(() => assert.rejects(
|
||||
promiseChannel.tracePromise(() => Promise.reject(promiseError)),
|
||||
promiseError))
|
||||
.then(common.mustCall(() => {
|
||||
assert.strictEqual(Object.getPrototypeOf(promiseContexts[0]), null);
|
||||
assert.strictEqual(Object.getPrototypeOf(promiseContexts[1]), null);
|
||||
assert.notStrictEqual(promiseContexts[0], promiseContexts[1]);
|
||||
assert.strictEqual(promiseContexts[2], promiseProvided);
|
||||
assert.deepStrictEqual(
|
||||
promiseContexts.slice(0, 3).map(({ result }) => result),
|
||||
['omitted', 'undefined', 'provided']);
|
||||
assert.strictEqual(promiseContexts[3].error, promiseError);
|
||||
}));
|
||||
Reference in new issue
Block a user