From 0742247658b083a3383daf15276dcf47213d827c Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 6 Apr 2026 23:58:43 +0200 Subject: [PATCH] feat: Include global scope for `eventLoopBlockIntegration` --- .../suites/thread-blocked-native/isolated.mjs | 2 ++ .../suites/thread-blocked-native/test.ts | 2 +- packages/node-native/src/common.ts | 3 ++- .../src/event-loop-block-integration.ts | 22 +++++++++++++++++-- .../src/event-loop-block-watchdog.ts | 15 +++++++------ 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/thread-blocked-native/isolated.mjs b/dev-packages/node-integration-tests/suites/thread-blocked-native/isolated.mjs index c2c0f39fc44e..992a07c083da 100644 --- a/dev-packages/node-integration-tests/suites/thread-blocked-native/isolated.mjs +++ b/dev-packages/node-integration-tests/suites/thread-blocked-native/isolated.mjs @@ -24,6 +24,8 @@ const fns = [ neverResolve, ]; +Sentry.getGlobalScope().setUser({ email: 'something@gmail.com' }); + setTimeout(() => { for (let id = 0; id < 10; id++) { Sentry.withIsolationScope(async () => { diff --git a/dev-packages/node-integration-tests/suites/thread-blocked-native/test.ts b/dev-packages/node-integration-tests/suites/thread-blocked-native/test.ts index 75f957f07af5..8dd49d126b67 100644 --- a/dev-packages/node-integration-tests/suites/thread-blocked-native/test.ts +++ b/dev-packages/node-integration-tests/suites/thread-blocked-native/test.ts @@ -249,7 +249,7 @@ describe('Thread Blocked Native', { timeout: 30_000 }, () => { message: 'Starting task 5', }, ], - user: { id: 5 }, + user: { id: 5, email: 'something@gmail.com' }, threads: { values: [ { diff --git a/packages/node-native/src/common.ts b/packages/node-native/src/common.ts index 2a96050dbc34..ba61aee7343c 100644 --- a/packages/node-native/src/common.ts +++ b/packages/node-native/src/common.ts @@ -1,4 +1,4 @@ -import type { Contexts, DsnComponents, Primitive, SdkMetadata, Session } from '@sentry/core'; +import type { Contexts, DsnComponents, Primitive, ScopeData, SdkMetadata, Session } from '@sentry/core'; export const POLL_RATIO = 2; @@ -40,5 +40,6 @@ export interface WorkerStartData extends ThreadBlockedIntegrationOptions { export interface ThreadState { session: Session | undefined; + scope: ScopeData; debugImages: Record; } diff --git a/packages/node-native/src/event-loop-block-integration.ts b/packages/node-native/src/event-loop-block-integration.ts index 7b5c4bc43430..1262eb5fb41d 100644 --- a/packages/node-native/src/event-loop-block-integration.ts +++ b/packages/node-native/src/event-loop-block-integration.ts @@ -8,8 +8,18 @@ import type { EventHint, Integration, IntegrationFn, + ScopeData, +} from '@sentry/core'; +import { + debug, + defineIntegration, + getClient, + getCurrentScope, + getFilenameToDebugIdMap, + getGlobalScope, + getIsolationScope, + mergeScopeData, } from '@sentry/core'; -import { debug, defineIntegration, getClient, getFilenameToDebugIdMap, getIsolationScope } from '@sentry/core'; import type { NodeClient } from '@sentry/node'; import { registerThread, threadPoll } from '@sentry-internal/node-native-stacktrace'; import type { ThreadBlockedIntegrationOptions, WorkerStartData } from './common'; @@ -37,6 +47,13 @@ async function getContexts(client: NodeClient): Promise { return event?.contexts || {}; } +function getLocalScopeData(): ScopeData { + const globalScope = getGlobalScope().getScopeData(); + const currentScope = getCurrentScope().getScopeData(); + mergeScopeData(globalScope, currentScope); + return globalScope; +} + type IntegrationInternal = { start: () => void; stop: () => void }; function poll(enabled: boolean, clientOptions: ClientOptions): void { @@ -45,8 +62,9 @@ function poll(enabled: boolean, clientOptions: ClientOptions): void { // We need to copy the session object and remove the toJSON method so it can be sent to the worker // serialized without making it a SerializedSession const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined; + const scope = getLocalScopeData(); // message the worker to tell it the main event loop is still running - threadPoll(enabled, { session, debugImages: getFilenameToDebugIdMap(clientOptions.stackParser) }); + threadPoll(enabled, { session, scope, debugImages: getFilenameToDebugIdMap(clientOptions.stackParser) }); } catch { // we ignore all errors } diff --git a/packages/node-native/src/event-loop-block-watchdog.ts b/packages/node-native/src/event-loop-block-watchdog.ts index a4eb696c7a95..cac20909d6a5 100644 --- a/packages/node-native/src/event-loop-block-watchdog.ts +++ b/packages/node-native/src/event-loop-block-watchdog.ts @@ -21,7 +21,6 @@ import type { ThreadState, WorkerStartData } from './common'; import { POLL_RATIO } from './common'; type CurrentScopes = { - scope: Scope; isolationScope: Scope; }; @@ -275,14 +274,16 @@ async function sendBlockEvent(crashedThreadId: string): Promise { ...getExceptionAndThreads(crashedThreadId, threads), }; - const asyncState = threads[crashedThreadId]?.asyncState; - if (asyncState) { - // We need to rehydrate the scopes from the serialized objects so we can call getScopeData() - const scope = Object.assign(new Scope(), asyncState.scope).getScopeData(); - const isolationScope = Object.assign(new Scope(), asyncState.isolationScope).getScopeData(); + const scope = crashedThread.pollState?.scope + ? new Scope().update(crashedThread.pollState.scope).getScopeData() + : new Scope().getScopeData(); + + if (crashedThread?.asyncState?.isolationScope) { + // We need to rehydrate the scope from the serialized object with properties beginning with _user, etc + const isolationScope = Object.assign(new Scope(), crashedThread.asyncState.isolationScope).getScopeData(); mergeScopeData(scope, isolationScope); - applyScopeToEvent(event, scope); } + applyScopeToEvent(event, scope); const allDebugImages: Record = Object.values(threads).reduce((acc, threadState) => { return { ...acc, ...threadState.pollState?.debugImages };