Skip to content

Commit 01b6da5

Browse files
Stephen Belangerrdw-msft
Stephen Belanger
authored andcommitted
diagnostics_channel: early-exit tracing channel trace methods
PR-URL: nodejs#51915 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent dd4caad commit 01b6da5

12 files changed

+250
-100
lines changed

doc/api/diagnostics_channel.md

+20
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,11 @@ if the given function throws an error. This will run the given function using
789789
[`channel.runStores(context, ...)`][] on the `start` channel which ensures all
790790
events should have any bound stores set to match this trace context.
791791

792+
To ensure only correct trace graphs are formed, events will only be published
793+
if subscribers are present prior to starting the trace. Subscriptions which are
794+
added after the trace begins will not receive future events from that trace,
795+
only future traces will be seen.
796+
792797
```mjs
793798
import diagnostics_channel from 'node:diagnostics_channel';
794799

@@ -838,6 +843,11 @@ returned promise rejects. This will run the given function using
838843
[`channel.runStores(context, ...)`][] on the `start` channel which ensures all
839844
events should have any bound stores set to match this trace context.
840845

846+
To ensure only correct trace graphs are formed, events will only be published
847+
if subscribers are present prior to starting the trace. Subscriptions which are
848+
added after the trace begins will not receive future events from that trace,
849+
only future traces will be seen.
850+
841851
```mjs
842852
import diagnostics_channel from 'node:diagnostics_channel';
843853

@@ -891,6 +901,11 @@ the callback is set. This will run the given function using
891901
[`channel.runStores(context, ...)`][] on the `start` channel which ensures all
892902
events should have any bound stores set to match this trace context.
893903

904+
To ensure only correct trace graphs are formed, events will only be published
905+
if subscribers are present prior to starting the trace. Subscriptions which are
906+
added after the trace begins will not receive future events from that trace,
907+
only future traces will be seen.
908+
894909
```mjs
895910
import diagnostics_channel from 'node:diagnostics_channel';
896911

@@ -979,6 +994,11 @@ of the callback while the `error` will either be a thrown error visible in the
979994
`end` event or the first callback argument in either of the `asyncStart` or
980995
`asyncEnd` events.
981996

997+
To ensure only correct trace graphs are formed, events should only be published
998+
if subscribers are present prior to starting the trace. Subscriptions which are
999+
added after the trace begins should not receive future events from that trace,
1000+
only future traces will be seen.
1001+
9821002
Tracing channels should follow a naming pattern of:
9831003

9841004
* `tracing:module.class.method:start` or `tracing:module.function:start`

lib/diagnostics_channel.js

+42-24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
ArrayPrototypePush,
77
ArrayPrototypeSplice,
88
SafeFinalizationRegistry,
9+
ObjectDefineProperty,
910
ObjectGetPrototypeOf,
1011
ObjectSetPrototypeOf,
1112
Promise,
@@ -250,35 +251,40 @@ function assertChannel(value, name) {
250251
}
251252
}
252253

254+
function tracingChannelFrom(nameOrChannels, name) {
255+
if (typeof nameOrChannels === 'string') {
256+
return channel(`tracing:${nameOrChannels}:${name}`);
257+
}
258+
259+
if (typeof nameOrChannels === 'object' && nameOrChannels !== null) {
260+
const channel = nameOrChannels[name];
261+
assertChannel(channel, `nameOrChannels.${name}`);
262+
return channel;
263+
}
264+
265+
throw new ERR_INVALID_ARG_TYPE('nameOrChannels',
266+
['string', 'object', 'TracingChannel'],
267+
nameOrChannels);
268+
}
269+
253270
class TracingChannel {
254271
constructor(nameOrChannels) {
255-
if (typeof nameOrChannels === 'string') {
256-
this.start = channel(`tracing:${nameOrChannels}:start`);
257-
this.end = channel(`tracing:${nameOrChannels}:end`);
258-
this.asyncStart = channel(`tracing:${nameOrChannels}:asyncStart`);
259-
this.asyncEnd = channel(`tracing:${nameOrChannels}:asyncEnd`);
260-
this.error = channel(`tracing:${nameOrChannels}:error`);
261-
} else if (typeof nameOrChannels === 'object') {
262-
const { start, end, asyncStart, asyncEnd, error } = nameOrChannels;
263-
264-
assertChannel(start, 'nameOrChannels.start');
265-
assertChannel(end, 'nameOrChannels.end');
266-
assertChannel(asyncStart, 'nameOrChannels.asyncStart');
267-
assertChannel(asyncEnd, 'nameOrChannels.asyncEnd');
268-
assertChannel(error, 'nameOrChannels.error');
269-
270-
this.start = start;
271-
this.end = end;
272-
this.asyncStart = asyncStart;
273-
this.asyncEnd = asyncEnd;
274-
this.error = error;
275-
} else {
276-
throw new ERR_INVALID_ARG_TYPE('nameOrChannels',
277-
['string', 'object', 'Channel'],
278-
nameOrChannels);
272+
for (const eventName of traceEvents) {
273+
ObjectDefineProperty(this, eventName, {
274+
__proto__: null,
275+
value: tracingChannelFrom(nameOrChannels, eventName),
276+
});
279277
}
280278
}
281279

280+
get hasSubscribers() {
281+
return this.start.hasSubscribers ||
282+
this.end.hasSubscribers ||
283+
this.asyncStart.hasSubscribers ||
284+
this.asyncEnd.hasSubscribers ||
285+
this.error.hasSubscribers;
286+
}
287+
282288
subscribe(handlers) {
283289
for (const name of traceEvents) {
284290
if (!handlers[name]) continue;
@@ -302,6 +308,10 @@ class TracingChannel {
302308
}
303309

304310
traceSync(fn, context = {}, thisArg, ...args) {
311+
if (!this.hasSubscribers) {
312+
return ReflectApply(fn, thisArg, args);
313+
}
314+
305315
const { start, end, error } = this;
306316

307317
return start.runStores(context, () => {
@@ -320,6 +330,10 @@ class TracingChannel {
320330
}
321331

322332
tracePromise(fn, context = {}, thisArg, ...args) {
333+
if (!this.hasSubscribers) {
334+
return ReflectApply(fn, thisArg, args);
335+
}
336+
323337
const { start, end, asyncStart, asyncEnd, error } = this;
324338

325339
function reject(err) {
@@ -358,6 +372,10 @@ class TracingChannel {
358372
}
359373

360374
traceCallback(fn, position = -1, context = {}, thisArg, ...args) {
375+
if (!this.hasSubscribers) {
376+
return ReflectApply(fn, thisArg, args);
377+
}
378+
361379
const { start, end, asyncStart, asyncEnd, error } = this;
362380

363381
function wrappedCallback(err, res) {

test/parallel/test-diagnostics-channel-tracing-channel-args-types.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ assert.throws(() => (channel = dc.tracingChannel(0)), {
2424
code: 'ERR_INVALID_ARG_TYPE',
2525
name: 'TypeError',
2626
message:
27-
/The "nameOrChannels" argument must be of type string or an instance of Channel or Object/,
27+
/The "nameOrChannels" argument must be of type string or an instance of TracingChannel or Object/,
2828
});
2929

3030
// tracingChannel creating without instance of Channel must throw error

test/parallel/test-diagnostics-channel-tracing-channel-async.js

-60
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const dc = require('diagnostics_channel');
5+
6+
const channel = dc.tracingChannel('test');
7+
8+
const handlers = {
9+
start: common.mustNotCall(),
10+
end: common.mustNotCall(),
11+
asyncStart: common.mustNotCall(),
12+
asyncEnd: common.mustNotCall(),
13+
error: common.mustNotCall()
14+
};
15+
16+
// While subscribe occurs _before_ the callback executes,
17+
// no async events should be published.
18+
channel.traceCallback(setImmediate, 0, {}, null, common.mustCall());
19+
channel.subscribe(handlers);

test/parallel/test-diagnostics-channel-tracing-channel-async-error.js test/parallel/test-diagnostics-channel-tracing-channel-callback-error.js

+5-15
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ function check(found) {
1515
}
1616

1717
const handlers = {
18-
start: common.mustCall(check, 2),
19-
end: common.mustCall(check, 2),
20-
asyncStart: common.mustCall(check, 2),
21-
asyncEnd: common.mustCall(check, 2),
18+
start: common.mustCall(check),
19+
end: common.mustCall(check),
20+
asyncStart: common.mustCall(check),
21+
asyncEnd: common.mustCall(check),
2222
error: common.mustCall((found) => {
2323
check(found);
2424
assert.deepStrictEqual(found.error, expectedError);
25-
}, 2)
25+
})
2626
};
2727

2828
channel.subscribe(handlers);
@@ -34,13 +34,3 @@ channel.traceCallback(function(cb, err) {
3434
assert.strictEqual(err, expectedError);
3535
assert.strictEqual(res, undefined);
3636
}), expectedError);
37-
38-
channel.tracePromise(function(value) {
39-
assert.deepStrictEqual(this, thisArg);
40-
return Promise.reject(value);
41-
}, input, thisArg, expectedError).then(
42-
common.mustNotCall(),
43-
common.mustCall((value) => {
44-
assert.deepStrictEqual(value, expectedError);
45-
})
46-
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const dc = require('diagnostics_channel');
5+
const assert = require('assert');
6+
7+
const channel = dc.tracingChannel('test');
8+
9+
const expectedResult = { foo: 'bar' };
10+
const input = { foo: 'bar' };
11+
const thisArg = { baz: 'buz' };
12+
13+
function check(found) {
14+
assert.deepStrictEqual(found, input);
15+
}
16+
17+
function checkAsync(found) {
18+
check(found);
19+
assert.strictEqual(found.error, undefined);
20+
assert.deepStrictEqual(found.result, expectedResult);
21+
}
22+
23+
const handlers = {
24+
start: common.mustCall(check),
25+
end: common.mustCall(check),
26+
asyncStart: common.mustCall(checkAsync),
27+
asyncEnd: common.mustCall(checkAsync),
28+
error: common.mustNotCall()
29+
};
30+
31+
channel.subscribe(handlers);
32+
33+
channel.traceCallback(function(cb, err, res) {
34+
assert.deepStrictEqual(this, thisArg);
35+
setImmediate(cb, err, res);
36+
}, 0, input, thisArg, common.mustCall((err, res) => {
37+
assert.strictEqual(err, null);
38+
assert.deepStrictEqual(res, expectedResult);
39+
}), null, expectedResult);
40+
41+
assert.throws(() => {
42+
channel.traceCallback(common.mustNotCall(), 0, input, thisArg, 1, 2, 3);
43+
}, /"callback" argument must be of type function/);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const dc = require('diagnostics_channel');
5+
6+
const channel = dc.tracingChannel('test');
7+
8+
const handlers = {
9+
start: common.mustNotCall(),
10+
end: common.mustNotCall(),
11+
asyncStart: common.mustNotCall(),
12+
asyncEnd: common.mustNotCall(),
13+
error: common.mustNotCall()
14+
};
15+
16+
// While subscribe occurs _before_ the promise resolves,
17+
// no async events should be published.
18+
channel.tracePromise(() => {
19+
return new Promise(setImmediate);
20+
}, {});
21+
channel.subscribe(handlers);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const dc = require('diagnostics_channel');
5+
const assert = require('assert');
6+
7+
const channel = dc.tracingChannel('test');
8+
9+
const expectedError = new Error('test');
10+
const input = { foo: 'bar' };
11+
const thisArg = { baz: 'buz' };
12+
13+
function check(found) {
14+
assert.deepStrictEqual(found, input);
15+
}
16+
17+
const handlers = {
18+
start: common.mustCall(check),
19+
end: common.mustCall(check),
20+
asyncStart: common.mustCall(check),
21+
asyncEnd: common.mustCall(check),
22+
error: common.mustCall((found) => {
23+
check(found);
24+
assert.deepStrictEqual(found.error, expectedError);
25+
})
26+
};
27+
28+
channel.subscribe(handlers);
29+
30+
channel.tracePromise(function(value) {
31+
assert.deepStrictEqual(this, thisArg);
32+
return Promise.reject(value);
33+
}, input, thisArg, expectedError).then(
34+
common.mustNotCall(),
35+
common.mustCall((value) => {
36+
assert.deepStrictEqual(value, expectedError);
37+
})
38+
);

0 commit comments

Comments
 (0)