diff --git a/packages/opentelemetry-scope-async-hooks/src/AsyncHooksScopeManager.ts b/packages/opentelemetry-scope-async-hooks/src/AsyncHooksScopeManager.ts index b18eb54884e..e6144811c3e 100644 --- a/packages/opentelemetry-scope-async-hooks/src/AsyncHooksScopeManager.ts +++ b/packages/opentelemetry-scope-async-hooks/src/AsyncHooksScopeManager.ts @@ -126,11 +126,13 @@ export class AsyncHooksScopeManager implements ScopeManager { 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); diff --git a/packages/opentelemetry-scope-async-hooks/test/asynchooks/AsyncHooksScopeManager.test.ts b/packages/opentelemetry-scope-async-hooks/test/asynchooks/AsyncHooksScopeManager.test.ts index e74a24b9fe0..4ade6914b51 100644 --- a/packages/opentelemetry-scope-async-hooks/test/asynchooks/AsyncHooksScopeManager.test.ts +++ b/packages/opentelemetry-scope-async-hooks/test/asynchooks/AsyncHooksScopeManager.test.ts @@ -16,10 +16,16 @@ import * as assert from 'assert'; import { AsyncHooksScopeManager } from '../../src'; +import { EventEmitter } from 'events'; describe('AsyncHooksScopeManager', () => { let scopeManager: AsyncHooksScopeManager; + afterEach(() => { + scopeManager.disable(); + scopeManager.enable(); + }); + describe('.enable()', () => { it('should work', () => { assert.doesNotThrow(() => { @@ -60,7 +66,7 @@ describe('AsyncHooksScopeManager', () => { }); }); - describe('.bind()', () => { + describe('.bind(function)', () => { it('should return the same target (when enabled)', () => { const test = { a: 1 }; assert.deepStrictEqual(scopeManager.bind(test), test); @@ -84,7 +90,7 @@ describe('AsyncHooksScopeManager', () => { /** * Even if asynchooks is disabled, the scope propagation will - * still works but it might be lost after any async op. + * still works but it might be lost after any async op. */ it('should return current scope (when disabled)', done => { scopeManager.disable(); @@ -124,4 +130,105 @@ 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'); + }); + }); });