Skip to content

Commit

Permalink
test more versions, refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
tlhunter committed Sep 7, 2023
1 parent ba749f7 commit 9939771
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 198 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@ jobs:
- 14.0.0 # no dc, first 14
- 14.16.1 # last without dc
- 14.17.0 # introduces dc
- 14.18.0 # last without ch.unsubscribe return
- 14.19.0 # introduces ch.unsubscribe return
- 14.x # nothing special, has dc, last 14
- 15.0.0 # first/last without dc
- 15.1.0 # introduces dc
- 15.x # nothing special, has dc, last 15
- 16.0.0 # introduces dc
- 16.13.0 # last without ch.unsubscribe return
- 16.14.0 # introduces ch.unsubscribe return
- 16.16.0 # last without dc.subscribe/unsubscribe
- 16.17.0 # introduces dc.subscribe/unsubscribe
- 16.x # nothing special, has dc, latest 16
- 17.0.0 # nothing special, first 17
- 17.0.0 # first 17, last without ch.unsubscribe return
- 17.1.0 # introduces ch.unsubscribe return
- 17.x # nothing special, has dc, last 17
- 18.0.0 # introduces dc
- 18.6.0 # last without dc.subscribe/unsubscribe
Expand Down
26 changes: 18 additions & 8 deletions checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@ function hasFullSupport() {
}
module.exports.hasFullSupport = hasFullSupport;

function providesTracingChannel() {
function hasTracingChannel() {
return hasFullSupport();
}
module.exports.providesTracingChannel = providesTracingChannel;
module.exports.hasTracingChannel = hasTracingChannel;

function providesTopSubscribeUnsubscribe() {
function hasTopSubscribeUnsubscribe() {
return hasFullSupport()
|| (MAJOR === 16 && MINOR >= 17)
|| (MAJOR === 18 && MINOR >= 7);
}
module.exports.providesTopSubscribeUnsubscribe = providesTopSubscribeUnsubscribe;
module.exports.hasTopSubscribeUnsubscribe = hasTopSubscribeUnsubscribe;

function providesDiagnosticsChannel() {
return providesTopSubscribeUnsubscribe()
function hasDiagnosticsChannel() {
return hasTopSubscribeUnsubscribe()
|| (MAJOR >= 16)
|| (MAJOR === 15 && MINOR >= 1)
|| (MAJOR === 14 && MINOR >= 17);
}
module.exports.providesDiagnosticsChannel = providesDiagnosticsChannel;
module.exports.hasDiagnosticsChannel = hasDiagnosticsChannel;

function hasGarbageCollectionBug() {
return providesDiagnosticsChannel()
return hasDiagnosticsChannel()
&& !hasFullSupport();
}
module.exports.hasGarbageCollectionBug = hasGarbageCollectionBug;
Expand All @@ -35,3 +35,13 @@ function hasZeroSubscribersBug() {
return MAJOR === 19 && MINOR === 9;
}
module.exports.hasZeroSubscribersBug = hasZeroSubscribersBug;

// if Channel#unsubscribe() returns a boolean
function hasChUnsubscribeReturn() {
return hasFullSupport()
|| (MAJOR === 14 && MINOR >= 19)
|| (MAJOR === 16 && MINOR >= 14)
|| (MAJOR === 17 && MINOR >= 1)
|| (MAJOR === 18);
}
module.exports.hasChUnsubscribeReturn = hasChUnsubscribeReturn;
196 changes: 13 additions & 183 deletions dc-polyfill.js
Original file line number Diff line number Diff line change
@@ -1,201 +1,31 @@
const ReflectApply = Reflect.apply;
const PromiseReject = Promise.reject;
const PromiseResolve = Promise.resolve;
const PromisePrototypeThen = Promise.prototype.then;
const ArrayPrototypeSplice = Array.prototype.splice;

const { ERR_INVALID_ARG_TYPE } = require('./errors.js');
const checks = require('./checks.js');

if (checks.hasFullSupport()) {
module.exports = require('node:diagnostics_channel');
return
return;
}

// TODO: everything else

let dc;
let channel_registry;
// TODO: global symbol channel registry when building from scratch

if (checks.providesDiagnosticsChannel()) {
dc = require('diagnostics_channel');
} else {
// TODO
channel_registry = Symbol.for('dc-polyfill');
dc = require('./reimplementation.js');
}
const dc = checks.hasDiagnosticsChannel()
? require('diagnostics_channel')
: require('./reimplementation.js');

if (checks.hasZeroSubscribersBug()) {
require('./patch-zero-subscriber-bug.js')(dc);
}

if (!checks.providesTopSubscribeUnsubscribe()) {
dc.subscribe = (channel, cb) => {
return dc.channel(channel).subscribe(cb);
};
dc.unsubscribe = (channel, cb) => {
if (dc.channel(channel).hasSubscribers) {
return dc.channel(channel).unsubscribe(cb);
}
};
if (!checks.hasTopSubscribeUnsubscribe()) {
require('./patch-top-subscribe-unsubscribe.js')(dc);
}

if (!checks.providesTracingChannel()) {
dc.tracingChannel = tracingChannel;
if (!checks.hasChUnsubscribeReturn()) {
require('./patch-channel-unsubscribe-return.js')(dc);
}

module.exports = dc;

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);
}
}

subscribe(handlers) {
for (const name of traceEvents) {
if (!handlers[name]) continue;

if (this[name]) this[name].subscribe(handlers[name]);
}
}

unsubscribe(handlers) {
let done = true;

for (const name of traceEvents) {
if (!handlers[name]) continue;

if (!(this[name] && this[name].unsubscribe(handlers[name]))) {
done = false;
}
}

return done;
}

traceSync(fn, context = {}, thisArg, ...args) {
const { start, end, error } = this;

return start.runStores(context, () => {
try {
const result = ReflectApply(fn, thisArg, args);
context.result = result;
return result;
} catch (err) {
context.error = err;
error.publish(context);
throw err;
} finally {
end.publish(context);
}
});
}

tracePromise(fn, context = {}, thisArg, ...args) {
const { start, end, asyncStart, asyncEnd, error } = this;

function reject(err) {
context.error = err;
error.publish(context);
asyncStart.publish(context);
// TODO: Is there a way to have asyncEnd _after_ the continuation?
asyncEnd.publish(context);
return PromiseReject(err);
}

function resolve(result) {
context.result = result;
asyncStart.publish(context);
// TODO: Is there a way to have asyncEnd _after_ the continuation?
asyncEnd.publish(context);
return result;
}

return start.runStores(context, () => {
try {
let promise = ReflectApply(fn, thisArg, args);
// Convert thenables to native promises
if (!(promise instanceof Promise)) {
promise = PromiseResolve(promise);
}
return PromisePrototypeThen(promise, resolve, reject);
} catch (err) {
context.error = err;
error.publish(context);
throw err;
} finally {
end.publish(context);
}
});
}

traceCallback(fn, position = -1, context = {}, thisArg, ...args) {
const { start, end, asyncStart, asyncEnd, error } = this;

function wrappedCallback(err, res) {
if (err) {
context.error = err;
error.publish(context);
} else {
context.result = res;
}

// Using runStores here enables manual context failure recovery
asyncStart.runStores(context, () => {
try {
if (callback) {
return ReflectApply(callback, this, arguments);
}
} finally {
asyncEnd.publish(context);
}
});
}

const callback = ArrayPrototypeAt(args, position);
if (typeof callback !== 'function') {
throw new ERR_INVALID_ARG_TYPE('callback', ['function'], callback);
}
ArrayPrototypeSplice(args, position, 1, wrappedCallback);

return start.runStores(context, () => {
try {
return ReflectApply(fn, thisArg, args);
} catch (err) {
context.error = err;
error.publish(context);
throw err;
} finally {
end.publish(context);
}
});
}
if (!checks.hasTracingChannel()) {
require('./patch-tracing-channel.js')(dc);
}

function tracingChannel(nameOrChannels) {
return new TracingChannel(nameOrChannels);
}
module.exports = dc;

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "A polyfill for the internal diagnostics_channel module",
"main": "dc-polyfill.js",
"scripts": {
"test": "tape test/*.js"
"test": "node -v && tape test/*.js"
},
"keywords": [
"diagnostics",
Expand Down
4 changes: 4 additions & 0 deletions patch-channel-unsubscribe-return.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @see https://github.com/nodejs/node/pull/40433
module.exports = function (dc) {
// TODO
};
10 changes: 10 additions & 0 deletions patch-top-subscribe-unsubscribe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = function (dc) {
dc.subscribe = (channel, cb) => {
return dc.channel(channel).subscribe(cb);
};
dc.unsubscribe = (channel, cb) => {
if (dc.channel(channel).hasSubscribers) {
return dc.channel(channel).unsubscribe(cb);
}
};
};
Loading

0 comments on commit 9939771

Please sign in to comment.