diff --git a/lib/roles/device/plugins/http.js b/lib/roles/device/plugins/http.js index b6447a96..b354fa90 100644 --- a/lib/roles/device/plugins/http.js +++ b/lib/roles/device/plugins/http.js @@ -10,12 +10,12 @@ var split = require('split') var logger = require('../../../util/logger') var devutil = require('../../../util/devutil') +var lifecycle = require('../../../util/lifecycle') module.exports = syrup.serial() .dependency(require('../support/adb')) - .dependency(require('../support/quit')) .dependency(require('../resources/remote')) - .define(function(options, adb, quit, remote) { + .define(function(options, adb, remote) { var log = logger.createLogger('device:plugins:http') var service = { @@ -34,18 +34,11 @@ module.exports = syrup.serial() , '--listen-http', service.port ]) .then(function(out) { + lifecycle.share('Remote shell', out) out.pipe(split()) .on('data', function(chunk) { log.info('Remote says: "%s"', chunk) }) - .on('error', function(err) { - log.fatal('Remote shell had an error', err.stack) - quit.fatal() - }) - .on('end', function() { - log.fatal('Remote shell ended') - quit.fatal() - }) }) .then(function() { return devutil.waitForPort(adb, options.serial, service.port) @@ -82,11 +75,7 @@ module.exports = syrup.serial() var resolver = Promise.defer() function resolve() { - proxyServer - .on('error', function(err) { - log.fatal('Proxy server had an error', err.stack) - quit.fatal() - }) + lifecycle.share('Proxy server', proxyServer) resolver.resolve() } diff --git a/lib/roles/device/plugins/input.js b/lib/roles/device/plugins/input.js index e86f6f0a..06e60a79 100644 --- a/lib/roles/device/plugins/input.js +++ b/lib/roles/device/plugins/input.js @@ -11,14 +11,14 @@ var keyutil = require('../../../util/keyutil') var streamutil = require('../../../util/streamutil') var logger = require('../../../util/logger') var ms = require('../../../wire/messagestream') +var lifecycle = require('../../../util/lifecycle') module.exports = syrup.serial() .dependency(require('../support/adb')) .dependency(require('../support/router')) .dependency(require('../support/push')) - .dependency(require('../support/quit')) .dependency(require('../resources/service')) - .define(function(options, adb, router, push, quit, apk) { + .define(function(options, adb, router, push, apk) { var log = logger.createLogger('device:plugins:input') var serviceQueue = [] @@ -49,18 +49,11 @@ module.exports = syrup.serial() )) }) .then(function(out) { + lifecycle.share('InputAgent shell', out) out.pipe(split()) .on('data', function(chunk) { log.info('Agent says: "%s"', chunk) }) - .on('error', function(err) { - log.fatal('InputAgent shell had an error', err.stack) - quit.fatal() - }) - .on('end', function() { - log.fatal('InputAgent shell ended') - quit.fatal() - }) }) .then(function() { return devutil.waitForPort(adb, options.serial, agent.port) @@ -69,14 +62,7 @@ module.exports = syrup.serial() agent.socket = conn agent.writer = new ms.DelimitingStream() agent.writer.pipe(conn) - conn.on('error', function(err) { - log.fatal('InputAgent socket had an error', err.stack) - quit.fatal() - }) - conn.on('end', function() { - log.fatal('InputAgent socket ended') - quit.fatal() - }) + lifecycle.share('InputAgent connection', conn) }) } @@ -160,14 +146,7 @@ module.exports = syrup.serial() }) service.writer = new ms.DelimitingStream() service.writer.pipe(conn) - conn.on('error', function(err) { - log.fatal('InputService socket had an error', err.stack) - quit.fatal() - }) - .on('end', function() { - log.fatal('InputService socket ended') - quit.fatal() - }) + lifecycle.share('InputService connection', conn) }) } diff --git a/lib/roles/device/plugins/logcat.js b/lib/roles/device/plugins/logcat.js index da5813a9..51f4f89b 100644 --- a/lib/roles/device/plugins/logcat.js +++ b/lib/roles/device/plugins/logcat.js @@ -3,29 +3,21 @@ var syrup = require('syrup') var logger = require('../../../util/logger') var wire = require('../../../wire') var wireutil = require('../../../wire/util') +var lifecycle = require('../../../util/lifecycle') module.exports = syrup.serial() .dependency(require('../support/adb')) .dependency(require('../support/router')) .dependency(require('../support/push')) - .dependency(require('../support/quit')) .dependency(require('./owner')) - .define(function(options, adb, router, push, quit, owner) { + .define(function(options, adb, router, push, owner) { var log = logger.createLogger('device:plugins:logcat') function openService() { log.info('Launching logcat service') return adb.openLogcat(options.serial) .then(function(logcat) { - return logcat - .on('error', function(err) { - log.fatal('Logcat had an error', err) - quit.fatal() - }) - .on('end', function() { - log.fatal('Logcat ended') - quit.fatal() - }) + return lifecycle.share('Logcat', logcat) }) } diff --git a/lib/roles/device/plugins/logsender.js b/lib/roles/device/plugins/logsender.js index 03ed2fa3..fa6b691f 100644 --- a/lib/roles/device/plugins/logsender.js +++ b/lib/roles/device/plugins/logsender.js @@ -4,11 +4,11 @@ var syrup = require('syrup') var logger = require('../../../util/logger') var wire = require('../../../wire') var wireutil = require('../../../wire/util') +var lifecycle = require('../../../util/lifecycle') module.exports = syrup.serial() .dependency(require('../support/push')) - .dependency(require('../support/quit')) - .define(function(options, push, quit) { + .define(function(options, push) { // Forward all logs logger.on('entry', function(entry) { push.send([ @@ -25,7 +25,7 @@ module.exports = syrup.serial() ]) }) - quit.observe(function() { + lifecycle.observe(function() { // Let's give it some time to flush logs before dying return Promise.delay(500) }) diff --git a/lib/roles/device/plugins/owner.js b/lib/roles/device/plugins/owner.js index 685f56bd..3f9a1cf0 100644 --- a/lib/roles/device/plugins/owner.js +++ b/lib/roles/device/plugins/owner.js @@ -5,6 +5,7 @@ var logger = require('../../../util/logger') var wire = require('../../../wire') var wireutil = require('../../../wire/util') var devutil = require('../../../util/devutil') +var lifecycle = require('../../../util/lifecycle') module.exports = syrup.serial() .dependency(require('./identity')) @@ -13,9 +14,7 @@ module.exports = syrup.serial() .dependency(require('../support/push')) .dependency(require('../support/sub')) .dependency(require('../support/channels')) - .dependency(require('../support/quit')) - .define(function(options, identity, input, router, push, sub, channels, - quit) { + .define(function(options, identity, input, router, push, sub, channels) { var log = logger.createLogger('device:plugins:owner') var owner = null @@ -131,7 +130,7 @@ module.exports = syrup.serial() } }) - quit.observe(function() { + lifecycle.observe(function() { if (isGrouped()) { leaveGroup() return Promise.delay(500) diff --git a/lib/roles/device/plugins/stats.js b/lib/roles/device/plugins/stats.js index e677ce1a..a5e1af2d 100644 --- a/lib/roles/device/plugins/stats.js +++ b/lib/roles/device/plugins/stats.js @@ -3,12 +3,12 @@ var split = require('split') var logger = require('../../../util/logger') var devutil = require('../../../util/devutil') +var lifecycle = require('../../../util/lifecycle') module.exports = syrup.serial() .dependency(require('../support/adb')) - .dependency(require('../support/quit')) .dependency(require('../resources/remote')) - .define(function(options, adb, quit, remote) { + .define(function(options, adb, remote) { var log = logger.createLogger('device:plugins:stats') var service = { @@ -24,33 +24,18 @@ module.exports = syrup.serial() , '--listen-stats', service.port ]) .then(function(out) { + lifecycle.share('Stats remote shell', out) out.pipe(split()) .on('data', function(chunk) { log.info('Remote says: "%s"', chunk) }) - .on('error', function(err) { - log.fatal('Remote shell had an error', err.stack) - quit.fatal() - }) - .on('end', function() { - log.fatal('Remote shell ended') - quit.fatal() - }) }) }) .then(function() { return devutil.waitForPort(adb, options.serial, service.port) }) .then(function(conn) { - conn.pipe(split()) - .on('error', function(err) { - log.fatal('Remote had an error', err.stack) - quit.fatal() - }) - .on('end', function() { - log.fatal('Remote ended') - quit.fatal() - }) + return lifecycle.share('Stats connection', conn) }) } diff --git a/lib/roles/device/plugins/touch.js b/lib/roles/device/plugins/touch.js index 5535f9e7..8f60dddf 100644 --- a/lib/roles/device/plugins/touch.js +++ b/lib/roles/device/plugins/touch.js @@ -6,13 +6,13 @@ var monkey = require('adbkit-monkey') var wire = require('../../../wire') var devutil = require('../../../util/devutil') var logger = require('../../../util/logger') +var lifecycle = require('../../../util/lifecycle') module.exports = syrup.serial() .dependency(require('../support/adb')) .dependency(require('../support/router')) - .dependency(require('../support/quit')) .dependency(require('../resources/remote')) - .define(function(options, adb, router, quit, remote) { + .define(function(options, adb, router, remote) { var log = logger.createLogger('device:plugins:touch') var service = { @@ -30,18 +30,11 @@ module.exports = syrup.serial() ]) }) .then(function(out) { + lifecycle.share('Touch remote shell', out) out.pipe(split()) .on('data', function(chunk) { log.info('Remote says: "%s"', chunk) }) - .on('error', function(err) { - log.fatal('Remote had an error', err.stack) - quit.fatal() - }) - .on('end', function() { - log.fatal('Remote ended') - quit.fatal() - }) }) .then(function() { return devutil.waitForPort(adb, options.serial, service.port) @@ -50,15 +43,7 @@ module.exports = syrup.serial() return Promise.promisifyAll(monkey.connectStream(conn)) }) .then(function(monkey) { - return monkey - .on('error', function(err) { - log.fatal('Monkey had an error', err.stack) - quit.fatal() - }) - .on('end', function() { - log.fatal('Monkey ended') - quit.fatal() - }) + return lifecycle.share('Touch monkey', monkey) }) } diff --git a/lib/roles/device/support/quit.js b/lib/roles/device/support/quit.js deleted file mode 100644 index bb7ad4d2..00000000 --- a/lib/roles/device/support/quit.js +++ /dev/null @@ -1,38 +0,0 @@ -var Promise = require('bluebird') -var syrup = require('syrup') - -var logger = require('../../../util/logger') - -module.exports = syrup.serial() - .define(function() { - var log = logger.createLogger('device:support:quit') - var cleanup = [] - - function graceful() { - log.info('Winding down for graceful exit') - - var wait = Promise.all(cleanup.map(function(fn) { - return fn() - })) - - return wait.then(function() { - process.exit(0) - }) - } - - function fatal() { - log.fatal('Shutting down due to fatal error') - process.exit(1) - } - - process.on('SIGINT', graceful) - process.on('SIGTERM', graceful) - - return { - graceful: graceful - , fatal: fatal - , observe: function(promise) { - cleanup.push(promise) - } - } - }) diff --git a/lib/roles/reaper.js b/lib/roles/reaper.js index d13752e9..49d98905 100644 --- a/lib/roles/reaper.js +++ b/lib/roles/reaper.js @@ -5,10 +5,10 @@ var logger = require('../util/logger') var wire = require('../wire') var wireutil = require('../wire/util') var dbapi = require('../db/api') +var lifecycle = require('../util/lifecycle') module.exports = function(options) { var log = logger.createLogger('reaper') - , quit = Promise.defer() , timer if (options.name) { @@ -40,28 +40,10 @@ module.exports = function(options) { }) .catch(function(err) { log.error('Failed to load device list: ', err.message, err.stack) - quit.reject(err) + lifecycle.fatal() }) } timer = setInterval(reap, options.reapInterval) - - process.on('SIGINT', function() { - quit.resolve() - }) - - process.on('SIGTERM', function() { - quit.resolve() - }) - - quit.promise - .then(function() { - process.exit(0) - }) - .catch(function(err) { - log.fatal('Error caused quit: ', err.stack) - process.exit(1) - }) - log.info('Reaping devices with no heartbeat') } diff --git a/lib/util/lifecycle.js b/lib/util/lifecycle.js new file mode 100644 index 00000000..6f747182 --- /dev/null +++ b/lib/util/lifecycle.js @@ -0,0 +1,47 @@ +var Promise = require('bluebird') + +var logger = require('./logger') +var log = logger.createLogger('util:lifecycle') + +function Lifecycle() { + this.observers = [] + process.on('SIGINT', this.graceful.bind(this)) + process.on('SIGTERM', this.graceful.bind(this)) +} + +Lifecycle.prototype.share = function(name, emitter) { + emitter.on('end', function() { + log.fatal('%s ended; we shall share its fate', name) + this.fatal() + }.bind(this)) + + emitter.on('error', function(err) { + log.fatal('%s had an error', name, err.stack) + this.fatal() + }.bind(this)) + + return emitter +} + +Lifecycle.prototype.graceful = function() { + log.info('Winding down for graceful exit') + + var wait = Promise.all(this.observers.map(function(fn) { + return fn() + })) + + return wait.then(function() { + process.exit(0) + }) +} + +Lifecycle.prototype.fatal = function() { + log.fatal('Shutting down due to fatal error') + process.exit(1) +} + +Lifecycle.prototype.observe = function(promise) { + this.observers.push(promise) +} + +module.exports = new Lifecycle() diff --git a/lib/util/vitals.js b/lib/util/vitals.js deleted file mode 100644 index 54579311..00000000 --- a/lib/util/vitals.js +++ /dev/null @@ -1,24 +0,0 @@ -var events = require('events') -var util = require('util') - -function Vitals() { - events.EventEmitter.call(this) -} - -util.inherits(Vitals, events.EventEmitter) - -Vitals.prototype.register = function(name, stream) { - var that = this - - stream.on('end', function() { - that.emit('end', name) - }) - - stream.on('error', function(err) { - that.emit('error', name, err) - }) - - return stream -} - -module.exports = Vitals