From 58a0b6153758878681a0984f5a0a6567f360bf5c Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Thu, 7 Mar 2024 17:04:17 -0800 Subject: [PATCH] Add early-exit to TracingChannel --- checks.js | 5 +- dc-polyfill.js | 1 - patch-tracing-channel.js | 90 ++++++++++++------- ...tics-channel-tracing-channel-async.spec.js | 2 +- 4 files changed, 64 insertions(+), 34 deletions(-) diff --git a/checks.js b/checks.js index 247ce37..46a0647 100644 --- a/checks.js +++ b/checks.js @@ -8,8 +8,11 @@ function hasFullSupport() { } module.exports.hasFullSupport = hasFullSupport; +// TracingChannel _did_ exist before this, but we need to replace everything +// anyway to get early-exit support on all the trace methods. function hasTracingChannel() { - return MAJOR >= 20; + return MAJOR >= 22 + || (MAJOR === 21 && MINOR >= 8); } module.exports.hasTracingChannel = hasTracingChannel; diff --git a/dc-polyfill.js b/dc-polyfill.js index 0ce1ebf..ded32ff 100644 --- a/dc-polyfill.js +++ b/dc-polyfill.js @@ -31,4 +31,3 @@ if (checks.hasSyncUnsubscribeBug()) { } module.exports = dc; - diff --git a/patch-tracing-channel.js b/patch-tracing-channel.js index d3f957f..3a1030b 100644 --- a/patch-tracing-channel.js +++ b/patch-tracing-channel.js @@ -1,10 +1,11 @@ const { - ReflectApply, + ArrayPrototypeAt, + ArrayPrototypeSplice, + ObjectDefineProperty, + PromisePrototypeThen, PromiseReject, PromiseResolve, - PromisePrototypeThen, - ArrayPrototypeSplice, - ArrayPrototypeAt, + ReflectApply, } = require('./primordials.js'); const { ERR_INVALID_ARG_TYPE } = require('./errors.js'); @@ -17,40 +18,57 @@ const traceEvents = [ 'error', ]; +function validateFunction(func, name) { + if (typeof func !== 'function') { + throw new ERR_INVALID_ARG_TYPE(name, ['function'], func); + } +} + +function assertChannel(value, name) { + if (!(value instanceof Channel)) { + throw new ERR_INVALID_ARG_TYPE(name, ['Channel'], value); + } +} + module.exports = function (unpatched) { const { channel } = unpatched; const dc = { ...unpatched }; + function tracingChannelFrom(nameOrChannels, name) { + if (typeof nameOrChannels === 'string') { + return channel(`tracing:${nameOrChannels}:${name}`); + } + + if (typeof nameOrChannels === 'object' && nameOrChannels !== null) { + const channel = nameOrChannels[name]; + assertChannel(channel, `nameOrChannels.${name}`); + return channel; + } + + throw new ERR_INVALID_ARG_TYPE('nameOrChannels', + ['string', 'object', 'TracingChannel'], + nameOrChannels); + } + class TracingChannel { constructor(nameOrChannels) { - if (typeof nameOrChannels === 'string') { - this.start = channel(`tracing:${nameOrChannels}:start`); - this.end = channel(`tracing:${nameOrChannels}:end`); - this.asyncStart = channel(`tracing:${nameOrChannels}:asyncStart`); - this.asyncEnd = channel(`tracing:${nameOrChannels}:asyncEnd`); - this.error = channel(`tracing:${nameOrChannels}:error`); - } else if (typeof nameOrChannels === 'object') { - const { start, end, asyncStart, asyncEnd, error } = nameOrChannels; - - // assertChannel(start, 'nameOrChannels.start'); - // assertChannel(end, 'nameOrChannels.end'); - // assertChannel(asyncStart, 'nameOrChannels.asyncStart'); - // assertChannel(asyncEnd, 'nameOrChannels.asyncEnd'); - // assertChannel(error, 'nameOrChannels.error'); - - this.start = start; - this.end = end; - this.asyncStart = asyncStart; - this.asyncEnd = asyncEnd; - this.error = error; - } else { - throw new ERR_INVALID_ARG_TYPE('nameOrChannels', - ['string', 'object', 'Channel'], - nameOrChannels); + for (const eventName of traceEvents) { + ObjectDefineProperty(this, eventName, { + __proto__: null, + value: tracingChannelFrom(nameOrChannels, eventName), + }); } } + get hasSubscribers() { + return this.start.hasSubscribers || + this.end.hasSubscribers || + this.asyncStart.hasSubscribers || + this.asyncEnd.hasSubscribers || + this.error.hasSubscribers; + } + subscribe(handlers) { for (const name of traceEvents) { if (!handlers[name]) continue; @@ -74,6 +92,10 @@ module.exports = function (unpatched) { } traceSync(fn, context = {}, thisArg, ...args) { + if (!this.hasSubscribers) { + return ReflectApply(fn, thisArg, args); + } + const { start, end, error } = this; return start.runStores(context, () => { @@ -92,6 +114,10 @@ module.exports = function (unpatched) { } tracePromise(fn, context = {}, thisArg, ...args) { + if (!this.hasSubscribers) { + return ReflectApply(fn, thisArg, args); + } + const { start, end, asyncStart, asyncEnd, error } = this; function reject(err) { @@ -130,6 +156,10 @@ module.exports = function (unpatched) { } traceCallback(fn, position = -1, context = {}, thisArg, ...args) { + if (!this.hasSubscribers) { + return ReflectApply(fn, thisArg, args); + } + const { start, end, asyncStart, asyncEnd, error } = this; function wrappedCallback(err, res) { @@ -153,9 +183,7 @@ module.exports = function (unpatched) { } const callback = ArrayPrototypeAt(args, position); - if (typeof callback !== 'function') { - throw new ERR_INVALID_ARG_TYPE('callback', ['function'], callback); - } + validateFunction(callback, 'callback'); ArrayPrototypeSplice(args, position, 1, wrappedCallback); return start.runStores(context, () => { diff --git a/test/test-diagnostics-channel-tracing-channel-async.spec.js b/test/test-diagnostics-channel-tracing-channel-async.spec.js index f583bc1..d0c0bdd 100644 --- a/test/test-diagnostics-channel-tracing-channel-async.spec.js +++ b/test/test-diagnostics-channel-tracing-channel-async.spec.js @@ -57,7 +57,7 @@ test('test-diagnostics-channel-tracing-channel-async', (t) => { try { channel.traceCallback(common.mustNotCall(), 0, input, thisArg, 1, 2, 3); } catch (err) { - if (MAJOR >= 20) { + if (MAJOR >= 22 || (MAJOR === 21 && MINOR >= 8)) { // By default, this error message is used for all of v20 // However, patch-sync-unsubscribe-bug causes the error to change to the older version mentioning Array t.ok(/"callback" argument must be of type function/.test(err.message));