diagnostics_channel: add opt-in subscriber suppression via bypassBy and bypassed() - #63651
diagnostics_channel: add opt-in subscriber suppression via bypassBy and bypassed()#63651DivyanshuX9 wants to merge 44 commits into
Conversation
Defer non-critical warnings to the next event loop iteration when can_call_into_js() returns false. This prevents crashes when V8 emits warnings during REPL preview evaluation or other contexts where JavaScript execution is temporarily forbidden. When a warning is emitted inside DisallowJavascriptExecutionScope, ProcessEmitWarningGeneric cannot be called immediately. Instead, use env->SetImmediate() to queue the warning emission for after the scope exits. This preserves full warning formatting, deprecation codes, and --redirect-warnings routing. Signed-off-by: Divyanshu Sharma <Divyanshu88999@gmail.com>
5b4110a to
8b122c2
Compare
Refs: nodejs#63623 Refs: nodejs#63651 Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Refs: nodejs#63623 Refs: nodejs#63651 Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
8b122c2 to
e4aea85
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #63651 +/- ##
==========================================
+ Coverage 90.14% 90.18% +0.03%
==========================================
Files 746 746
Lines 242763 242912 +149
Branches 45761 45799 +38
==========================================
+ Hits 218848 219073 +225
+ Misses 15402 15331 -71
+ Partials 8513 8508 -5
🚀 New features to boost your workflow:
|
Qard
left a comment
There was a problem hiding this comment.
Tests are missing a lot of necessary common.mustCall(fn) wrappers.
Also, it seems like this was just vibe-coded without reviewing the output before submitting the PR. Please ensure it is in a good state and that the test suite and lint passes before submitting.
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
…update tests to use common.mustCall
|
@Qard , thanks for the detailed review. So far the checklist of what i have fixed in the latest push: [] Removed the impossible ALS try/catch fallback Ready for re-review and any changes if you want, when you have time please review it. |
0f6e780 to
8fa6fd8
Compare
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
8fa6fd8 to
b8194af
Compare
|
The node_errors.cc change still seems to be present. |
…ove SetImmediate deferral) Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
|
I think implementation + tests + docs of a new public API should come in one PR. |
|
@Flarna Well once BridgeAR also mentioned about doc
at that time was not sure how far reviews will go ,also @rochdev wanted some examples rightaway, So made a seperate PR for that to keep this one clean , yes if i get a green signal to land this one will will update and merge that pr doc in this one. |
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
|
@Flarna i have moved all the docs here |
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
2ba3a7a to
f7f593e
Compare
|
@Flarna , lowkey I have addressed all feedback in latest push:
Thank you for the thorough review. |
also @Flarna at 251 line is this correct or should i just remove REPLACE ME with the stability:1.1 right away |
|
No, the The stability index is just documentation and independent of that. |
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
2fccc37 to
58785bb
Compare
|
@BridgeAR following up on your performance concern, ran a microbenchmark on the normal-subscriber hot path (1,000,000 iterations, no bypass() active):
Test 1 is the pure normal path with no bypass subscriber registered at all, the overhead is just the _bypassSubscribers !== null null-check plus the extra fields on the channel object, comes out to around 2.2ns per call.
|
Signed-off-by: DivyanshuX9 <divyanshu88999@gmail.com>
81685dd to
aa285c8
Compare
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Three bad-merge fragments were committed without conflict markers: - unsubscribe: spurious decRef/maybeMarkInactive before return false - bindStore: stale bindStore(store, transform) fragment injected inside the else block of bindStore(store, transform, options), with missing closing braces for the else block and the method - unbindStore: spurious delete/decRef/maybeMarkInactive before return false All three were remnants of the pre-bypass single-path implementation that were not removed during the bypass-feature merge. Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
…s entries Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
The subscriber buffer (dc_binding.subscribers) is replaced with a new
larger TypedArray when native channel storage grows past its initial
capacity. The old code destructured it once at module load:
const { subscribers: subscriberCounts } = dc_binding;
After growth, dc_binding.subscribers points to the new array but
subscriberCounts still holds a reference to the old one. Any
subscribe/unsubscribe/bindStore/unbindStore call after growth would
increment/decrement the stale array, leaving the live array at zero.
Channel::HasSubscribers() reads from the live array and would return
false even with active subscribers, breaking the C++ test
NativeChannelsGrowSubscriberStorage.
Fix: remove the cached destructuring and access dc_binding.subscribers
directly at every call site. The comment above the binding already
stated this requirement; the code just did not follow it.
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
ActiveChannel.publish() accessed this._subscribers with optional chaining (subscribers?.length || 0). This is unnecessary because: - _subscribers is always initialized to [] by markActive() before any ActiveChannel method can be called - maybeMarkInactive() sets _subscribers back to undefined only after switching the prototype back to Channel.prototype, so ActiveChannel methods are never reachable with a nullish _subscribers Use subscribers.length directly. Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (7)
doc/api/diagnostics_channel.md:685
- The
changes:entry forchannel.bindStore()usesversion: REPLACEME. This should be replaced with the actual semver release(s) that will includeoptions.bypassId.
- v18.19.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63651
description: Added `options.bypassId` parameter.
test/parallel/test-diagnostics-channel-bypass.js:182
channel.runStores()does not publish to the channel, so the subscribedhandleris never invoked and these assertions will never run. To test bypassed stores, move the assertions into the function passed torunStores()and remove the unnecessarysubscribe()/unsubscribe().
const handler = common.mustCall(() => {
assert.strictEqual(normalAls.getStore()?.value, 42);
assert.strictEqual(als.getStore(), undefined);
});
ch.subscribe(handler);
test/parallel/test-diagnostics-channel-bypass.js:197
- These type-validation tests only assert
name: 'TypeError'. Most diagnostics_channel tests assert the errorcode(e.g.ERR_INVALID_ARG_TYPE) to avoid passing on unrelated TypeErrors.
assert.throws(() => ch.subscribe(() => {}, { bypassId: bad }), {
name: 'TypeError'
});
lib/diagnostics_channel.js:317
- This implementation splits normal subscribers and
bypassIdsubscribers into separate lists and publishes them in two passes, which changes invocation order: all non-bypass subscribers always run before any bypass-enabled subscriber, regardless of subscription order. If subscriber ordering is expected to follow registration order, consider preserving a single ordered list when any bypass subscribers exist (at the cost of an extra per-subscriber branch in the bypass-enabled case).
// Bypass path, only entered if bypass subscribers exist.
if (this._bypassSubscribers !== null) {
const activeKeys = getBypassStorage().getStore();
const bypassSubscribers = this._bypassSubscribers;
for (let i = 0; i < bypassSubscribers.length; i++) {
lib/diagnostics_channel.js:793
- PR description mentions APIs like
suppressed()/{ subscriberId }, but the code/docs introducebypass()/{ bypassId }. If the final API isbypass, please update the PR description to match so reviewers/users aren't misled.
function bypass(key, fn, thisArg, ...args) {
validateFunction(fn, 'fn');
validateBypassKey(key, 'key');
doc/api/diagnostics_channel.md:238
- The doc YAML front-matter still contains
added: REPLACEME. This placeholder should be replaced with the actual version(s) before landing so the docs site and release tooling remain accurate.
<!-- YAML
added: REPLACEME
-->
doc/api/diagnostics_channel.md:570
- The
changes:entry forchannel.subscribe()usesversion: REPLACEME. This should be replaced with the actual semver release(s) that will includeoptions.bypassId.
This issue also appears on line 681 of the same file.
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63651
description: Added `options.bypassId` parameter.


Summary
Adds opt-in subscriber suppression to
diagnostics_channelso APMagents can prevent recursive instrumentation when their own internal
code calls into instrumented libraries.
What Changed
lib/diagnostics_channel.jssuppressed(key, fn, thisArg, ...args)- runsfnwiththe given key active in the current async context
subscribe()andbindStore()accept{ subscriberId }optionpath has zero overhead, ALS is only hit when bypass subscribers exist
AsyncLocalStorageinitialization viagetSuppressionsStorage()async_hooks is available
validateBypassKey()helper for all key validationtest/parallel/test-diagnostics-channel-suppression.jsstore, nested, and type validation cases
Why
APM agents currently implement suppression themselves using a
custom ALS noop marker - every vendor reimplements the same
pattern differently. Moving it into core:
Backward Compatibility
Naming
As per @bengl final name is "Bypass"
Addresses:#63623