diff --git a/boot.js b/boot.js index 6775384..c3952df 100644 --- a/boot.js +++ b/boot.js @@ -340,10 +340,47 @@ function callWithCbOrNextTick (func, cb, context) { } else { process.nextTick(cb) } - } else if (func.length === 2) { - func(err, cb) } else { - func(err, context, cb) + if (this._timeout === 0) { + if (func.length === 2) { + func(err, cb) + } else { + func(err, context, cb) + } + } else { + timeoutCall.call(this, func, err, context, cb) + } + } +} + +function timeoutCall (func, rootErr, context, cb) { + const name = func.name + debug('setting up ready timeout', name, this._timeout) + let timer = setTimeout(() => { + debug('timed out', name) + timer = null + const toutErr = new Error(`ERR_AVVIO_READY_TIMEOUT: plugin did not start in time: ${name}`) + toutErr.code = 'ERR_AVVIO_READY_TIMEOUT' + toutErr.fn = func + this._error = toutErr + cb(toutErr) + }, this._timeout) + + if (func.length === 2) { + func(rootErr, timeoutCb.bind(this)) + } else { + func(rootErr, context, timeoutCb.bind(this)) + } + + function timeoutCb (err) { + if (timer) { + clearTimeout(timer) + this._error = rootErr || err + cb(this._error) + } else { + // timeout has been triggered + // can not call cb twice + } } } diff --git a/test/plugin-timeout.js b/test/plugin-timeout.test.js similarity index 56% rename from test/plugin-timeout.js rename to test/plugin-timeout.test.js index 7351532..d0e061c 100644 --- a/test/plugin-timeout.js +++ b/test/plugin-timeout.test.js @@ -107,3 +107,90 @@ test('throw in override without autostart', (t) => { }) }, 20) }) + +test('timeout without calling next in ready and ignoring the error', (t) => { + t.plan(11) + const app = boot({}, { + timeout: 10, // 10 ms + autostart: false + }) + + let preReady = false + + app.use(function one (app, opts, next) { + t.pass('loaded') + app.ready(function readyOk (err, done) { + t.notOk(err) + t.pass('first ready called') + done() + }) + next() + }) + + app.on('preReady', () => { + t.pass('preReady should be called') + preReady = true + }) + + app.on('start', () => { + t.pass('start should be called') + }) + + app.ready(function onReadyWithoutDone (err, done) { + t.pass('wrong ready called') + t.ok(preReady, 'preReady already called') + t.notOk(err) + // done() // Don't call done + }) + + app.ready(function onReadyTwo (err) { + t.ok(err) + t.strictEqual(err.message, 'ERR_AVVIO_READY_TIMEOUT: plugin did not start in time: onReadyWithoutDone') + t.strictEqual(err.code, 'ERR_AVVIO_READY_TIMEOUT') + // don't rethrow the error + }) + + app.start() +}) + +test('timeout without calling next in ready and rethrowing the error', (t) => { + t.plan(11) + const app = boot({}, { + timeout: 10, // 10 ms + autostart: true + }) + + app.use(function one (app, opts, next) { + t.pass('loaded') + app.ready(function readyOk (err, done) { + t.ok(err) + t.strictEqual(err.message, 'ERR_AVVIO_READY_TIMEOUT: plugin did not start in time: onReadyWithoutDone') + t.strictEqual(err.code, 'ERR_AVVIO_READY_TIMEOUT') + done(err) + }) + next() + }) + + app.on('preReady', () => { + t.pass('preReady should be called') + }) + + app.on('start', () => { + t.pass('start should be called in any case') + }) + + app.ready(function onReadyWithoutDone (err, done) { + t.pass('wrong ready called') + t.notOk(err) + // done() // Don't call done + }) + + app.ready(function onReadyTwo (err, done) { + t.ok(err) + t.strictEqual(err.message, 'ERR_AVVIO_READY_TIMEOUT: plugin did not start in time: onReadyWithoutDone') + t.strictEqual(err.code, 'ERR_AVVIO_READY_TIMEOUT') + done(err) + }) + + app.start() +})