Skip to content

Commit

Permalink
chore: use unique symbols to identify context values
Browse files Browse the repository at this point in the history
  • Loading branch information
dyladan committed Feb 12, 2020
1 parent 2987a59 commit 5ad4383
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 62 deletions.
4 changes: 2 additions & 2 deletions packages/opentelemetry-core/src/context/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import { Span, SpanContext } from '@opentelemetry/api';
import { Context } from '@opentelemetry/scope-base';

const ACTIVE_SPAN_KEY = 'ACTIVE_SPAN';
const EXTRACTED_SPAN_CONTEXT_KEY = 'EXTRACTED_SPAN_CONTEXT';
const ACTIVE_SPAN_KEY = Context.getKey('OpenTelemetry Context Key ACTIVE_SPAN');
const EXTRACTED_SPAN_CONTEXT_KEY = Context.getKey('OpenTelemetry Context Key EXTRACTED_SPAN_CONTEXT');

/**
* Return the active span if one exists
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Context } from '@opentelemetry/scope-base';

describe('AsyncHooksScopeManager', () => {
let scopeManager: AsyncHooksScopeManager;
const key1 = Context.getKey('test key 1');

beforeEach(() => {
scopeManager = new AsyncHooksScopeManager();
Expand Down Expand Up @@ -55,7 +56,7 @@ describe('AsyncHooksScopeManager', () => {
});

it('should run the callback (object as target)', done => {
const test = Context.ROOT_CONTEXT.setValue('a', 1);
const test = Context.ROOT_CONTEXT.setValue(key1, 1);
scopeManager.with(test, () => {
assert.strictEqual(scopeManager.active(), test, 'should have scope');
return done();
Expand All @@ -80,8 +81,8 @@ describe('AsyncHooksScopeManager', () => {
});

it('should finally restore an old scope', done => {
const scope1 = Context.ROOT_CONTEXT.setValue('name', 'scope1');
const scope2 = Context.ROOT_CONTEXT.setValue('name', 'scope2');
const scope1 = Context.ROOT_CONTEXT.setValue(key1, 'scope1');
const scope2 = Context.ROOT_CONTEXT.setValue(key1, 'scope2');
scopeManager.with(scope1, () => {
assert.strictEqual(scopeManager.active(), scope1);
scopeManager.with(scope2, () => {
Expand Down Expand Up @@ -113,7 +114,7 @@ describe('AsyncHooksScopeManager', () => {
});

it('should return current scope (when enabled)', done => {
const scope = Context.ROOT_CONTEXT.setValue('a', 1);
const scope = Context.ROOT_CONTEXT.setValue(key1, 1);
const fn = scopeManager.bind(() => {
assert.strictEqual(scopeManager.active(), scope, 'should have scope');
return done();
Expand All @@ -127,7 +128,7 @@ describe('AsyncHooksScopeManager', () => {
*/
it('should return current scope (when disabled)', done => {
scopeManager.disable();
const scope = Context.ROOT_CONTEXT.setValue('a', 1);
const scope = Context.ROOT_CONTEXT.setValue(key1, 1);
const fn = scopeManager.bind(() => {
assert.strictEqual(scopeManager.active(), scope, 'should have scope');
return done();
Expand All @@ -137,7 +138,7 @@ describe('AsyncHooksScopeManager', () => {

it('should fail to return current scope (when disabled + async op)', done => {
scopeManager.disable();
const scope = Context.ROOT_CONTEXT.setValue('a', 1);
const scope = Context.ROOT_CONTEXT.setValue(key1, 1);
const fn = scopeManager.bind(() => {
setTimeout(() => {
assert.strictEqual(
Expand All @@ -153,7 +154,7 @@ describe('AsyncHooksScopeManager', () => {

it('should return current scope (when re-enabled + async op)', done => {
scopeManager.enable();
const scope = Context.ROOT_CONTEXT.setValue('a', 1);
const scope = Context.ROOT_CONTEXT.setValue(key1, 1);
const fn = scopeManager.bind(() => {
setTimeout(() => {
assert.strictEqual(scopeManager.active(), scope, 'should have scope');
Expand All @@ -179,7 +180,7 @@ describe('AsyncHooksScopeManager', () => {

it('should return current scope and removeListener (when enabled)', done => {
const ee = new EventEmitter();
const scope = Context.ROOT_CONTEXT.setValue('a', 1);
const scope = Context.ROOT_CONTEXT.setValue(key1, 1);
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
assert.deepStrictEqual(scopeManager.active(), scope);
Expand All @@ -194,7 +195,7 @@ describe('AsyncHooksScopeManager', () => {

it('should return current scope and removeAllListener (when enabled)', done => {
const ee = new EventEmitter();
const scope = Context.ROOT_CONTEXT.setValue('a', 1);
const scope = Context.ROOT_CONTEXT.setValue(key1, 1);
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
assert.deepStrictEqual(scopeManager.active(), scope);
Expand All @@ -214,7 +215,7 @@ describe('AsyncHooksScopeManager', () => {
it('should return scope (when disabled)', done => {
scopeManager.disable();
const ee = new EventEmitter();
const scope = Context.ROOT_CONTEXT.setValue('a', 1);
const scope = Context.ROOT_CONTEXT.setValue(key1, 1);
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
assert.deepStrictEqual(scopeManager.active(), scope);
Expand All @@ -231,7 +232,7 @@ describe('AsyncHooksScopeManager', () => {
it('should not return current scope (when disabled + async op)', done => {
scopeManager.disable();
const ee = new EventEmitter();
const scope = Context.ROOT_CONTEXT.setValue('a', 1);
const scope = Context.ROOT_CONTEXT.setValue(key1, 1);
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
setImmediate(() => {
Expand All @@ -249,7 +250,7 @@ describe('AsyncHooksScopeManager', () => {
it('should return current scope (when enabled + async op)', done => {
scopeManager.enable();
const ee = new EventEmitter();
const scope = Context.ROOT_CONTEXT.setValue('a', 1);
const scope = Context.ROOT_CONTEXT.setValue(key1, 1);
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
setImmediate(() => {
Expand Down
25 changes: 14 additions & 11 deletions packages/opentelemetry-scope-base/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,39 @@
* limitations under the License.
*/

/** Map of identifiers to an unknown value used internally to store context */
type Store = { [identifer: string]: unknown };

/**
* Class which stores and manages current context values. All methods which
* update context such as get and delete do not modify an existing context,
* but create a new one with updated values.
*/
export class Context {
private _currentContext: Store;
private _currentContext: Map<symbol, unknown>;

/** The root context is used as the default parent context when there is no active context */
public static readonly ROOT_CONTEXT = new Context();

/** Get a key to uniquely identify a context value */
public static getKey(description: string) {
return Symbol(description);
}

/**
* Construct a new context which inherits values from an optional parent context.
*
* @param parentContext a context from which to inherit values
*/
private constructor(parentContext?: Store) {
this._currentContext = Object.assign(Object.create(null), parentContext);
private constructor(parentContext?: Map<symbol, unknown>) {
this._currentContext = parentContext ? new Map(parentContext) : new Map();
}

/**
* Get a value from the context.
*
* @param key key which identifies a context value
*/
getValue(key: string): unknown {
return this._currentContext[key];
getValue(key: symbol): unknown {
return this._currentContext.get(key);
}

/**
Expand All @@ -53,9 +56,9 @@ export class Context {
* @param key context key for which to set the value
* @param value value to set for the given key
*/
setValue(key: string, value: unknown): Context {
setValue(key: symbol, value: unknown): Context {
const context = new Context(this._currentContext);
context._currentContext[key] = value;
context._currentContext.set(key, value);
return context;
}

Expand All @@ -65,9 +68,9 @@ export class Context {
*
* @param key context key for which to clear a value
*/
deleteValue(key: string): Context {
deleteValue(key: symbol): Context {
const context = new Context(this._currentContext);
delete context._currentContext[key];
context._currentContext.delete(key);
return context;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ describe('NoopScopeManager', () => {
});

it('should run the callback (object as target)', done => {
const test = Context.ROOT_CONTEXT.setValue('a', 1);
const key = Context.getKey('test key 1');
const test = Context.ROOT_CONTEXT.setValue(key, 1);
scopeManager.with(test, () => {
assert.strictEqual(
scopeManager.active(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ let clock: any;

describe('ZoneScopeManager', () => {
let scopeManager: ZoneScopeManager;
const key1 = Context.getKey('test key 1');
const key2 = Context.getKey('test key 2');

beforeEach(() => {
clock = sinon.useFakeTimers();
Expand All @@ -38,7 +40,7 @@ describe('ZoneScopeManager', () => {

describe('.enable()', () => {
it('should work', () => {
const ctx = Context.ROOT_CONTEXT.setValue('a', 1);
const ctx = Context.ROOT_CONTEXT.setValue(key1, 1);
assert.doesNotThrow(() => {
assert(scopeManager.enable() === scopeManager, 'should return this');
scopeManager.with(ctx, () => {
Expand All @@ -50,7 +52,7 @@ describe('ZoneScopeManager', () => {

describe('.disable()', () => {
it('should work', () => {
const ctx = Context.ROOT_CONTEXT.setValue('a', 1);
const ctx = Context.ROOT_CONTEXT.setValue(key1, 1);
assert.doesNotThrow(() => {
assert(scopeManager.disable() === scopeManager, 'should return this');
scopeManager.with(ctx, () => {
Expand All @@ -69,7 +71,7 @@ describe('ZoneScopeManager', () => {
});

it('should run the callback (object as target)', done => {
const test = Context.ROOT_CONTEXT.setValue('a', 1);
const test = Context.ROOT_CONTEXT.setValue(key1, 1);
scopeManager.with(test, () => {
assert.strictEqual(scopeManager.active(), test, 'should have scope');
return done();
Expand All @@ -94,9 +96,9 @@ describe('ZoneScopeManager', () => {
});

it('should finally restore an old scope, including the async task', done => {
const scope1 = Context.ROOT_CONTEXT.setValue('scope', 'scope1');
const scope2 = Context.ROOT_CONTEXT.setValue('scope', 'scope2');
const scope3 = Context.ROOT_CONTEXT.setValue('scope', 'scope3');
const scope1 = Context.ROOT_CONTEXT.setValue(key1, 'scope1');
const scope2 = Context.ROOT_CONTEXT.setValue(key1, 'scope2');
const scope3 = Context.ROOT_CONTEXT.setValue(key1, 'scope3');

scopeManager.with(scope1, () => {
assert.strictEqual(scopeManager.active(), scope1);
Expand All @@ -118,9 +120,9 @@ describe('ZoneScopeManager', () => {
});

it('should finally restore an old scope when scope is an object, including the async task', done => {
const scope1 = Context.ROOT_CONTEXT.setValue('x', 1);
const scope2 = Context.ROOT_CONTEXT.setValue('x', 2);
const scope3 = Context.ROOT_CONTEXT.setValue('x', 3);
const scope1 = Context.ROOT_CONTEXT.setValue(key1, 1);
const scope2 = Context.ROOT_CONTEXT.setValue(key1, 2);
const scope3 = Context.ROOT_CONTEXT.setValue(key1, 3);
scopeManager.with(scope1, () => {
assert.strictEqual(scopeManager.active(), scope1);
scopeManager.with(scope2, () => {
Expand All @@ -140,29 +142,29 @@ describe('ZoneScopeManager', () => {
assert.strictEqual(scopeManager.active(), window);
});
it('should correctly return the scopes for 3 parallel actions', () => {
const rootSpan = Context.ROOT_CONTEXT.setValue('name', 'root');
const rootSpan = Context.ROOT_CONTEXT.setValue(key1, 'root');
scopeManager.with(rootSpan, () => {
assert.ok(
scopeManager.active().getValue('name') === 'root',
scopeManager.active().getValue(key1) === 'root',
'Current span is rootSpan'
);
const concurrentSpan1 = Context.ROOT_CONTEXT.setValue(
'span',
key2,
'concurrentSpan1'
);
const concurrentSpan2 = Context.ROOT_CONTEXT.setValue(
'span',
key2,
'concurrentSpan2'
);
const concurrentSpan3 = Context.ROOT_CONTEXT.setValue(
'span',
key2,
'concurrentSpan3'
);

scopeManager.with(concurrentSpan1, () => {
setTimeout(() => {
assert.ok(
scopeManager.active().getValue('span') === concurrentSpan1,
scopeManager.active().getValue(key2) === concurrentSpan1,
'Current span is concurrentSpan1'
);
}, 10);
Expand All @@ -171,7 +173,7 @@ describe('ZoneScopeManager', () => {
scopeManager.with(concurrentSpan2, () => {
setTimeout(() => {
assert.ok(
scopeManager.active().getValue('span') === concurrentSpan2,
scopeManager.active().getValue(key2) === concurrentSpan2,
'Current span is concurrentSpan2'
);
}, 20);
Expand All @@ -180,7 +182,7 @@ describe('ZoneScopeManager', () => {
scopeManager.with(concurrentSpan3, () => {
setTimeout(() => {
assert.ok(
scopeManager.active().getValue('span') === concurrentSpan3,
scopeManager.active().getValue(key2) === concurrentSpan3,
'Current span is concurrentSpan3'
);
}, 30);
Expand All @@ -199,12 +201,12 @@ describe('ZoneScopeManager', () => {
}

getTitle() {
return (scopeManager.active().getValue('obj') as Obj).title;
return (scopeManager.active().getValue(key1) as Obj).title;
}
}

const obj1 = new Obj('a1');
const ctx = Context.ROOT_CONTEXT.setValue('obj', obj1);
const ctx = Context.ROOT_CONTEXT.setValue(key1, obj1);
obj1.title = 'a2';
const obj2 = new Obj('b1');
const wrapper: any = scopeManager.bind(obj2.getTitle, ctx);
Expand All @@ -230,7 +232,7 @@ describe('ZoneScopeManager', () => {
});

it('should return current scope (when enabled)', done => {
const scope = Context.ROOT_CONTEXT.setValue('ctx', { a: 1 });
const scope = Context.ROOT_CONTEXT.setValue(key1, { a: 1 });
const fn: any = scopeManager.bind(() => {
assert.strictEqual(scopeManager.active(), scope, 'should have scope');
return done();
Expand All @@ -240,7 +242,7 @@ describe('ZoneScopeManager', () => {

it('should return root scope (when disabled)', done => {
scopeManager.disable();
const scope = Context.ROOT_CONTEXT.setValue('ctx', { a: 1 });
const scope = Context.ROOT_CONTEXT.setValue(key1, { a: 1 });
const fn: any = scopeManager.bind(() => {
assert.strictEqual(
scopeManager.active(),
Expand All @@ -253,7 +255,7 @@ describe('ZoneScopeManager', () => {
});

it('should bind the the certain scope to the target "addEventListener" function', done => {
const scope1 = Context.ROOT_CONTEXT.setValue('a', 1);
const scope1 = Context.ROOT_CONTEXT.setValue(key1, 1);
const element = document.createElement('div');

scopeManager.bind(element, scope1);
Expand All @@ -277,7 +279,7 @@ describe('ZoneScopeManager', () => {
});

it('should preserve zone when creating new click event inside zone', done => {
const scope1 = Context.ROOT_CONTEXT.setValue('a', 1);
const scope1 = Context.ROOT_CONTEXT.setValue(key1, 1);
const element = document.createElement('div');

scopeManager.bind(element, scope1);
Expand Down
Loading

0 comments on commit 5ad4383

Please sign in to comment.