Skip to content

Commit 70a128f

Browse files
Flarnadyladan
andauthored
feat: add support to forward args in context.with (#1883)
Co-authored-by: Daniel Dyla <[email protected]>
1 parent cacbbdc commit 70a128f

File tree

14 files changed

+177
-34
lines changed

14 files changed

+177
-34
lines changed

packages/opentelemetry-api/src/api/context.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,16 @@ export class ContextAPI {
7878
*
7979
* @param context context to be active during function execution
8080
* @param fn function to execute in a context
81+
* @param thisArg optional receiver to be used for calling fn
82+
* @param args optional arguments forwarded to fn
8183
*/
82-
public with<T extends (...args: unknown[]) => ReturnType<T>>(
84+
public with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
8385
context: Context,
84-
fn: T
85-
): ReturnType<T> {
86-
return this._getContextManager().with(context, fn);
86+
fn: F,
87+
thisArg?: ThisParameterType<F>,
88+
...args: A
89+
): ReturnType<F> {
90+
return this._getContextManager().with(context, fn, thisArg, ...args);
8791
}
8892

8993
/**

packages/opentelemetry-api/test/api/api.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,26 @@ describe('API', () => {
4141
assert.strictEqual(typeof tracer, 'object');
4242
});
4343

44+
describe('Context', () => {
45+
it('with should forward this, arguments and return value', () => {
46+
function fnWithThis(this: string, a: string, b: number): string {
47+
assert.strictEqual(this, 'that');
48+
assert.strictEqual(arguments.length, 2);
49+
assert.strictEqual(a, 'one');
50+
assert.strictEqual(b, 2);
51+
return 'done';
52+
}
53+
54+
const res = context.with(ROOT_CONTEXT, fnWithThis, 'that', 'one', 2);
55+
assert.strictEqual(res, 'done');
56+
57+
assert.strictEqual(
58+
context.with(ROOT_CONTEXT, () => 3.14),
59+
3.14
60+
);
61+
});
62+
});
63+
4464
describe('GlobalTracerProvider', () => {
4565
const spanContext = {
4666
traceId: 'd4cda95b652f4a1592b449d5929fda1b',

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ export abstract class AbstractAsyncHooksContextManager
4242
implements ContextManager {
4343
abstract active(): Context;
4444

45-
abstract with<T extends (...args: unknown[]) => ReturnType<T>>(
45+
abstract with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
4646
context: Context,
47-
fn: T
48-
): ReturnType<T>;
47+
fn: F,
48+
thisArg?: ThisParameterType<F>,
49+
...args: A
50+
): ReturnType<F>;
4951

5052
abstract enable(): this;
5153

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ export class AsyncHooksContextManager extends AbstractAsyncHooksContextManager {
3838
return this._stack[this._stack.length - 1] ?? ROOT_CONTEXT;
3939
}
4040

41-
with<T extends (...args: unknown[]) => ReturnType<T>>(
41+
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
4242
context: Context,
43-
fn: T
44-
): ReturnType<T> {
43+
fn: F,
44+
thisArg?: ThisParameterType<F>,
45+
...args: A
46+
): ReturnType<F> {
4547
this._enterContext(context);
4648
try {
47-
return fn();
49+
return fn.call(thisArg!, ...args);
4850
} finally {
4951
this._exitContext();
5052
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ export class AsyncLocalStorageContextManager extends AbstractAsyncHooksContextMa
3030
return this._asyncLocalStorage.getStore() ?? ROOT_CONTEXT;
3131
}
3232

33-
with<T extends (...args: unknown[]) => ReturnType<T>>(
33+
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
3434
context: Context,
35-
fn: T
36-
): ReturnType<T> {
37-
return this._asyncLocalStorage.run(context, fn) as ReturnType<T>;
35+
fn: F,
36+
thisArg?: ThisParameterType<F>,
37+
...args: A
38+
): ReturnType<F> {
39+
const cb = thisArg == null ? fn : fn.bind(thisArg);
40+
return this._asyncLocalStorage.run(context, cb as any, ...args);
3841
}
3942

4043
enable(): this {

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,30 @@ for (const contextManagerClass of [
109109
return done();
110110
});
111111

112+
it('should forward this, arguments and return value', () => {
113+
function fnWithThis(this: string, a: string, b: number): string {
114+
assert.strictEqual(this, 'that');
115+
assert.strictEqual(arguments.length, 2);
116+
assert.strictEqual(a, 'one');
117+
assert.strictEqual(b, 2);
118+
return 'done';
119+
}
120+
121+
const res = contextManager.with(
122+
ROOT_CONTEXT,
123+
fnWithThis,
124+
'that',
125+
'one',
126+
2
127+
);
128+
assert.strictEqual(res, 'done');
129+
130+
assert.strictEqual(
131+
contextManager.with(ROOT_CONTEXT, () => 3.14),
132+
3.14
133+
);
134+
});
135+
112136
it('should finally restore an old context', done => {
113137
const ctx1 = ROOT_CONTEXT.setValue(key1, 'ctx1');
114138
const ctx2 = ROOT_CONTEXT.setValue(key1, 'ctx2');

packages/opentelemetry-context-base/src/NoopContextManager.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ export class NoopContextManager implements types.ContextManager {
2222
return ROOT_CONTEXT;
2323
}
2424

25-
with<T extends (...args: unknown[]) => ReturnType<T>>(
25+
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
2626
_context: types.Context,
27-
fn: T
28-
): ReturnType<T> {
29-
return fn();
27+
fn: F,
28+
thisArg?: ThisParameterType<F>,
29+
...args: A
30+
): ReturnType<F> {
31+
return fn.call(thisArg, ...args);
3032
}
3133

3234
bind<T>(target: T, _context?: types.Context): T {

packages/opentelemetry-context-base/src/types.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,15 @@ export interface ContextManager {
5050
* Run the fn callback with object set as the current active context
5151
* @param context Any object to set as the current active context
5252
* @param fn A callback to be immediately run within a specific context
53+
* @param thisArg optional receiver to be used for calling fn
54+
* @param args optional arguments forwarded to fn
5355
*/
54-
with<T extends (...args: unknown[]) => ReturnType<T>>(
56+
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
5557
context: Context,
56-
fn: T
57-
): ReturnType<T>;
58+
fn: F,
59+
thisArg?: ThisParameterType<F>,
60+
...args: A
61+
): ReturnType<F>;
5862

5963
/**
6064
* Bind an object as the current context (or a specific one)

packages/opentelemetry-context-base/test/NoopContextManager.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,30 @@ describe('NoopContextManager', () => {
7070
return done();
7171
});
7272
});
73+
74+
it('should forward this, arguments and return value', () => {
75+
function fnWithThis(this: string, a: string, b: number): string {
76+
assert.strictEqual(this, 'that');
77+
assert.strictEqual(arguments.length, 2);
78+
assert.strictEqual(a, 'one');
79+
assert.strictEqual(b, 2);
80+
return 'done';
81+
}
82+
83+
const res = contextManager.with(
84+
ROOT_CONTEXT,
85+
fnWithThis,
86+
'that',
87+
'one',
88+
2
89+
);
90+
assert.strictEqual(res, 'done');
91+
92+
assert.strictEqual(
93+
contextManager.with(ROOT_CONTEXT, () => 3.14),
94+
3.14
95+
);
96+
});
7397
});
7498

7599
describe('.active()', () => {

packages/opentelemetry-context-zone-peer-dep/src/ZoneContextManager.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,19 @@ export class ZoneContextManager implements ContextManager {
244244
* The context will be set as active
245245
* @param context A context (span) to be called with provided callback
246246
* @param fn Callback function
247+
* @param thisArg optional receiver to be used for calling fn
248+
* @param args optional arguments forwarded to fn
247249
*/
248-
with<T extends (...args: unknown[]) => ReturnType<T>>(
250+
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
249251
context: Context | null,
250-
fn: () => ReturnType<T>
251-
): ReturnType<T> {
252+
fn: F,
253+
thisArg?: ThisParameterType<F>,
254+
...args: A
255+
): ReturnType<F> {
252256
const zoneName = this._createZoneName();
253257

254258
const newZone = this._createZone(zoneName, context);
255259

256-
return newZone.run(fn, context);
260+
return newZone.run(fn, thisArg, args);
257261
}
258262
}

0 commit comments

Comments
 (0)