Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

diagnostics_channel: add tracing channel #44943

Merged
merged 9 commits into from Mar 31, 2023
647 changes: 647 additions & 0 deletions doc/api/diagnostics_channel.md

Large diffs are not rendered by default.

307 changes: 282 additions & 25 deletions lib/diagnostics_channel.js

Large diffs are not rendered by default.

13 changes: 4 additions & 9 deletions src/node_util.cc
Expand Up @@ -263,18 +263,13 @@ void WeakReference::Get(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(weak_ref->target_.Get(isolate));
}

void WeakReference::GetRef(const FunctionCallbackInfo<Value>& args) {
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(
v8::Number::New(isolate, weak_ref->reference_count_));
}

void WeakReference::IncRef(const FunctionCallbackInfo<Value>& args) {
WeakReference* weak_ref = Unwrap<WeakReference>(args.Holder());
weak_ref->reference_count_++;
if (weak_ref->target_.IsEmpty()) return;
if (weak_ref->reference_count_ == 1) weak_ref->target_.ClearWeak();
args.GetReturnValue().Set(
v8::Number::New(args.GetIsolate(), weak_ref->reference_count_));
}

void WeakReference::DecRef(const FunctionCallbackInfo<Value>& args) {
Expand All @@ -283,6 +278,8 @@ void WeakReference::DecRef(const FunctionCallbackInfo<Value>& args) {
weak_ref->reference_count_--;
if (weak_ref->target_.IsEmpty()) return;
if (weak_ref->reference_count_ == 0) weak_ref->target_.SetWeak();
args.GetReturnValue().Set(
v8::Number::New(args.GetIsolate(), weak_ref->reference_count_));
}

static void GuessHandleType(const FunctionCallbackInfo<Value>& args) {
Expand Down Expand Up @@ -362,7 +359,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(ArrayBufferViewHasBuffer);
registry->Register(WeakReference::New);
registry->Register(WeakReference::Get);
registry->Register(WeakReference::GetRef);
registry->Register(WeakReference::IncRef);
registry->Register(WeakReference::DecRef);
registry->Register(GuessHandleType);
Expand Down Expand Up @@ -466,7 +462,6 @@ void Initialize(Local<Object> target,
WeakReference::kInternalFieldCount);
weak_ref->Inherit(BaseObject::GetConstructorTemplate(env));
SetProtoMethod(isolate, weak_ref, "get", WeakReference::Get);
SetProtoMethod(isolate, weak_ref, "getRef", WeakReference::GetRef);
SetProtoMethod(isolate, weak_ref, "incRef", WeakReference::IncRef);
SetProtoMethod(isolate, weak_ref, "decRef", WeakReference::DecRef);
SetConstructorFunction(context, target, "WeakReference", weak_ref);
Expand Down
1 change: 0 additions & 1 deletion src/node_util.h
Expand Up @@ -21,7 +21,6 @@ class WeakReference : public SnapshotableObject {
v8::Local<v8::Object> target);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Get(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetRef(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IncRef(const v8::FunctionCallbackInfo<v8::Value>& args);
static void DecRef(const v8::FunctionCallbackInfo<v8::Value>& args);

Expand Down
108 changes: 108 additions & 0 deletions test/parallel/test-diagnostics-channel-bind-store.js
@@ -0,0 +1,108 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const dc = require('diagnostics_channel');
const { AsyncLocalStorage } = require('async_hooks');

let n = 0;
const thisArg = new Date();
const inputs = [
{ foo: 'bar' },
{ baz: 'buz' },
];

const channel = dc.channel('test');

// Bind a storage directly to published data
const store1 = new AsyncLocalStorage();
channel.bindStore(store1);
let store1bound = true;

// Bind a store with transformation of published data
const store2 = new AsyncLocalStorage();
channel.bindStore(store2, common.mustCall((data) => {
assert.strictEqual(data, inputs[n]);
return { data };
}, 4));

// Regular subscribers should see publishes from runStores calls
channel.subscribe(common.mustCall((data) => {
if (store1bound) {
assert.deepStrictEqual(data, store1.getStore());
}
assert.deepStrictEqual({ data }, store2.getStore());
assert.strictEqual(data, inputs[n]);
}, 4));

// Verify stores are empty before run
assert.strictEqual(store1.getStore(), undefined);
assert.strictEqual(store2.getStore(), undefined);

channel.runStores(inputs[n], common.mustCall(function(a, b) {
// Verify this and argument forwarding
assert.strictEqual(this, thisArg);
assert.strictEqual(a, 1);
assert.strictEqual(b, 2);

// Verify store 1 state matches input
assert.strictEqual(store1.getStore(), inputs[n]);

// Verify store 2 state has expected transformation
assert.deepStrictEqual(store2.getStore(), { data: inputs[n] });

// Should support nested contexts
n++;
channel.runStores(inputs[n], common.mustCall(function() {
// Verify this and argument forwarding
assert.strictEqual(this, undefined);

// Verify store 1 state matches input
assert.strictEqual(store1.getStore(), inputs[n]);

// Verify store 2 state has expected transformation
assert.deepStrictEqual(store2.getStore(), { data: inputs[n] });
}));
n--;

// Verify store 1 state matches input
assert.strictEqual(store1.getStore(), inputs[n]);

// Verify store 2 state has expected transformation
assert.deepStrictEqual(store2.getStore(), { data: inputs[n] });
}), thisArg, 1, 2);

// Verify stores are empty after run
assert.strictEqual(store1.getStore(), undefined);
assert.strictEqual(store2.getStore(), undefined);

// Verify unbinding works
assert.ok(channel.unbindStore(store1));
store1bound = false;

// Verify unbinding a store that is not bound returns false
assert.ok(!channel.unbindStore(store1));

n++;
channel.runStores(inputs[n], common.mustCall(() => {
// Verify after unbinding store 1 will remain undefined
assert.strictEqual(store1.getStore(), undefined);

// Verify still bound store 2 receives expected data
assert.deepStrictEqual(store2.getStore(), { data: inputs[n] });
}));

// Contain transformer errors and emit on next tick
const fail = new Error('fail');
channel.bindStore(store1, () => {
throw fail;
});

let calledRunStores = false;
process.once('uncaughtException', common.mustCall((err) => {
assert.strictEqual(calledRunStores, true);
assert.strictEqual(err, fail);
}));

channel.runStores(inputs[n], common.mustCall());
calledRunStores = true;
@@ -0,0 +1,46 @@
'use strict';

const common = require('../common');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');

const expectedError = new Error('test');
const input = { foo: 'bar' };
const thisArg = { baz: 'buz' };

function check(found) {
assert.deepStrictEqual(found, input);
}

const handlers = {
start: common.mustCall(check, 2),
end: common.mustCall(check, 2),
asyncStart: common.mustCall(check, 2),
asyncEnd: common.mustCall(check, 2),
error: common.mustCall((found) => {
check(found);
assert.deepStrictEqual(found.error, expectedError);
}, 2)
};

channel.subscribe(handlers);

channel.traceCallback(function(cb, err) {
assert.deepStrictEqual(this, thisArg);
setImmediate(cb, err);
}, 0, input, thisArg, common.mustCall((err, res) => {
assert.strictEqual(err, expectedError);
assert.strictEqual(res, undefined);
}), expectedError);

channel.tracePromise(function(value) {
assert.deepStrictEqual(this, thisArg);
return Promise.reject(value);
}, input, thisArg, expectedError).then(
common.mustNotCall(),
common.mustCall((value) => {
assert.deepStrictEqual(value, expectedError);
})
);
60 changes: 60 additions & 0 deletions test/parallel/test-diagnostics-channel-tracing-channel-async.js
@@ -0,0 +1,60 @@
'use strict';

const common = require('../common');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');

const expectedResult = { foo: 'bar' };
const input = { foo: 'bar' };
const thisArg = { baz: 'buz' };

function check(found) {
assert.deepStrictEqual(found, input);
}

const handlers = {
start: common.mustCall(check, 2),
end: common.mustCall(check, 2),
asyncStart: common.mustCall((found) => {
check(found);
assert.strictEqual(found.error, undefined);
assert.deepStrictEqual(found.result, expectedResult);
}, 2),
asyncEnd: common.mustCall((found) => {
check(found);
assert.strictEqual(found.error, undefined);
assert.deepStrictEqual(found.result, expectedResult);
}, 2),
error: common.mustNotCall()
};

channel.subscribe(handlers);

channel.traceCallback(function(cb, err, res) {
assert.deepStrictEqual(this, thisArg);
setImmediate(cb, err, res);
}, 0, input, thisArg, common.mustCall((err, res) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(res, expectedResult);
}), null, expectedResult);

channel.tracePromise(function(value) {
assert.deepStrictEqual(this, thisArg);
return Promise.resolve(value);
}, input, thisArg, expectedResult).then(
common.mustCall((value) => {
assert.deepStrictEqual(value, expectedResult);
}),
common.mustNotCall()
);

let failed = false;
try {
channel.traceCallback(common.mustNotCall(), 0, input, thisArg, 1, 2, 3);
} catch (err) {
assert.ok(/"callback" argument must be of type function/.test(err.message));
failed = true;
}
assert.strictEqual(failed, true);
@@ -0,0 +1,29 @@
'use strict';

const common = require('../common');
const { AsyncLocalStorage } = require('async_hooks');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');
const store = new AsyncLocalStorage();

const firstContext = { foo: 'bar' };
const secondContext = { baz: 'buz' };

channel.start.bindStore(store, common.mustCall(() => {
return firstContext;
}));

channel.asyncStart.bindStore(store, common.mustCall(() => {
return secondContext;
}));

assert.strictEqual(store.getStore(), undefined);
channel.traceCallback(common.mustCall((cb) => {
assert.deepStrictEqual(store.getStore(), firstContext);
setImmediate(cb);
}), 0, {}, null, common.mustCall(() => {
assert.deepStrictEqual(store.getStore(), secondContext);
}));
assert.strictEqual(store.getStore(), undefined);
@@ -0,0 +1,24 @@
'use strict';

const common = require('../common');
const { setTimeout } = require('node:timers/promises');
const { AsyncLocalStorage } = require('async_hooks');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');
const store = new AsyncLocalStorage();

const context = { foo: 'bar' };

channel.start.bindStore(store, common.mustCall(() => {
return context;
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add something similar as in the callback case here:

channel.asyncStart.bindStore(store, common.mustCall(() => {
  return secondContext;
}));

But asserts stay as is to actually verify the runStores is not used in promise case?

Not meant as blocking comment, perfectly fine to do this in a followup or skip at all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll do that as a follow up. Don't want to tempt fate with CI the day before v20 cut-off. 😂


assert.strictEqual(store.getStore(), undefined);
channel.tracePromise(common.mustCall(async () => {
assert.deepStrictEqual(store.getStore(), context);
await setTimeout(1);
assert.deepStrictEqual(store.getStore(), context);
}));
assert.strictEqual(store.getStore(), undefined);
@@ -0,0 +1,21 @@
'use strict';

const common = require('../common');
const { AsyncLocalStorage } = require('async_hooks');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');
const store = new AsyncLocalStorage();

const context = { foo: 'bar' };

channel.start.bindStore(store, common.mustCall(() => {
return context;
}));

assert.strictEqual(store.getStore(), undefined);
channel.traceSync(common.mustCall(() => {
assert.deepStrictEqual(store.getStore(), context);
}));
assert.strictEqual(store.getStore(), undefined);
@@ -0,0 +1,39 @@
'use strict';

const common = require('../common');
const dc = require('diagnostics_channel');
const assert = require('assert');

const channel = dc.tracingChannel('test');

const expectedError = new Error('test');
const input = { foo: 'bar' };
const thisArg = { baz: 'buz' };

function check(found) {
assert.deepStrictEqual(found, input);
}

const handlers = {
start: common.mustCall(check),
end: common.mustCall(check),
asyncStart: common.mustNotCall(),
asyncEnd: common.mustNotCall(),
error: common.mustCall((found) => {
check(found);
assert.deepStrictEqual(found.error, expectedError);
})
};

channel.subscribe(handlers);
try {
channel.traceSync(function(err) {
assert.deepStrictEqual(this, thisArg);
assert.strictEqual(err, expectedError);
throw err;
}, input, thisArg, expectedError);

throw new Error('It should not reach this error');
} catch (error) {
assert.deepStrictEqual(error, expectedError);
}