Skip to content

Commit

Permalink
fix(web-server): detach listeners after running
Browse files Browse the repository at this point in the history
Remove listeners from the global process and the webserver when we're
done. The EmitterWrapper can be used to wrap any EventEmitter and
remembers the listeners that were via the wrappers addListener/on
function. Then the wrapper's removeAllListeners function can be used
to remove the wrapper's listeners, leaving the listeners that were
added directly to the unwrapped emitter attached.
  • Loading branch information
jpommerening committed Mar 10, 2014
1 parent f9dee46 commit 3baa8e1
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 5 deletions.
31 changes: 31 additions & 0 deletions lib/emitter_wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
function EmitterWrapper(emitter) {
this.listeners = {};
this.emitter = emitter;
}

EmitterWrapper.prototype.addListener = EmitterWrapper.prototype.on = function (event, listener) {
this.emitter.addListener(event, listener);

if (!this.listeners.hasOwnProperty(event)) {
this.listeners[event] = [];
}

this.listeners[event].push(listener);

return this;
};

EmitterWrapper.prototype.removeAllListeners = function (event) {
var events = event ? [event] : Object.keys(this.listeners);
var self = this;
events.forEach(function (event) {
self.listeners[event].forEach(function (listener) {
self.emitter.removeListener(event, listener);
});
delete self.listeners[event];
});

return this;
};

module.exports = EmitterWrapper;
13 changes: 8 additions & 5 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var EventEmitter = events.EventEmitter;
var Executor = require('./executor');
var Browser = require('./browser');
var BrowserCollection = require('./browser_collection');
var EmitterWrapper = require('./emitter_wrapper');
var processWrapper = new EmitterWrapper(process);

var log = logger.create();

Expand Down Expand Up @@ -198,25 +200,26 @@ var start = function(injector, config, launcher, globalEmitter, preprocess, file
});

globalEmitter.emitAsync('exit').then(function() {
// All systems down, stop the webserver
webServer.close(function () {
webServer.close(function() {
webServer.removeAllListeners();
processWrapper.removeAllListeners();
done(code || 0);
});
});
};


try {
process.on('SIGINT', disconnectBrowsers);
process.on('SIGTERM', disconnectBrowsers);
processWrapper.on('SIGINT', disconnectBrowsers);
processWrapper.on('SIGTERM', disconnectBrowsers);
} catch (e) {
// Windows doesn't support signals yet, so they simply don't get this handling.
// https://github.com/joyent/node/issues/1553
}

// Handle all unhandled exceptions, so we don't just exit but
// disconnect the browsers before exiting.
process.on('uncaughtException', function(error) {
processWrapper.on('uncaughtException', function (error) {
log.error(error);
disconnectBrowsers(1);
});
Expand Down
57 changes: 57 additions & 0 deletions test/unit/emitter_wrapper.spec.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#==============================================================================
# lib/emitter_wrapper.js module
#==============================================================================
describe 'emitter_wrapper', ->
EmitterWrapper = require '../../lib/emitter_wrapper'
events = require 'events'
EventEmitter = events.EventEmitter

emitter = null
wrapped = null
called = false

beforeEach ->
emitter = new EventEmitter()
emitter.aMethod = (e) -> called = true
emitter.on 'anEvent', emitter.aMethod
wrapped = new EmitterWrapper(emitter)

#===========================================================================
# wrapper.addListener
#===========================================================================
describe 'addListener', ->
aListener = (e) -> true

it 'should add a listener to the wrapped emitter', ->
wrapped.addListener 'anEvent', aListener
expect(emitter.listeners('anEvent')).to.contain aListener

it 'returns the wrapped emitter', ->
expect(wrapped.addListener 'anEvent', aListener).to.equal wrapped

#===========================================================================
# wrapper.removeAllListeners
#===========================================================================
describe 'removeAllListeners', ->
aListener = (e) -> true

beforeEach ->
wrapped.addListener 'anEvent', aListener

it 'should remove listeners that were attached via the wrapper', ->
wrapped.removeAllListeners()
expect(emitter.listeners('anEvent')).not.to.contain aListener

it 'should not remove listeners that were attached to the original emitter', ->
wrapped.removeAllListeners()
expect(emitter.listeners('anEvent')).to.contain emitter.aMethod

it 'should remove only matching listeners when called with an event name', ->
anotherListener = (e) -> true
wrapped.addListener 'anotherEvent', anotherListener
wrapped.removeAllListeners('anEvent')
expect(emitter.listeners('anEvent')).not.to.contain aListener
expect(emitter.listeners('anotherEvent')).to.contain anotherListener

it 'returns the wrapped emitter', ->
expect(wrapped.addListener 'anEvent', aListener).to.equal wrapped

0 comments on commit 3baa8e1

Please sign in to comment.