Skip to content

Commit

Permalink
chore(ah-scope): remove support for event emitter and promises for now
Browse files Browse the repository at this point in the history
  • Loading branch information
vmarchaud committed Jul 24, 2019
1 parent be47ffe commit 5245950
Show file tree
Hide file tree
Showing 2 changed files with 1 addition and 305 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,6 @@

import { ScopeManager } from '@opentelemetry/scope-base';
import * as asyncHooks from 'async_hooks';
import { EventEmitter } from 'events';

type Func<T> = (...args: unknown[]) => T;

type PatchedEventEmitter = {
/**
* Store a map for each event of all original listener and their "patched"
* version so when the listener is removed by the user, we remove the
* correspoding "patched" function.
*/
__ot_listeners?: { [name: string]: WeakMap<Func<void>, Func<void>> };
} & EventEmitter;

const ADD_LISTENER_METHODS = [
'addListener' as 'addListener',
'on' as 'on',
'once' as 'once',
'prependListener' as 'prependListener',
'prependOnceListener' as 'prependOnceListener',
];

export class AsyncHooksScopeManager implements ScopeManager {
private asyncHook: asyncHooks.AsyncHook;
Expand Down Expand Up @@ -76,12 +56,8 @@ export class AsyncHooksScopeManager implements ScopeManager {
if (!scope) {
scope = this.active();
}
if (target instanceof EventEmitter) {
return this._bindEventEmitter(target, scope);
} else if (typeof target === 'function') {
if (typeof target === 'function') {
return this._bindFunction(target, scope);
} else if (target instanceof Promise) {
return this._bindPromise(target, scope);
}
return target;
}
Expand Down Expand Up @@ -116,128 +92,6 @@ export class AsyncHooksScopeManager implements ScopeManager {
return contextWrapper as any;
}

/**
* By default, EventEmitter call their callback with their scope, which we do
* not want, instead we will bind a specific scope to all callbacks that
* go through it.
* @param target EventEmitter a instance of EventEmitter to patch
* @param scope the scope we want to bind
*/
private _bindEventEmitter<T extends EventEmitter>(
target: T,
scope?: unknown
): T {
const ee = (target as unknown) as PatchedEventEmitter;
if (ee.__ot_listeners !== undefined) return target;
ee.__ot_listeners = {};

// patch methods that add a listener to propagate scope
ADD_LISTENER_METHODS.forEach(methodName => {
ee[methodName] = this._patchEEAddListener(ee, ee[methodName], scope);
});
// patch methods that remove a listener
ee.removeListener = this._patchEERemoveListener(ee, ee.removeListener);
ee.off = this._patchEERemoveListener(ee, ee.off);
// patch method that remove all listeners
ee.removeAllListeners = this._patchEERemoteAllListeners(
ee,
ee.removeAllListeners
);

return target;
}

private _bindPromise<T extends Promise<unknown>>(
target: T,
scope?: unknown
): T {
const scopeManager = this;
const then = target.then;
target.then = function(this: {}) {
const args = new Array(arguments.length);
for (let i = 0; i < args.length; i++) {
args[i] = scopeManager.bind(arguments[i], scope);
}
/**
* Forced to remove compilation error here, the type of Promise
* is impossible(?) to reproduce when modifying it.
*/
// tslint:disable-next-line:ban-ts-ignore
// @ts-ignore
return then.apply(this, args);
};
return target;
}

/**
* Patch methods that remove a given listener so that we match the "patched"
* version of that listener (the one that propagate context).
* @param ee EventEmitter instance
* @param original reference to the patched method
*/
private _patchEERemoveListener(ee: PatchedEventEmitter, original: Function) {
return function(this: {}, event: string, listener: Func<void>) {
if (
ee.__ot_listeners === undefined ||
ee.__ot_listeners[event] === undefined
) {
return original.call(this, event, listener);
}
const events = ee.__ot_listeners[event];
const patchedListener = events.get(listener);
return original.call(this, event, patchedListener || listener);
};
}

/**
* Patch methods that remove all listeners so we remove our
* internal references for a given event.
* @param ee EventEmitter instance
* @param original reference to the patched method
*/
private _patchEERemoteAllListeners(
ee: PatchedEventEmitter,
original: Function
) {
return function(this: {}, event: string) {
if (
ee.__ot_listeners === undefined ||
ee.__ot_listeners[event] === undefined
) {
return original.call(this, event);
}
delete ee.__ot_listeners[event];
return original.call(this, event);
};
}

/**
* Patch methods on an event emitter instance that can add listeners so we
* can force them to propagate a given context.
* @param ee EventEmitter instance
* @param original reference to the patched method
* @param [scope] scope to propagate when calling listeners
*/
private _patchEEAddListener(
ee: PatchedEventEmitter,
original: Function,
scope?: unknown
) {
const scopeManager = this;
return function(this: {}, event: string, listener: Func<void>) {
if (ee.__ot_listeners === undefined) ee.__ot_listeners = {};
let listeners = ee.__ot_listeners[event];
if (listeners === undefined) {
listeners = new WeakMap();
ee.__ot_listeners[event] = listeners;
}
const patchedListener = scopeManager.bind(listener, scope);
// store a weak reference of the user listener to ours
listeners.set(listener, patchedListener);
return original.call(this, event, patchedListener);
};
}

/**
* Init hook will be called when userland create a async scope, setting the
* scope as the current one if it exist.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import * as assert from 'assert';
import { AsyncHooksScopeManager } from '../../src';
import { EventEmitter } from 'events';

describe('AsyncHooksScopeManager', () => {
let scopeManager: AsyncHooksScopeManager;
Expand Down Expand Up @@ -130,161 +129,4 @@ describe('AsyncHooksScopeManager', () => {
fn();
});
});

describe('.bind(event-emitter)', () => {
it('should return the same target (when enabled)', () => {
const ee = new EventEmitter();
assert.deepStrictEqual(scopeManager.bind(ee), ee);
});

it('should return the same target (when disabled)', () => {
const ee = new EventEmitter();
scopeManager.disable();
assert.deepStrictEqual(scopeManager.bind(ee), ee);
scopeManager.enable();
});

it('should return current scope and removeListener (when enabled)', done => {
const ee = new EventEmitter();
const scope = { a: 2 };
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
assert.deepStrictEqual(scopeManager.active(), scope);
patchedEe.off('test', handler);
assert.strictEqual(patchedEe.listeners('test').length, 0);
return done();
};
patchedEe.on('test', handler);
assert.strictEqual(patchedEe.listeners('test').length, 1);
patchedEe.emit('test');
});

it('should return current scope and removeAllListener (when enabled)', done => {
const ee = new EventEmitter();
const scope = { a: 2 };
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
assert.deepStrictEqual(scopeManager.active(), scope);
patchedEe.removeAllListeners('test');
assert.strictEqual(patchedEe.listeners('test').length, 0);
return done();
};
patchedEe.on('test', handler);
assert.strictEqual(patchedEe.listeners('test').length, 1);
patchedEe.emit('test');
});

/**
* Even if asynchooks is disabled, the scope propagation will
* still works but it might be lost after any async op.
*/
it('should return scope (when disabled)', done => {
scopeManager.disable();
const ee = new EventEmitter();
const scope = { a: 2 };
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
assert.deepStrictEqual(scopeManager.active(), scope);
patchedEe.off('test', handler);
assert.strictEqual(patchedEe.listeners('test').length, 0);
scopeManager.enable();
return done();
};
patchedEe.on('test', handler);
assert.strictEqual(patchedEe.listeners('test').length, 1);
patchedEe.emit('test');
});

it('should not return current scope (when disabled + async op)', done => {
scopeManager.disable();
const ee = new EventEmitter();
const scope = { a: 3 };
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
setImmediate(() => {
assert.deepStrictEqual(scopeManager.active(), null);
patchedEe.removeAllListeners('test');
assert.strictEqual(patchedEe.listeners('test').length, 0);
return done();
});
};
patchedEe.on('test', handler);
assert.strictEqual(patchedEe.listeners('test').length, 1);
patchedEe.emit('test');
});

it('should return current scope (when enabled + async op)', done => {
scopeManager.enable();
const ee = new EventEmitter();
const scope = { a: 3 };
const patchedEe = scopeManager.bind(ee, scope);
const handler = () => {
setImmediate(() => {
assert.deepStrictEqual(scopeManager.active(), scope);
patchedEe.removeAllListeners('test');
assert.strictEqual(patchedEe.listeners('test').length, 0);
return done();
});
};
patchedEe.on('test', handler);
assert.strictEqual(patchedEe.listeners('test').length, 1);
patchedEe.emit('test');
});
});

describe('.bind(promise)', () => {
it('should return the same target (when enabled)', () => {
const promise = Promise.resolve();
assert.deepStrictEqual(scopeManager.bind(promise), promise);
});

it('should return the scope (when enabled)', done => {
const scope = { a: 3 };
const promise = Promise.resolve();
scopeManager.bind(promise, scope);
promise.then(() => {
assert.deepStrictEqual(scopeManager.active(), scope);
return done();
});
});

it('should return the scope (when disabled)', done => {
scopeManager.disable();
const scope = { a: 3 };
const promise = Promise.resolve();
scopeManager.bind(promise, scope);
promise.then(() => {
assert.deepStrictEqual(scopeManager.active(), scope);
scopeManager.enable();
return done();
});
});

it('should not return the scope (when disabled + async op)', done => {
scopeManager.disable();
const scope = { a: 3 };
const promise = Promise.resolve();
scopeManager.bind(promise, scope);
promise.then(() => {
setImmediate(() => {
assert.deepStrictEqual(scopeManager.active(), null);
scopeManager.enable();
return done();
});
});
});

it('should return the scope (when enabled + async op)', done => {
const scope = { a: 3 };
const promise = Promise.resolve();
scopeManager.bind(promise, scope);
promise.then(() => {
setImmediate(() => {
assert.deepStrictEqual(scopeManager.active(), scope);
scopeManager.enable();
return done();
});
});
});
});
});

0 comments on commit 5245950

Please sign in to comment.