Skip to content

diagnostics_channel: add opt-in subscriber suppression via bypassBy and bypassed()#63651

Open
DivyanshuX9 wants to merge 35 commits into
nodejs:mainfrom
DivyanshuX9:diag/suppression-als
Open

diagnostics_channel: add opt-in subscriber suppression via bypassBy and bypassed()#63651
DivyanshuX9 wants to merge 35 commits into
nodejs:mainfrom
DivyanshuX9:diag/suppression-als

Conversation

@DivyanshuX9

@DivyanshuX9 DivyanshuX9 commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds opt-in subscriber suppression to diagnostics_channel so APM
agents can prevent recursive instrumentation when their own internal
code calls into instrumented libraries.

What Changed

lib/diagnostics_channel.js

  • Added suppressed(key, fn, thisArg, ...args) - runs fn with
    the given key active in the current async context
  • subscribe() and bindStore() accept { subscriberId } option
  • Separate lazy storage for bypassed subscribers/stores - normal
    path has zero overhead, ALS is only hit when bypass subscribers exist
  • Lazy AsyncLocalStorage initialization via getSuppressionsStorage()
    • required because diagnostics_channel loads during bootstrap before
      async_hooks is available
  • Unified validateBypassKey() helper for all key validation

test/parallel/test-diagnostics-channel-suppression.js

  • 10 test scenarios covering sync, async, Promise, timer,
    store, 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:

  • Eliminates per-handler overhead on the hot path
  • Gives vendors a standard API
  • Correctly propagates across Promise/timer boundaries via ALS

Backward Compatibility

  • Fully opt-in : existing code unaffected
  • Normal subscribers pay zero extra cost
  • ALS only initialized and queried when bypass subscribers exist

Naming

As per @bengl final name is "Bypass"

Addresses:#63623

DivyanshuX9 and others added 2 commits May 24, 2026 21:33
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>
Copilot AI review requested due to automatic review settings May 29, 2026 21:31
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. diagnostics_channel Issues and PRs related to diagnostics channel needs-ci PRs that need a full CI run. labels May 29, 2026
DivyanshuX9 added a commit to DivyanshuX9/node that referenced this pull request May 29, 2026
Refs: nodejs#63623

Refs: nodejs#63651
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
@DivyanshuX9 DivyanshuX9 force-pushed the diag/suppression-als branch from 5b4110a to 8b122c2 Compare May 29, 2026 21:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@DivyanshuX9

DivyanshuX9 commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

@BridgeAR @bengl @Qard , I am opening this as a implementation
of #63623. Test file is included. Happy to adjust the API shape
or implementation based on your feedback before CI runs.

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>
@DivyanshuX9 DivyanshuX9 force-pushed the diag/suppression-als branch from 8b122c2 to e4aea85 Compare May 29, 2026 22:37
@codecov

codecov Bot commented May 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.95833% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.24%. Comparing base (384d7a4) to head (4cb0a57).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
lib/diagnostics_channel.js 98.95% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #63651      +/-   ##
==========================================
+ Coverage   90.21%   90.24%   +0.02%     
==========================================
  Files         741      741              
  Lines      241608   241755     +147     
  Branches    45534    45557      +23     
==========================================
+ Hits       217972   218177     +205     
+ Misses      15154    15148       -6     
+ Partials     8482     8430      -52     
Files with missing lines Coverage Δ
lib/diagnostics_channel.js 98.50% <98.95%> (+0.33%) ⬆️

... and 36 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Qard Qard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread lib/diagnostics_channel.js Outdated
Comment thread src/node_errors.cc
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
Comment thread lib/diagnostics_channel.js Outdated
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
@DivyanshuX9

DivyanshuX9 commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

@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
[] Removed node_errors.cc changes as you suggested
[] Replaced all IIFE wrappers with plain blocks in tests
[] Added common.mustCall() / common.mustNotCall() to all handlers
[] Ran lint and tests locally , both passing

Ready for re-review and any changes if you want, when you have time please review it.

Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
Comment thread test/parallel/test-diagnostics-channel-suppression.js Outdated
@DivyanshuX9 DivyanshuX9 force-pushed the diag/suppression-als branch from 0f6e780 to 8fa6fd8 Compare May 30, 2026 12:27
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
@DivyanshuX9 DivyanshuX9 force-pushed the diag/suppression-als branch from 8fa6fd8 to b8194af Compare May 30, 2026 12:38
@Qard

Qard commented May 30, 2026

Copy link
Copy Markdown
Member

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>
@DivyanshuX9

DivyanshuX9 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

@bengl both items from your review are addressed.

On the TracingChannel side, I deep-dived into this, and the per subscriber bypassId already works in TracingChannel without any additional code in traceSync, tracePromise, or traceCallback because the bypassId gets passed from TracingChannel.subscribe() down through BoundedChannel.subscribe() to its underlying Channel.subscribe() where it’s stored in _bypassSubscribers. All code paths in the 3 trace methods will hit ActiveChannel.publish() which performs the per handler bypassId check including the async continuations in tracePromise and traceCallback. Same for stores via RunStoresScope.

I also added tests 11, 12, and 13 to verify this for traceSync, tracePromise, and traceCallback, all tests 11-13 pass. Tests 12 & 13 specifically provide coverage for async boundaries to ensure bypass context is propagated correctly through the continuation window.

A note of caution: the convenience function subscribe(name, fn) does NOT support options in this PR, and bypassId would only be accessible via the instance API (ch.subscribe(fn, { bypassId })). This is done on purpose as bypass is essentially an advanced APM vendor feature and those vendors are already using the instance API pattern. If the team would like, I would be glad to add options support to the module-level helpers in a subsequent PR.

What could make sense as next steps from here:

-Docs update in doc/api/diagnostics_channel.md covering bypass() and the bypassId option with a real-world APM example.

-Mirroring this in dc-polyfill so Bun and Deno users aren't stuck waiting for runtime support.

Happy to pick up any of those or adjust anything here based on your feedback.

Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
@DivyanshuX9 DivyanshuX9 changed the title diagnostics_channel: add opt-in subscriber suppression via suppressedBy and suppressed() diagnostics_channel: add opt-in subscriber suppression via bypassBy and bypassed() Jun 14, 2026
@DivyanshuX9 DivyanshuX9 requested a review from bengl June 14, 2026 17:21
@DivyanshuX9

DivyanshuX9 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor Author

@bengl @BridgeAR
just following up. Latest push addressed:

  • Addressed the changes what Mr BridgeAR said to do so
  • Renamed suppressed/subscriberId → bypass/bypassId everywhere
  • also some tracing channel tests (test 11 to 13) as per bengl stated it
  • jasnell approved the implementation

Still waiting on your re-review when you have time. Let me know if there's anything specific blocking approval so I can address it.

@rochdev

rochdev commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

@DivyanshuX9 Thanks for the additional details. I was trying to figure out whether bypassBy was used in the subscriber or the publisher, but I now get that it's used in neither and is just to be used anywhere in code not necessarily related to either. With that said, I think it's fine to have it built-in even if it only saves a few lines.

Additional docs are definitely needed though.

@DivyanshuX9

DivyanshuX9 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@BridgeAR @bengl @rochdev
I opened the docs PR: #64250

Covers bypass(), bypassId on subscribe/bindStore/tracingChannel,the APM recursion use case, and multi-vendor coordination.
Both MJS and CJS examples throughout.

Tell if i'm missing somethin' or I need to add anything else

@Flarna Flarna left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Docs are missing

Comment thread lib/diagnostics_channel.js Outdated
Comment thread lib/diagnostics_channel.js Outdated
Comment thread lib/diagnostics_channel.js Outdated
Comment thread lib/diagnostics_channel.js Outdated
Comment thread lib/diagnostics_channel.js Outdated
@DivyanshuX9

Copy link
Copy Markdown
Contributor Author

@Flarna docs are in PR: #64250
since it is going to be a continuation of this PR , after all the changes and reviews i'll update them.

@Flarna

Flarna commented Jul 3, 2026

Copy link
Copy Markdown
Member

I think implementation + tests + docs of a new public API should come in one PR.

@DivyanshuX9

Copy link
Copy Markdown
Contributor Author

@Flarna Well once BridgeAR also mentioned about doc

We definitely also need documentation for the feature.

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>
@DivyanshuX9

Copy link
Copy Markdown
Contributor Author

@Flarna i have moved all the docs here

Comment thread doc/api/diagnostics_channel.md
Comment thread doc/api/diagnostics_channel.md Outdated
Comment thread doc/api/diagnostics_channel.md Outdated
Comment thread doc/api/diagnostics_channel.md Outdated
Comment thread doc/api/diagnostics_channel.md Outdated
Comment thread doc/api/diagnostics_channel.md Outdated
Comment thread doc/api/diagnostics_channel.md Outdated
Comment thread doc/api/diagnostics_channel.md Outdated
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
@DivyanshuX9 DivyanshuX9 force-pushed the diag/suppression-als branch from 2ba3a7a to f7f593e Compare July 8, 2026 13:44
@DivyanshuX9

Copy link
Copy Markdown
Contributor Author

@Flarna , lowkey I have addressed all feedback in latest push:

  1. Added Stability: 1.1 - Active development to bypass()
  2. Removed bypass example from Overview (not a typical usage pattern)
  3. Rewrote key parameter description to clarify no single match required
  4. Rewrote description paragraph to focus on behavior, not internals
  5. Fixed channel name to http.client.request.start throughout
  6. Replaced repeated subscribe/bindStore bypass examples with link to bypass() docs
  7. Removed all the unrelated REPLACEME changes entry from tracingChannel.subscribe

Thank you for the thorough review.

@DivyanshuX9

DivyanshuX9 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Stability: 1.1 - Active development

also @Flarna at 251 line is this correct or should i just remove REPLACE ME with the stability:1.1 right away

@Flarna

Flarna commented Jul 8, 2026

Copy link
Copy Markdown
Member

No, the REPLACEME is changed by the release tooling when a release is made, see https://github.com/nodejs/node/blob/main/doc/contributing/api-documentation.md#writing-documentation

The stability index is just documentation and independent of that.

Comment thread doc/api/diagnostics_channel.md Outdated
Signed-off-by: Divyanshu Sharma <divyanshu88999@gmail.com>
@DivyanshuX9 DivyanshuX9 force-pushed the diag/suppression-als branch from 2fccc37 to 58785bb Compare July 13, 2026 13:31
@DivyanshuX9

Copy link
Copy Markdown
Contributor Author

@BridgeAR following up on your performance concern, ran a microbenchmark on the normal-subscriber hot path (1,000,000 iterations, no bypass() active):

main branch delta
normal-only (no bypass sub) 5.77ms 7.97ms +2.2ms (+38%)
normal + bypass sub (bypass idle) 14.9ms 19.6ms +4.7ms (+32%)

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.
Test 2 is when an APM tool has opted in but bypass() isn't currently active, getBypassStorage().getStore() runs on every publish anyway, that's where the bigger cost comes from (~4.7ns/call over baseline).
Before I touch anything wanted to ask, is the 2.2ns on the pure normal path within acceptable range for this feature, or do you want me to look at avoiding the hidden-class shape change? For test 2 I could try caching the getStore() result per microtask turn but that adds complexity, not sure if its worth it.
Happy to dig into either if you think it matters.

image image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. diagnostics_channel Issues and PRs related to diagnostics channel needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants