diff --git a/lib/timers.js b/lib/timers.js index 6d456da36f..28fa6a4cfb 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -156,12 +156,19 @@ function TimersList(msecs, unrefed) { this._timer = new TimerWrap(); this._unrefed = unrefed; this.msecs = msecs; + this.nextTick = false; } function listOnTimeout() { var list = this._list; var msecs = list.msecs; + if (list.nextTick) { + list.nextTick = false; + process.nextTick(listOnTimeoutNT, list); + return; + } + debug('timeout callback %d', msecs); var now = TimerWrap.now(); @@ -239,6 +246,14 @@ function tryOnTimeout(timer, list) { } finally { if (!threw) return; + // Postpone all later list events to next tick. We need to do this + // so that the events are called in the order they were created. + const lists = list._unrefed === true ? unrefedLists : refedLists; + for (var key in lists) { + if (key > list.msecs) { + lists[key].nextTick = true; + } + } // We need to continue processing after domain error handling // is complete, but not by using whatever domain was left over // when the timeout threw its exception. diff --git a/test/parallel/test-domain-timers-uncaught-exception.js b/test/parallel/test-domain-timers-uncaught-exception.js new file mode 100644 index 0000000000..d564d853ac --- /dev/null +++ b/test/parallel/test-domain-timers-uncaught-exception.js @@ -0,0 +1,25 @@ +'use strict'; +const common = require('../common'); + +// This test ensures that the timer callbacks are called in the order in which +// they were created in the event of an unhandled exception in the domain. + +const domain = require('domain').create(); +const assert = require('assert'); + +let first = false; + +domain.run(function() { + setTimeout(() => { throw new Error('FAIL'); }, 1); + setTimeout(() => { first = true; }, 1); + setTimeout(() => { assert.strictEqual(first, true); }, 2); + + // Ensure that 2 ms have really passed + let i = 1e6; + while (i--); +}); + +domain.once('error', common.mustCall((err) => { + assert(err); + assert.strictEqual(err.message, 'FAIL'); +}));