Skip to content

Commit

Permalink
Merge 38c11b8 into 7f8bc41
Browse files Browse the repository at this point in the history
  • Loading branch information
puzpuzpuz committed Aug 18, 2020
2 parents 7f8bc41 + 38c11b8 commit 23c85f0
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 9 deletions.
54 changes: 47 additions & 7 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,54 @@
'use strict'

const isWrappedSymbol = Symbol('cls-rtracer-is-wrapped')
const wrappedSymbol = Symbol('cls-rtracer-wrapped-function')

function wrapEmitterMethod (emitter, method, wrapper) {
if (emitter[method][isWrappedSymbol]) {
return
}

const desc = Object.getOwnPropertyDescriptor(emitter, method)
if (desc && !desc.writable) {
return
}

const original = emitter[method]
const wrapped = wrapper(original, method)
wrapped[isWrappedSymbol] = true
emitter[method] = wrapped

return wrapped
}

const addMethods = [
'on',
'addListener',
'prependListener'
]

const removeMethods = [
'off',
'removeListener'
]

/**
* Monkey patches `.emit()` method of the given emitter, so
* that all event listeners are run in scope of the provided
* async resource.
* Wraps EventEmitter listener registration methods of the
* given emitter, so that all listeners are run in scope of
* the provided async resource.
*/
const wrapEmitter = (emitter, asyncResource) => {
const original = emitter.emit
emitter.emit = function (type, ...args) {
return asyncResource.runInAsyncScope(original, emitter, type, ...args)
function wrapEmitter (emitter, asyncResource) {
for (const method of addMethods) {
wrapEmitterMethod(emitter, method, (original) => function (name, handler) {
handler[wrappedSymbol] = asyncResource.runInAsyncScope.bind(asyncResource, handler, emitter)
return original.call(this, name, handler[wrappedSymbol])
})
}

for (const method of removeMethods) {
wrapEmitterMethod(emitter, method, (original) => function (name, handler) {
return original.call(this, name, handler[wrappedSymbol] || handler)
})
}
}

Expand Down
68 changes: 66 additions & 2 deletions tests/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ const { AsyncResource, executionAsyncId } = require('async_hooks')
const { wrapEmitter } = require('../src/util')

describe('wrapEmitter', () => {
test('binds event listenters with async resource', (done) => {
test('binds event listeners with async resource', (done) => {
const emitter = new EventEmitter()
const asyncResource = new AsyncResource('foobar')
wrapEmitter(emitter, asyncResource)

setTimeout(() => {
emitter.emit('foo')
}, 0)
Expand All @@ -24,4 +23,69 @@ describe('wrapEmitter', () => {
}
})
})

test('does not bind previously registered event listeners', (done) => {
const emitter = new EventEmitter()
const asyncResource = new AsyncResource('foobar')

emitter.on('foo', () => {
try {
expect(executionAsyncId()).not.toEqual(asyncResource.asyncId())
done()
} catch (error) {
done(error)
}
})

wrapEmitter(emitter, asyncResource)
setTimeout(() => {
emitter.emit('foo')
}, 0)
})

test('does not prevent event listeners from being removed', (done) => {
const emitter = new EventEmitter()
const asyncResource = new AsyncResource('foobar')
wrapEmitter(emitter, asyncResource)

const listener = () => {
done(new Error('Boom'))
}
emitter.on('foo', listener)
emitter.off('foo', listener)

emitter.emit('foo')
done()
})

test('wraps only once on multiple invocations', () => {
const emitter = new EventEmitter()
const asyncResource = new AsyncResource('foobar')

const unwrappedMethod = emitter.addListener
wrapEmitter(emitter, asyncResource)
const wrappedMethod1 = emitter.addListener
wrapEmitter(emitter, asyncResource)
const wrappedMethod2 = emitter.addListener

expect(unwrappedMethod).not.toEqual(wrappedMethod1)
expect(wrappedMethod1).toEqual(wrappedMethod2)
})

test('does not wrap when method is not writable', () => {
const emitter = new EventEmitter()
const asyncResource = new AsyncResource('foobar')

const unwrappedMethod = emitter.addListener
delete emitter.addListener
Object.defineProperty(emitter, 'addListener', {
value: unwrappedMethod,
writable: false,
enumerable: true
})

wrapEmitter(emitter, asyncResource)
const wrappedMethod = emitter.addListener
expect(unwrappedMethod).toEqual(wrappedMethod)
})
})

0 comments on commit 23c85f0

Please sign in to comment.