Skip to content

Commit

Permalink
feat: add support to forward args in context.with (#1883)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Dyla <[email protected]>
  • Loading branch information
Flarna and dyladan authored Feb 8, 2021
1 parent cacbbdc commit 70a128f
Show file tree
Hide file tree
Showing 14 changed files with 177 additions and 34 deletions.
12 changes: 8 additions & 4 deletions packages/opentelemetry-api/src/api/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,16 @@ export class ContextAPI {
*
* @param context context to be active during function execution
* @param fn function to execute in a context
* @param thisArg optional receiver to be used for calling fn
* @param args optional arguments forwarded to fn
*/
public with<T extends (...args: unknown[]) => ReturnType<T>>(
public with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context,
fn: T
): ReturnType<T> {
return this._getContextManager().with(context, fn);
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
return this._getContextManager().with(context, fn, thisArg, ...args);
}

/**
Expand Down
20 changes: 20 additions & 0 deletions packages/opentelemetry-api/test/api/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ describe('API', () => {
assert.strictEqual(typeof tracer, 'object');
});

describe('Context', () => {
it('with should forward this, arguments and return value', () => {
function fnWithThis(this: string, a: string, b: number): string {
assert.strictEqual(this, 'that');
assert.strictEqual(arguments.length, 2);
assert.strictEqual(a, 'one');
assert.strictEqual(b, 2);
return 'done';
}

const res = context.with(ROOT_CONTEXT, fnWithThis, 'that', 'one', 2);
assert.strictEqual(res, 'done');

assert.strictEqual(
context.with(ROOT_CONTEXT, () => 3.14),
3.14
);
});
});

describe('GlobalTracerProvider', () => {
const spanContext = {
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export abstract class AbstractAsyncHooksContextManager
implements ContextManager {
abstract active(): Context;

abstract with<T extends (...args: unknown[]) => ReturnType<T>>(
abstract with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context,
fn: T
): ReturnType<T>;
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F>;

abstract enable(): this;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ export class AsyncHooksContextManager extends AbstractAsyncHooksContextManager {
return this._stack[this._stack.length - 1] ?? ROOT_CONTEXT;
}

with<T extends (...args: unknown[]) => ReturnType<T>>(
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context,
fn: T
): ReturnType<T> {
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
this._enterContext(context);
try {
return fn();
return fn.call(thisArg!, ...args);
} finally {
this._exitContext();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ export class AsyncLocalStorageContextManager extends AbstractAsyncHooksContextMa
return this._asyncLocalStorage.getStore() ?? ROOT_CONTEXT;
}

with<T extends (...args: unknown[]) => ReturnType<T>>(
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context,
fn: T
): ReturnType<T> {
return this._asyncLocalStorage.run(context, fn) as ReturnType<T>;
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
const cb = thisArg == null ? fn : fn.bind(thisArg);
return this._asyncLocalStorage.run(context, cb as any, ...args);
}

enable(): this {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,30 @@ for (const contextManagerClass of [
return done();
});

it('should forward this, arguments and return value', () => {
function fnWithThis(this: string, a: string, b: number): string {
assert.strictEqual(this, 'that');
assert.strictEqual(arguments.length, 2);
assert.strictEqual(a, 'one');
assert.strictEqual(b, 2);
return 'done';
}

const res = contextManager.with(
ROOT_CONTEXT,
fnWithThis,
'that',
'one',
2
);
assert.strictEqual(res, 'done');

assert.strictEqual(
contextManager.with(ROOT_CONTEXT, () => 3.14),
3.14
);
});

it('should finally restore an old context', done => {
const ctx1 = ROOT_CONTEXT.setValue(key1, 'ctx1');
const ctx2 = ROOT_CONTEXT.setValue(key1, 'ctx2');
Expand Down
10 changes: 6 additions & 4 deletions packages/opentelemetry-context-base/src/NoopContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ export class NoopContextManager implements types.ContextManager {
return ROOT_CONTEXT;
}

with<T extends (...args: unknown[]) => ReturnType<T>>(
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
_context: types.Context,
fn: T
): ReturnType<T> {
return fn();
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
return fn.call(thisArg, ...args);
}

bind<T>(target: T, _context?: types.Context): T {
Expand Down
10 changes: 7 additions & 3 deletions packages/opentelemetry-context-base/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,15 @@ export interface ContextManager {
* Run the fn callback with object set as the current active context
* @param context Any object to set as the current active context
* @param fn A callback to be immediately run within a specific context
* @param thisArg optional receiver to be used for calling fn
* @param args optional arguments forwarded to fn
*/
with<T extends (...args: unknown[]) => ReturnType<T>>(
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context,
fn: T
): ReturnType<T>;
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F>;

/**
* Bind an object as the current context (or a specific one)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ describe('NoopContextManager', () => {
return done();
});
});

it('should forward this, arguments and return value', () => {
function fnWithThis(this: string, a: string, b: number): string {
assert.strictEqual(this, 'that');
assert.strictEqual(arguments.length, 2);
assert.strictEqual(a, 'one');
assert.strictEqual(b, 2);
return 'done';
}

const res = contextManager.with(
ROOT_CONTEXT,
fnWithThis,
'that',
'one',
2
);
assert.strictEqual(res, 'done');

assert.strictEqual(
contextManager.with(ROOT_CONTEXT, () => 3.14),
3.14
);
});
});

describe('.active()', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,19 @@ export class ZoneContextManager implements ContextManager {
* The context will be set as active
* @param context A context (span) to be called with provided callback
* @param fn Callback function
* @param thisArg optional receiver to be used for calling fn
* @param args optional arguments forwarded to fn
*/
with<T extends (...args: unknown[]) => ReturnType<T>>(
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context | null,
fn: () => ReturnType<T>
): ReturnType<T> {
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
const zoneName = this._createZoneName();

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

return newZone.run(fn, context);
return newZone.run(fn, thisArg, args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,30 @@ describe('ZoneContextManager', () => {
return done();
});

it('should forward this, arguments and return value', () => {
function fnWithThis(this: string, a: string, b: number): string {
assert.strictEqual(this, 'that');
assert.strictEqual(arguments.length, 2);
assert.strictEqual(a, 'one');
assert.strictEqual(b, 2);
return 'done';
}

const res = contextManager.with(
ROOT_CONTEXT,
fnWithThis,
'that',
'one',
2
);
assert.strictEqual(res, 'done');

assert.strictEqual(
contextManager.with(ROOT_CONTEXT, () => 3.14),
3.14
);
});

it('should finally restore an old context, including the async task', done => {
const ctx1 = ROOT_CONTEXT.setValue(key1, 'ctx1');
const ctx2 = ROOT_CONTEXT.setValue(key1, 'ctx2');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ export class TestStackContextManager implements ContextManager {
return this._contextStack[this._contextStack.length - 1] ?? ROOT_CONTEXT;
}

with<T extends (...args: unknown[]) => ReturnType<T>>(
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context,
fn: T
): ReturnType<T> {
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
this._contextStack.push(context);
try {
return fn();
return fn.call(thisArg, ...args);
} finally {
this._contextStack.pop();
}
Expand Down
12 changes: 8 additions & 4 deletions packages/opentelemetry-web/src/StackContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,20 @@ export class StackContextManager implements ContextManager {
* The context will be set as active
* @param context
* @param fn Callback function
* @param thisArg optional receiver to be used for calling fn
* @param args optional arguments forwarded to fn
*/
with<T extends (...args: unknown[]) => ReturnType<T>>(
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context | null,
fn: () => ReturnType<T>
): ReturnType<T> {
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
const previousContext = this._currentContext;
this._currentContext = context || ROOT_CONTEXT;

try {
return fn();
return fn.call(thisArg, ...args);
} finally {
this._currentContext = previousContext;
}
Expand Down
24 changes: 24 additions & 0 deletions packages/opentelemetry-web/test/StackContextManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,30 @@ describe('StackContextManager', () => {
});
assert.strictEqual(contextManager.active(), window);
});

it('should forward this, arguments and return value', () => {
function fnWithThis(this: string, a: string, b: number): string {
assert.strictEqual(this, 'that');
assert.strictEqual(arguments.length, 2);
assert.strictEqual(a, 'one');
assert.strictEqual(b, 2);
return 'done';
}

const res = contextManager.with(
ROOT_CONTEXT,
fnWithThis,
'that',
'one',
2
);
assert.strictEqual(res, 'done');

assert.strictEqual(
contextManager.with(ROOT_CONTEXT, () => 3.14),
3.14
);
});
});

describe('.bind(function)', () => {
Expand Down

0 comments on commit 70a128f

Please sign in to comment.