Skip to content

Commit 8edb24d

Browse files
author
Stephen Belanger
committed
diagnostics_channel: early-exit tracing channel trace methods
1 parent f4af4b1 commit 8edb24d

12 files changed

+251
-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

+43-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,41 @@ function assertChannel(value, name) {
250251
}
251252
}
252253

254+
function channelOrName(nameOrChannels, name) {
255+
if (typeof nameOrChannels === 'string') {
256+
return channel(`tracing:${nameOrChannels}:${name}`)
257+
}
258+
259+
if (typeof nameOrChannels === 'object') {
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+
value: channelOrName(nameOrChannels, eventName)
275+
});
279276
}
280277
}
281278

279+
get hasSubscribers() {
280+
for (const name of traceEvents) {
281+
if (this[name].hasSubscribers) {
282+
return true
283+
}
284+
}
285+
286+
return false
287+
}
288+
282289
subscribe(handlers) {
283290
for (const name of traceEvents) {
284291
if (!handlers[name]) continue;
@@ -302,6 +309,10 @@ class TracingChannel {
302309
}
303310

304311
traceSync(fn, context = {}, thisArg, ...args) {
312+
if (!this.hasSubscribers) {
313+
return ReflectApply(fn, thisArg, args);
314+
}
315+
305316
const { start, end, error } = this;
306317

307318
return start.runStores(context, () => {
@@ -320,6 +331,10 @@ class TracingChannel {
320331
}
321332

322333
tracePromise(fn, context = {}, thisArg, ...args) {
334+
if (!this.hasSubscribers) {
335+
return ReflectApply(fn, thisArg, args);
336+
}
337+
323338
const { start, end, asyncStart, asyncEnd, error } = this;
324339

325340
function reject(err) {
@@ -358,6 +373,10 @@ class TracingChannel {
358373
}
359374

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

363382
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 renamed to 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)