Skip to content

Commit d5ac938

Browse files
authored
ref(core): Rename Hub to AsyncContextStack & remove unneeded methods (#11630)
Should save some bundle size...! I also moved stuff around a bit so we don't have so much somewhat unrelated stuff packed into the former `hub.ts` file - needed some work to avoid circular dependencies, but I think it's fine now. I left the stuff we actually need in, and renamed it for clarity so this is not confusing anymore. closes #11482
1 parent 44bc6cf commit d5ac938

File tree

12 files changed

+362
-724
lines changed

12 files changed

+362
-724
lines changed

packages/core/src/asyncContext.ts

Lines changed: 0 additions & 119 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { Carrier } from './../carrier';
2+
import { getMainCarrier, getSentryCarrier } from './../carrier';
3+
import { getStackAsyncContextStrategy } from './stackStrategy';
4+
import type { AsyncContextStrategy } from './types';
5+
6+
/**
7+
* @private Private API with no semver guarantees!
8+
*
9+
* Sets the global async context strategy
10+
*/
11+
export function setAsyncContextStrategy(strategy: AsyncContextStrategy | undefined): void {
12+
// Get main carrier (global for every environment)
13+
const registry = getMainCarrier();
14+
const sentry = getSentryCarrier(registry);
15+
sentry.acs = strategy;
16+
}
17+
18+
/**
19+
* Get the current async context strategy.
20+
* If none has been setup, the default will be used.
21+
*/
22+
export function getAsyncContextStrategy(carrier: Carrier): AsyncContextStrategy {
23+
const sentry = getSentryCarrier(carrier);
24+
25+
if (sentry.acs) {
26+
return sentry.acs;
27+
}
28+
29+
// Otherwise, use the default one (stack)
30+
return getStackAsyncContextStrategy();
31+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import type { Client, Scope as ScopeInterface } from '@sentry/types';
2+
import { isThenable } from '@sentry/utils';
3+
import { getDefaultCurrentScope, getDefaultIsolationScope } from '../currentScopes';
4+
import { Scope } from '../scope';
5+
6+
import { getMainCarrier, getSentryCarrier } from './../carrier';
7+
import type { AsyncContextStrategy } from './types';
8+
9+
interface Layer {
10+
client?: Client;
11+
scope: ScopeInterface;
12+
}
13+
14+
/**
15+
* This is an object that holds a stack of scopes.
16+
*/
17+
export class AsyncContextStack {
18+
private readonly _stack: Layer[];
19+
private _isolationScope: ScopeInterface;
20+
21+
public constructor(scope?: ScopeInterface, isolationScope?: ScopeInterface) {
22+
let assignedScope;
23+
if (!scope) {
24+
assignedScope = new Scope();
25+
} else {
26+
assignedScope = scope;
27+
}
28+
29+
let assignedIsolationScope;
30+
if (!isolationScope) {
31+
assignedIsolationScope = new Scope();
32+
} else {
33+
assignedIsolationScope = isolationScope;
34+
}
35+
36+
this._stack = [{ scope: assignedScope }];
37+
this._isolationScope = assignedIsolationScope;
38+
}
39+
40+
/**
41+
* Fork a scope for the stack.
42+
*/
43+
public withScope<T>(callback: (scope: ScopeInterface) => T): T {
44+
const scope = this._pushScope();
45+
46+
let maybePromiseResult: T;
47+
try {
48+
maybePromiseResult = callback(scope);
49+
} catch (e) {
50+
this._popScope();
51+
throw e;
52+
}
53+
54+
if (isThenable(maybePromiseResult)) {
55+
// @ts-expect-error - isThenable returns the wrong type
56+
return maybePromiseResult.then(
57+
res => {
58+
this._popScope();
59+
return res;
60+
},
61+
e => {
62+
this._popScope();
63+
throw e;
64+
},
65+
);
66+
}
67+
68+
this._popScope();
69+
return maybePromiseResult;
70+
}
71+
72+
/**
73+
* Get the client of the stack.
74+
*/
75+
public getClient<C extends Client>(): C | undefined {
76+
return this.getStackTop().client as C;
77+
}
78+
79+
/**
80+
* Returns the scope of the top stack.
81+
*/
82+
public getScope(): ScopeInterface {
83+
return this.getStackTop().scope;
84+
}
85+
86+
/**
87+
* Get the isolation scope for the stack.
88+
*/
89+
public getIsolationScope(): ScopeInterface {
90+
return this._isolationScope;
91+
}
92+
93+
/**
94+
* Returns the scope stack for domains or the process.
95+
*/
96+
public getStack(): Layer[] {
97+
return this._stack;
98+
}
99+
100+
/**
101+
* Returns the topmost scope layer in the order domain > local > process.
102+
*/
103+
public getStackTop(): Layer {
104+
return this._stack[this._stack.length - 1];
105+
}
106+
107+
/**
108+
* Push a scope to the stack.
109+
*/
110+
private _pushScope(): ScopeInterface {
111+
// We want to clone the content of prev scope
112+
const scope = this.getScope().clone();
113+
this.getStack().push({
114+
client: this.getClient(),
115+
scope,
116+
});
117+
return scope;
118+
}
119+
120+
/**
121+
* Pop a scope from the stack.
122+
*/
123+
private _popScope(): boolean {
124+
if (this.getStack().length <= 1) return false;
125+
return !!this.getStack().pop();
126+
}
127+
}
128+
129+
/**
130+
* Get the global async context stack.
131+
* This will be removed during the v8 cycle and is only here to make migration easier.
132+
*/
133+
function getAsyncContextStack(): AsyncContextStack {
134+
const registry = getMainCarrier();
135+
const sentry = getSentryCarrier(registry) as { hub?: AsyncContextStack };
136+
137+
// If there's no hub, or its an old API, assign a new one
138+
if (sentry.hub) {
139+
return sentry.hub;
140+
}
141+
142+
sentry.hub = new AsyncContextStack(getDefaultCurrentScope(), getDefaultIsolationScope());
143+
return sentry.hub;
144+
}
145+
146+
function withScope<T>(callback: (scope: ScopeInterface) => T): T {
147+
return getAsyncContextStack().withScope(callback);
148+
}
149+
150+
function withSetScope<T>(scope: ScopeInterface, callback: (scope: ScopeInterface) => T): T {
151+
const hub = getAsyncContextStack() as AsyncContextStack;
152+
return hub.withScope(() => {
153+
hub.getStackTop().scope = scope;
154+
return callback(scope);
155+
});
156+
}
157+
158+
function withIsolationScope<T>(callback: (isolationScope: ScopeInterface) => T): T {
159+
return getAsyncContextStack().withScope(() => {
160+
return callback(getAsyncContextStack().getIsolationScope());
161+
});
162+
}
163+
164+
/**
165+
* Get the stack-based async context strategy.
166+
*/
167+
export function getStackAsyncContextStrategy(): AsyncContextStrategy {
168+
return {
169+
withIsolationScope,
170+
withScope,
171+
withSetScope,
172+
withSetIsolationScope: <T>(_isolationScope: ScopeInterface, callback: (isolationScope: ScopeInterface) => T) => {
173+
return withIsolationScope(callback);
174+
},
175+
getCurrentScope: () => getAsyncContextStack().getScope(),
176+
getIsolationScope: () => getAsyncContextStack().getIsolationScope(),
177+
};
178+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { Scope } from '@sentry/types';
2+
import type {
3+
startInactiveSpan,
4+
startSpan,
5+
startSpanManual,
6+
suppressTracing,
7+
withActiveSpan,
8+
} from './../tracing/trace';
9+
import type { getActiveSpan } from './../utils/spanUtils';
10+
11+
/**
12+
* @private Private API with no semver guarantees!
13+
*
14+
* Strategy used to track async context.
15+
*/
16+
export interface AsyncContextStrategy {
17+
/**
18+
* Fork the isolation scope inside of the provided callback.
19+
*/
20+
withIsolationScope: <T>(callback: (isolationScope: Scope) => T) => T;
21+
22+
/**
23+
* Fork the current scope inside of the provided callback.
24+
*/
25+
withScope: <T>(callback: (isolationScope: Scope) => T) => T;
26+
27+
/**
28+
* Set the provided scope as the current scope inside of the provided callback.
29+
*/
30+
withSetScope: <T>(scope: Scope, callback: (scope: Scope) => T) => T;
31+
32+
/**
33+
* Set the provided isolation as the current isolation scope inside of the provided callback.
34+
*/
35+
withSetIsolationScope: <T>(isolationScope: Scope, callback: (isolationScope: Scope) => T) => T;
36+
37+
/**
38+
* Get the currently active scope.
39+
*/
40+
getCurrentScope: () => Scope;
41+
42+
/**
43+
* Get the currently active isolation scope.
44+
*/
45+
getIsolationScope: () => Scope;
46+
47+
// OPTIONAL: Custom tracing methods
48+
// These are used so that we can provide OTEL-based implementations
49+
50+
/** Start an active span. */
51+
startSpan?: typeof startSpan;
52+
53+
/** Start an inactive span. */
54+
startInactiveSpan?: typeof startInactiveSpan;
55+
56+
/** Start an active manual span. */
57+
startSpanManual?: typeof startSpanManual;
58+
59+
/** Get the currently active span. */
60+
getActiveSpan?: typeof getActiveSpan;
61+
62+
/** Make a span the active span in the context of the callback. */
63+
withActiveSpan?: typeof withActiveSpan;
64+
65+
/** Suppress tracing in the given callback, ensuring no spans are generated inside of it. */
66+
suppressTracing?: typeof suppressTracing;
67+
}

0 commit comments

Comments
 (0)