Skip to content

Commit b3f41f6

Browse files
feat: improve ContextManager performance using AsyncLocalStorage
Use AsyncLocalStorage in ContentManager to improve performance
1 parent 744c871 commit b3f41f6

File tree

1 file changed

+6
-75
lines changed

1 file changed

+6
-75
lines changed

packages/opentelemetry-context-async-hooks/src/AsyncHooksContextManager.ts

+6-75
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { ContextManager, Context } from '@opentelemetry/context-base';
18-
import * as asyncHooks from 'async_hooks';
18+
import { AsyncLocalStorage } from 'async_hooks';
1919
import { EventEmitter } from 'events';
2020

2121
type Func<T> = (...args: unknown[]) => T;
@@ -38,34 +38,21 @@ const ADD_LISTENER_METHODS = [
3838
];
3939

4040
export class AsyncHooksContextManager implements ContextManager {
41-
private _asyncHook: asyncHooks.AsyncHook;
42-
private _contexts: Map<number, Context | undefined> = new Map();
43-
private _stack: Array<Context | undefined> = [];
41+
private _asyncLocalStorage: AsyncLocalStorage<Context>;
4442

4543
constructor() {
46-
this._asyncHook = asyncHooks.createHook({
47-
init: this._init.bind(this),
48-
before: this._before.bind(this),
49-
after: this._after.bind(this),
50-
destroy: this._destroy.bind(this),
51-
promiseResolve: this._destroy.bind(this),
52-
});
44+
this._asyncLocalStorage = new AsyncLocalStorage();
5345
}
5446

5547
active(): Context {
56-
return this._stack[this._stack.length - 1] ?? Context.ROOT_CONTEXT;
48+
return this._asyncLocalStorage.getStore() ?? Context.ROOT_CONTEXT;
5749
}
5850

5951
with<T extends (...args: unknown[]) => ReturnType<T>>(
6052
context: Context,
6153
fn: T
6254
): ReturnType<T> {
63-
this._enterContext(context);
64-
try {
65-
return fn();
66-
} finally {
67-
this._exitContext();
68-
}
55+
return this._asyncLocalStorage.run(context, fn) as ReturnType<T>;
6956
}
7057

7158
bind<T>(target: T, context?: Context): T {
@@ -82,14 +69,11 @@ export class AsyncHooksContextManager implements ContextManager {
8269
}
8370

8471
enable(): this {
85-
this._asyncHook.enable();
8672
return this;
8773
}
8874

8975
disable(): this {
90-
this._asyncHook.disable();
91-
this._contexts.clear();
92-
this._stack = [];
76+
this._asyncLocalStorage.disable();
9377
return this;
9478
}
9579

@@ -217,57 +201,4 @@ export class AsyncHooksContextManager implements ContextManager {
217201
return original.call(this, event, patchedListener);
218202
};
219203
}
220-
221-
/**
222-
* Init hook will be called when userland create a async context, setting the
223-
* context as the current one if it exist.
224-
* @param uid id of the async context
225-
*/
226-
private _init(uid: number) {
227-
const context = this._stack[this._stack.length - 1];
228-
if (context !== undefined) {
229-
this._contexts.set(uid, context);
230-
}
231-
}
232-
233-
/**
234-
* Destroy hook will be called when a given context is no longer used so we can
235-
* remove its attached context.
236-
* @param uid uid of the async context
237-
*/
238-
private _destroy(uid: number) {
239-
this._contexts.delete(uid);
240-
}
241-
242-
/**
243-
* Before hook is called just beforing executing a async context.
244-
* @param uid uid of the async context
245-
*/
246-
private _before(uid: number) {
247-
const context = this._contexts.get(uid);
248-
if (context !== undefined) {
249-
this._enterContext(context);
250-
}
251-
}
252-
253-
/**
254-
* After hook is called just after completing the execution of a async context.
255-
*/
256-
private _after() {
257-
this._exitContext();
258-
}
259-
260-
/**
261-
* Set the given context as active
262-
*/
263-
private _enterContext(context: Context) {
264-
this._stack.push(context);
265-
}
266-
267-
/**
268-
* Remove the context at the root of the stack
269-
*/
270-
private _exitContext() {
271-
this._stack.pop();
272-
}
273204
}

0 commit comments

Comments
 (0)