Skip to content

Conversation

@timfish
Copy link
Collaborator

@timfish timfish commented Sep 24, 2025

This PR retains the original polled state feature so that we still support debug images and session termination on older versions of Node.

For Node.js v24 (and v22 with --experimental-async-context-frame flag) this PR adds support for fetching state from an AsyncLocalStorage instance from the native side. This allows us to fetch the current scopes from the Open Telemetry context object.

With this we will be able to include all scope data and trace context!

import { AsyncLocalStorage } from 'node:async_hooks';
import { Worker } from 'node:worker_threads';
import { registerThread } from '@sentry-internal/node-native-stacktrace';
import { longWork } from './long-work.js';

const asyncLocalStorage = new AsyncLocalStorage();
const SOME_UNIQUE_SYMBOL = Symbol.for('sentry_scopes');

// stateLookup defines the keys required to lookup the context inside the Otel ALS store:
registerThread({ asyncLocalStorage, stateLookup: ['_currentContext', SOME_UNIQUE_SYMBOL] });

function withTraceId(traceId, fn) {
  // This is a decent approximation of how Otel stores context in the ALS store
  return asyncLocalStorage.run({
    _currentContext: new Map([ [SOME_UNIQUE_SYMBOL, { traceId }] ])
  }, fn);
}

const watchdog = new Worker('./test/watchdog.js');

for (let i = 0; i < 10; i++) {
  withTraceId(`trace-${i}`, () => {
    if (i === 5) {
      longWork();
    }
  });
}

Results in the following output:

{
  "0": {
    "frames": [
      {
        "function": "from",
        "filename": "node:buffer",
        "lineno": 304,
        "colno": 28
      },
      {
        "function": "pbkdf2Sync",
        "filename": "node:internal/crypto/pbkdf2",
        "lineno": 79,
        "colno": 17
      },
      {
        "function": "longWork",
        "filename": "/Users/tim/Documents/Repos/node-native-stacktrace/test/long-work.js",
        "lineno": 6,
        "colno": 25
      },
      {
        "function": "?",
        "filename": "file:///Users/tim/Documents/Repos/node-native-stacktrace/test/async-storage.mjs",
        "lineno": 22,
        "colno": 7
      },
    ],
    "asyncState": {
      "traceId": "trace-5"
    }
  }
}

@timfish timfish force-pushed the timfish/feat/thread-state-global-function branch from bd16bcd to b642be1 Compare September 26, 2025 10:32
@timfish timfish marked this pull request as ready for review September 26, 2025 10:56
@timfish timfish force-pushed the timfish/feat/thread-state-global-function branch 2 times, most recently from 80402df to 1ee6ed1 Compare September 28, 2025 17:08
@timfish timfish changed the title feat: Capture thread state by calling global function feat: Capture thread state from AsyncLocalStorage store Sep 28, 2025
@timfish timfish force-pushed the timfish/feat/thread-state-global-function branch 2 times, most recently from e623ef6 to 936d588 Compare October 8, 2025 12:42
@timfish timfish marked this pull request as draft October 8, 2025 13:22
@timfish timfish requested a review from Copilot October 8, 2025 13:25

This comment was marked as outdated.

@timfish timfish force-pushed the timfish/feat/thread-state-global-function branch from 1bd5e78 to 9046e07 Compare October 9, 2025 09:17
@timfish timfish requested a review from Copilot October 9, 2025 10:29

This comment was marked as outdated.

@timfish timfish force-pushed the timfish/feat/thread-state-global-function branch 6 times, most recently from 63a93d1 to 1eccc68 Compare October 9, 2025 12:51
@timfish timfish force-pushed the timfish/feat/thread-state-global-function branch 2 times, most recently from c26eb88 to 0937b72 Compare October 21, 2025 18:07
@timfish timfish force-pushed the timfish/feat/thread-state-global-function branch from 0937b72 to b3c5cd6 Compare October 21, 2025 18:08
@timfish timfish marked this pull request as ready for review October 21, 2025 18:32
@timfish timfish requested a review from AbhiPrasad October 21, 2025 18:33
@AbhiPrasad
Copy link
Member

@sentry review

poll_state};
},
thread_isolate));
std::cref(thread_info.async_store)));
Copy link
Member

Choose a reason for hiding this comment

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

so this is being accessed in a lock, but it's async right? Will the async operation be safe? Should we just copy the reference to thread_info.async_store?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unfortunately the v8 objects are mostly not copyable. Here we are passing the reference which is the potentially risky thing.

The lock is held until all the futures have completed so this reference can never be changed from any of our code. This is however just a reference to an object on the javascript heap. Because we hold it as a v8::Global<v8::Value>, this retains a reference which will stop it getting GC'd.

It could go out of scope as the isolate terminates which I've tried to protect against here:
https://github.com/getsentry/sentry-javascript-node-native-stacktrace/pull/24/files#diff-469028a692a3e0cf7037091c4846a3fbd09baf3f789e51d7b4271b5ef911f64bR188

@timfish
Copy link
Collaborator Author

timfish commented Oct 22, 2025

Before we merge this PR I want to see if the changes are sufficient to add scope capture in the @sentry/node-native package!

@timfish timfish force-pushed the timfish/feat/thread-state-global-function branch from 15aa515 to b98a775 Compare October 22, 2025 23:49
@timfish timfish force-pushed the timfish/feat/thread-state-global-function branch from e49f7a2 to eafa21b Compare October 23, 2025 10:03
@timfish timfish requested a review from AbhiPrasad October 23, 2025 10:57
@timfish
Copy link
Collaborator Author

timfish commented Oct 23, 2025

This has now been tested working with our integration capturing scope!

@timfish timfish enabled auto-merge (squash) October 23, 2025 17:09
@timfish timfish merged commit 4aeeac7 into main Oct 23, 2025
61 checks passed
@timfish timfish deleted the timfish/feat/thread-state-global-function branch October 23, 2025 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants