Skip to content

Commit 3556072

Browse files
author
Stephen Belanger
committed
Add early-exit to TracingChannel
1 parent 3a01859 commit 3556072

7 files changed

+142
-34
lines changed

checks.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ function hasFullSupport() {
88
}
99
module.exports.hasFullSupport = hasFullSupport;
1010

11+
// TracingChannel _did_ exist before this, but we need to replace everything
12+
// anyway to get early-exit support on all the trace methods.
1113
function hasTracingChannel() {
12-
return MAJOR >= 20;
14+
return MAJOR >= 22
15+
|| (MAJOR === 21 && MINOR >= 8);
1316
}
1417
module.exports.hasTracingChannel = hasTracingChannel;
1518

dc-polyfill.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,3 @@ if (checks.hasSyncUnsubscribeBug()) {
3131
}
3232

3333
module.exports = dc;
34-

patch-tracing-channel.js

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
const {
2-
ReflectApply,
2+
ArrayPrototypeAt,
3+
ArrayPrototypeSplice,
4+
ObjectDefineProperty,
5+
PromisePrototypeThen,
36
PromiseReject,
47
PromiseResolve,
5-
PromisePrototypeThen,
6-
ArrayPrototypeSplice,
7-
ArrayPrototypeAt,
8+
ReflectApply,
89
} = require('./primordials.js');
910

1011
const { ERR_INVALID_ARG_TYPE } = require('./errors.js');
@@ -17,40 +18,57 @@ const traceEvents = [
1718
'error',
1819
];
1920

21+
function validateFunction(func, name) {
22+
if (typeof func !== 'function') {
23+
throw new ERR_INVALID_ARG_TYPE(name, ['function'], func);
24+
}
25+
}
26+
27+
function assertChannel(value, name) {
28+
if (!(value instanceof Channel)) {
29+
throw new ERR_INVALID_ARG_TYPE(name, ['Channel'], value);
30+
}
31+
}
32+
2033
module.exports = function (unpatched) {
2134
const { channel } = unpatched;
2235

2336
const dc = { ...unpatched };
2437

38+
function tracingChannelFrom(nameOrChannels, name) {
39+
if (typeof nameOrChannels === 'string') {
40+
return channel(`tracing:${nameOrChannels}:${name}`);
41+
}
42+
43+
if (typeof nameOrChannels === 'object' && nameOrChannels !== null) {
44+
const channel = nameOrChannels[name];
45+
assertChannel(channel, `nameOrChannels.${name}`);
46+
return channel;
47+
}
48+
49+
throw new ERR_INVALID_ARG_TYPE('nameOrChannels',
50+
['string', 'object', 'TracingChannel'],
51+
nameOrChannels);
52+
}
53+
2554
class TracingChannel {
2655
constructor(nameOrChannels) {
27-
if (typeof nameOrChannels === 'string') {
28-
this.start = channel(`tracing:${nameOrChannels}:start`);
29-
this.end = channel(`tracing:${nameOrChannels}:end`);
30-
this.asyncStart = channel(`tracing:${nameOrChannels}:asyncStart`);
31-
this.asyncEnd = channel(`tracing:${nameOrChannels}:asyncEnd`);
32-
this.error = channel(`tracing:${nameOrChannels}:error`);
33-
} else if (typeof nameOrChannels === 'object') {
34-
const { start, end, asyncStart, asyncEnd, error } = nameOrChannels;
35-
36-
// assertChannel(start, 'nameOrChannels.start');
37-
// assertChannel(end, 'nameOrChannels.end');
38-
// assertChannel(asyncStart, 'nameOrChannels.asyncStart');
39-
// assertChannel(asyncEnd, 'nameOrChannels.asyncEnd');
40-
// assertChannel(error, 'nameOrChannels.error');
41-
42-
this.start = start;
43-
this.end = end;
44-
this.asyncStart = asyncStart;
45-
this.asyncEnd = asyncEnd;
46-
this.error = error;
47-
} else {
48-
throw new ERR_INVALID_ARG_TYPE('nameOrChannels',
49-
['string', 'object', 'Channel'],
50-
nameOrChannels);
56+
for (const eventName of traceEvents) {
57+
ObjectDefineProperty(this, eventName, {
58+
__proto__: null,
59+
value: tracingChannelFrom(nameOrChannels, eventName),
60+
});
5161
}
5262
}
5363

64+
get hasSubscribers() {
65+
return this.start.hasSubscribers ||
66+
this.end.hasSubscribers ||
67+
this.asyncStart.hasSubscribers ||
68+
this.asyncEnd.hasSubscribers ||
69+
this.error.hasSubscribers;
70+
}
71+
5472
subscribe(handlers) {
5573
for (const name of traceEvents) {
5674
if (!handlers[name]) continue;
@@ -74,6 +92,10 @@ module.exports = function (unpatched) {
7492
}
7593

7694
traceSync(fn, context = {}, thisArg, ...args) {
95+
if (!this.hasSubscribers) {
96+
return ReflectApply(fn, thisArg, args);
97+
}
98+
7799
const { start, end, error } = this;
78100

79101
return start.runStores(context, () => {
@@ -92,6 +114,10 @@ module.exports = function (unpatched) {
92114
}
93115

94116
tracePromise(fn, context = {}, thisArg, ...args) {
117+
if (!this.hasSubscribers) {
118+
return ReflectApply(fn, thisArg, args);
119+
}
120+
95121
const { start, end, asyncStart, asyncEnd, error } = this;
96122

97123
function reject(err) {
@@ -130,6 +156,10 @@ module.exports = function (unpatched) {
130156
}
131157

132158
traceCallback(fn, position = -1, context = {}, thisArg, ...args) {
159+
if (!this.hasSubscribers) {
160+
return ReflectApply(fn, thisArg, args);
161+
}
162+
133163
const { start, end, asyncStart, asyncEnd, error } = this;
134164

135165
function wrappedCallback(err, res) {
@@ -153,9 +183,7 @@ module.exports = function (unpatched) {
153183
}
154184

155185
const callback = ArrayPrototypeAt(args, position);
156-
if (typeof callback !== 'function') {
157-
throw new ERR_INVALID_ARG_TYPE('callback', ['function'], callback);
158-
}
186+
validateFunction(callback, 'callback');
159187
ArrayPrototypeSplice(args, position, 1, wrappedCallback);
160188

161189
return start.runStores(context, () => {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ test('test-diagnostics-channel-tracing-channel-async', (t) => {
5757
try {
5858
channel.traceCallback(common.mustNotCall(), 0, input, thisArg, 1, 2, 3);
5959
} catch (err) {
60-
if (MAJOR >= 20) {
60+
if (MAJOR >= 22 || (MAJOR === 21 && MINOR >= 8)) {
6161
// By default, this error message is used for all of v20
6262
// However, patch-sync-unsubscribe-bug causes the error to change to the older version mentioning Array
6363
t.ok(/"callback" argument must be of type function/.test(err.message));
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
const test = require('tape');
4+
const common = require('./common.js');
5+
const dc = require('../dc-polyfill.js');
6+
7+
test('test-diagnostics-channel-tracing-channel-callback-early-exit', (t) => {
8+
t.plan(1);
9+
const channel = dc.tracingChannel('test');
10+
11+
const handlers = {
12+
start: common.mustNotCall(),
13+
end: common.mustNotCall(),
14+
asyncStart: common.mustNotCall(),
15+
asyncEnd: common.mustNotCall(),
16+
error: common.mustNotCall()
17+
};
18+
19+
// While subscribe occurs _before_ the callback executes,
20+
// no async events should be published.
21+
channel.traceCallback(setImmediate, 0, {}, null, common.mustCall());
22+
channel.subscribe(handlers);
23+
24+
t.ok(true);
25+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
3+
const test = require('tape');
4+
const common = require('./common.js');
5+
const dc = require('../dc-polyfill.js');
6+
7+
test('test-diagnostics-channel-tracing-channel-promise-early-exit', (t) => {
8+
t.plan(1);
9+
const channel = dc.tracingChannel('test');
10+
11+
const handlers = {
12+
start: common.mustNotCall(),
13+
end: common.mustNotCall(),
14+
asyncStart: common.mustNotCall(),
15+
asyncEnd: common.mustNotCall(),
16+
error: common.mustNotCall()
17+
};
18+
19+
// While subscribe occurs _before_ the promise resolves,
20+
// no async events should be published.
21+
channel.tracePromise(() => {
22+
return new Promise(setImmediate);
23+
}, {});
24+
channel.subscribe(handlers);
25+
26+
t.ok(true);
27+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
const test = require('tape');
4+
const common = require('./common.js');
5+
const dc = require('../dc-polyfill.js');
6+
7+
test('test-diagnostics-channel-tracing-channel-sync-early-exit', (t) => {
8+
t.plan(1);
9+
const channel = dc.tracingChannel('test');
10+
11+
const handlers = {
12+
start: common.mustNotCall(),
13+
end: common.mustNotCall(),
14+
asyncStart: common.mustNotCall(),
15+
asyncEnd: common.mustNotCall(),
16+
error: common.mustNotCall()
17+
};
18+
19+
// While subscribe occurs _before_ the sync call ends,
20+
// no end event should be published.
21+
channel.traceSync(() => {
22+
channel.subscribe(handlers);
23+
}, {});
24+
25+
t.ok(true);
26+
});

0 commit comments

Comments
 (0)