Skip to content

ref(core): Don't pass hub to runWithAsyncContext callback #7850

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/core/src/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface AsyncContextStrategy {
/**
* Runs the supplied callback in its own async context.
*/
runWithAsyncContext<T>(callback: (hub: Hub) => T, options: RunWithAsyncContextOptions): T;
runWithAsyncContext<T>(callback: () => T, options: RunWithAsyncContextOptions): T;
}

/**
Expand Down Expand Up @@ -593,15 +593,15 @@ export function setAsyncContextStrategy(strategy: AsyncContextStrategy | undefin
* @param options Options to pass to the async context strategy
* @returns The result of the callback
*/
export function runWithAsyncContext<T>(callback: (hub: Hub) => T, options: RunWithAsyncContextOptions = {}): T {
export function runWithAsyncContext<T>(callback: () => T, options: RunWithAsyncContextOptions = {}): T {
const registry = getMainCarrier();

if (registry.__SENTRY__ && registry.__SENTRY__.acs) {
return registry.__SENTRY__.acs.runWithAsyncContext(callback, options);
}

// if there was no strategy, fallback to just calling the callback
return callback(getCurrentHub());
return callback();
}

/**
Expand Down
11 changes: 9 additions & 2 deletions packages/nextjs/src/server/utils/wrapperUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { captureException, getActiveTransaction, runWithAsyncContext, startTransaction } from '@sentry/core';
import {
captureException,
getActiveTransaction,
getCurrentHub,
runWithAsyncContext,
startTransaction,
} from '@sentry/core';
import type { Transaction } from '@sentry/types';
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils';
import type { IncomingMessage, ServerResponse } from 'http';
Expand Down Expand Up @@ -74,7 +80,8 @@ export function withTracedServerSideDataFetcher<F extends (...args: any[]) => Pr
},
): (...params: Parameters<F>) => Promise<ReturnType<F>> {
return async function (this: unknown, ...args: Parameters<F>): Promise<ReturnType<F>> {
return runWithAsyncContext(async hub => {
return runWithAsyncContext(async () => {
const hub = getCurrentHub();
let requestTransaction: Transaction | undefined = getTransactionFromRequest(req);
let dataFetcherSpan;

Expand Down
5 changes: 3 additions & 2 deletions packages/nextjs/src/server/wrapApiHandlerWithSentry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { hasTracingEnabled, runWithAsyncContext } from '@sentry/core';
import { getCurrentHub, hasTracingEnabled, runWithAsyncContext } from '@sentry/core';
import { captureException, startTransaction } from '@sentry/node';
import type { Transaction } from '@sentry/types';
import {
Expand Down Expand Up @@ -63,7 +63,8 @@ export function withSentry(apiHandler: NextApiHandler, parameterizedRoute?: stri
// eslint-disable-next-line complexity, @typescript-eslint/no-explicit-any
const boundHandler = runWithAsyncContext(
// eslint-disable-next-line complexity
async hub => {
async () => {
const hub = getCurrentHub();
let transaction: Transaction | undefined;
const currentScope = hub.getScope();
const options = hub.getClient()?.getOptions();
Expand Down
12 changes: 10 additions & 2 deletions packages/nextjs/src/server/wrapServerComponentWithSentry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { addTracingExtensions, captureException, runWithAsyncContext, startTransaction } from '@sentry/core';
import {
addTracingExtensions,
captureException,
getCurrentHub,
runWithAsyncContext,
startTransaction,
} from '@sentry/core';
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils';

import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/nextNavigationErrorUtils';
Expand All @@ -20,7 +26,9 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
// hook. 🤯
return new Proxy(appDirComponent, {
apply: (originalFunction, thisArg, args) => {
return runWithAsyncContext(hub => {
return runWithAsyncContext(() => {
const hub = getCurrentHub();

let maybePromiseResult;

const traceparentData = context.sentryTraceHeader
Expand Down
6 changes: 4 additions & 2 deletions packages/nextjs/test/serverSdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ describe('Server init()', () => {
it("initializes both global hub and domain hub when there's an active domain", () => {
const globalHub = getCurrentHub();

runWithAsyncContext(globalHub2 => {
runWithAsyncContext(() => {
const globalHub2 = getCurrentHub();
// If we call runWithAsyncContext before init, it executes the callback in the same context as there is no
// strategy yet
expect(globalHub2).toBe(globalHub);
Expand All @@ -126,7 +127,8 @@ describe('Server init()', () => {

init({});

runWithAsyncContext(domainHub => {
runWithAsyncContext(() => {
const domainHub = getCurrentHub();
// this tag should end up only in the domain hub
domainHub.setTag('dogs', 'areGreat');

Expand Down
6 changes: 3 additions & 3 deletions packages/node/src/async/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ function createNewHub(parent: Hub | undefined): Hub {
return getHubFromCarrier(carrier);
}

function runWithAsyncContext<T>(callback: (hub: Hub) => T, options: RunWithAsyncContextOptions): T {
function runWithAsyncContext<T>(callback: () => T, options: RunWithAsyncContextOptions): T {
const activeDomain = getActiveDomain<domain.Domain & Carrier>();

if (activeDomain && options?.reuseExisting) {
// We're already in a domain, so we don't need to create a new one, just call the callback with the current hub
return callback(getHubFromCarrier(activeDomain));
return callback();
}

const local = domain.create() as domain.Domain & Carrier;
Expand All @@ -41,7 +41,7 @@ function runWithAsyncContext<T>(callback: (hub: Hub) => T, options: RunWithAsync
setHubOnCarrier(local, newHub);

return local.bind(() => {
return callback(newHub);
return callback();
})();
}

Expand Down
6 changes: 3 additions & 3 deletions packages/node/src/async/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ export function setHooksAsyncContextStrategy(): void {
return getHubFromCarrier(carrier);
}

function runWithAsyncContext<T>(callback: (hub: Hub) => T, options: RunWithAsyncContextOptions): T {
function runWithAsyncContext<T>(callback: () => T, options: RunWithAsyncContextOptions): T {
const existingHub = getCurrentHub();

if (existingHub && options?.reuseExisting) {
// We're already in an async context, so we don't need to create a new one
// just call the callback with the current hub
return callback(existingHub);
return callback();
}

const newHub = createNewHub(existingHub);

return asyncStorage.run(newHub, () => {
return callback(newHub);
return callback();
});
}

Expand Down
3 changes: 2 additions & 1 deletion packages/node/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ export function requestHandler(
});
};
}
runWithAsyncContext(currentHub => {
runWithAsyncContext(() => {
const currentHub = getCurrentHub();
currentHub.configureScope(scope => {
scope.setSDKProcessingMetadata({
request: req,
Expand Down
33 changes: 22 additions & 11 deletions packages/node/test/async/domain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ describe('domains', () => {
const globalHub = getCurrentHub();
globalHub.setExtra('a', 'b');

runWithAsyncContext(hub1 => {
runWithAsyncContext(() => {
const hub1 = getCurrentHub();
expect(hub1).toEqual(globalHub);

hub1.setExtra('c', 'd');
expect(hub1).not.toEqual(globalHub);

runWithAsyncContext(hub2 => {
runWithAsyncContext(() => {
const hub2 = getCurrentHub();
expect(hub2).toEqual(hub1);
expect(hub2).not.toEqual(globalHub);

Expand All @@ -53,13 +55,15 @@ describe('domains', () => {
const globalHub = getCurrentHub();
await addRandomExtra(globalHub, 'a');

await runWithAsyncContext(async hub1 => {
await runWithAsyncContext(async () => {
const hub1 = getCurrentHub();
expect(hub1).toEqual(globalHub);

await addRandomExtra(hub1, 'b');
expect(hub1).not.toEqual(globalHub);

await runWithAsyncContext(async hub2 => {
await runWithAsyncContext(async () => {
const hub2 = getCurrentHub();
expect(hub2).toEqual(hub1);
expect(hub2).not.toEqual(globalHub);

Expand All @@ -72,16 +76,19 @@ describe('domains', () => {
test('hub single instance', () => {
setDomainAsyncContextStrategy();

runWithAsyncContext(hub => {
runWithAsyncContext(() => {
const hub = getCurrentHub();
expect(hub).toBe(getCurrentHub());
});
});

test('within a domain not reused', () => {
setDomainAsyncContextStrategy();

runWithAsyncContext(hub1 => {
runWithAsyncContext(hub2 => {
runWithAsyncContext(() => {
const hub1 = getCurrentHub();
runWithAsyncContext(() => {
const hub2 = getCurrentHub();
expect(hub1).not.toBe(hub2);
});
});
Expand All @@ -90,9 +97,11 @@ describe('domains', () => {
test('within a domain reused when requested', () => {
setDomainAsyncContextStrategy();

runWithAsyncContext(hub1 => {
runWithAsyncContext(() => {
const hub1 = getCurrentHub();
runWithAsyncContext(
hub2 => {
() => {
const hub2 = getCurrentHub();
expect(hub1).toBe(hub2);
},
{ reuseExisting: true },
Expand All @@ -106,7 +115,8 @@ describe('domains', () => {
let d1done = false;
let d2done = false;

runWithAsyncContext(hub => {
runWithAsyncContext(() => {
const hub = getCurrentHub();
hub.getStack().push({ client: 'process' } as any);
expect(hub.getStack()[1]).toEqual({ client: 'process' });
// Just in case so we don't have to worry which one finishes first
Expand All @@ -119,7 +129,8 @@ describe('domains', () => {
});
});

runWithAsyncContext(hub => {
runWithAsyncContext(() => {
const hub = getCurrentHub();
hub.getStack().push({ client: 'local' } as any);
expect(hub.getStack()[1]).toEqual({ client: 'local' });
setTimeout(() => {
Expand Down
41 changes: 27 additions & 14 deletions packages/node/test/async/hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ conditionalTest({ min: 12 })('async_hooks', () => {
});

test('without strategy hubs should be equal', () => {
runWithAsyncContext(hub1 => {
runWithAsyncContext(hub2 => {
runWithAsyncContext(() => {
const hub1 = getCurrentHub();
runWithAsyncContext(() => {
const hub2 = getCurrentHub();
expect(hub1).toBe(hub2);
});
});
Expand All @@ -28,13 +30,15 @@ conditionalTest({ min: 12 })('async_hooks', () => {
const globalHub = getCurrentHub();
globalHub.setExtra('a', 'b');

runWithAsyncContext(hub1 => {
runWithAsyncContext(() => {
const hub1 = getCurrentHub();
expect(hub1).toEqual(globalHub);

hub1.setExtra('c', 'd');
expect(hub1).not.toEqual(globalHub);

runWithAsyncContext(hub2 => {
runWithAsyncContext(() => {
const hub2 = getCurrentHub();
expect(hub2).toEqual(hub1);
expect(hub2).not.toEqual(globalHub);

Expand All @@ -59,13 +63,15 @@ conditionalTest({ min: 12 })('async_hooks', () => {
const globalHub = getCurrentHub();
await addRandomExtra(globalHub, 'a');

await runWithAsyncContext(async hub1 => {
await runWithAsyncContext(async () => {
const hub1 = getCurrentHub();
expect(hub1).toEqual(globalHub);

await addRandomExtra(hub1, 'b');
expect(hub1).not.toEqual(globalHub);

await runWithAsyncContext(async hub2 => {
await runWithAsyncContext(async () => {
const hub2 = getCurrentHub();
expect(hub2).toEqual(hub1);
expect(hub2).not.toEqual(globalHub);

Expand All @@ -78,16 +84,19 @@ conditionalTest({ min: 12 })('async_hooks', () => {
test('context single instance', () => {
setHooksAsyncContextStrategy();

runWithAsyncContext(hub => {
expect(hub).toBe(getCurrentHub());
const globalHub = getCurrentHub();
runWithAsyncContext(() => {
expect(globalHub).not.toBe(getCurrentHub());
});
});

test('context within a context not reused', () => {
setHooksAsyncContextStrategy();

runWithAsyncContext(hub1 => {
runWithAsyncContext(hub2 => {
runWithAsyncContext(() => {
const hub1 = getCurrentHub();
runWithAsyncContext(() => {
const hub2 = getCurrentHub();
expect(hub1).not.toBe(hub2);
});
});
Expand All @@ -96,9 +105,11 @@ conditionalTest({ min: 12 })('async_hooks', () => {
test('context within a context reused when requested', () => {
setHooksAsyncContextStrategy();

runWithAsyncContext(hub1 => {
runWithAsyncContext(() => {
const hub1 = getCurrentHub();
runWithAsyncContext(
hub2 => {
() => {
const hub2 = getCurrentHub();
expect(hub1).toBe(hub2);
},
{ reuseExisting: true },
Expand All @@ -112,7 +123,8 @@ conditionalTest({ min: 12 })('async_hooks', () => {
let d1done = false;
let d2done = false;

runWithAsyncContext(hub => {
runWithAsyncContext(() => {
const hub = getCurrentHub();
hub.getStack().push({ client: 'process' } as any);
expect(hub.getStack()[1]).toEqual({ client: 'process' });
// Just in case so we don't have to worry which one finishes first
Expand All @@ -125,7 +137,8 @@ conditionalTest({ min: 12 })('async_hooks', () => {
});
});

runWithAsyncContext(hub => {
runWithAsyncContext(() => {
const hub = getCurrentHub();
hub.getStack().push({ client: 'local' } as any);
expect(hub.getStack()[1]).toEqual({ client: 'local' });
setTimeout(() => {
Expand Down
3 changes: 2 additions & 1 deletion packages/node/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ describe('SentryNode', () => {
setNodeAsyncContextStrategy();
const client = new NodeClient(options);

runWithAsyncContext(hub => {
runWithAsyncContext(() => {
const hub = getCurrentHub();
hub.bindClient(client);
expect(getCurrentHub().getClient()).toBe(client);
hub.captureEvent({ message: 'test domain' });
Expand Down