Skip to content

Commit e0e11bb

Browse files
committed
fix: permit waking async interval with unreliable clock
The logic for waking the AsyncInterruptibleInterval sooner than its interval is dependent on an ability to reliably mark the last call made to the wrapped function. In environments like AWS Lambda where instances can be frozen and later thawed, it's possible for the last call to be in a distant past even though the internal timer has not completed yet. This change ensures that we immediately reschedule in these situations. NODE-2829
1 parent 6113b24 commit e0e11bb

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-4
lines changed

lib/utils.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -713,12 +713,13 @@ function makeInterruptableAsyncInterval(fn, options) {
713713
const interval = options.interval || 1000;
714714
const minInterval = options.minInterval || 500;
715715
const immediate = typeof options.immediate === 'boolean' ? options.immediate : false;
716+
const clock = typeof options.clock === 'function' ? options.clock : now;
716717

717718
function wake() {
718-
const currentTime = now();
719+
const currentTime = clock();
719720
const timeSinceLastWake = currentTime - lastWakeTime;
720721
const timeSinceLastCall = currentTime - lastCallTime;
721-
const timeUntilNextCall = Math.max(interval - timeSinceLastCall, 0);
722+
const timeUntilNextCall = interval - timeSinceLastCall;
722723
lastWakeTime = currentTime;
723724

724725
// For the streaming protocol: there is nothing obviously stopping this
@@ -737,6 +738,14 @@ function makeInterruptableAsyncInterval(fn, options) {
737738
if (timeUntilNextCall > minInterval) {
738739
reschedule(minInterval);
739740
}
741+
742+
// This is possible in virtualized environments like AWS Lambda where our
743+
// clock is unreliable. In these cases the timer is "running" but never
744+
// actually completes, so we want to execute immediately and then attempt
745+
// to reschedule.
746+
if (timeUntilNextCall < 0) {
747+
executeAndReschedule();
748+
}
740749
}
741750

742751
function stop() {
@@ -758,7 +767,7 @@ function makeInterruptableAsyncInterval(fn, options) {
758767

759768
function executeAndReschedule() {
760769
lastWakeTime = 0;
761-
lastCallTime = now();
770+
lastCallTime = clock();
762771

763772
fn(err => {
764773
if (err) throw err;
@@ -769,7 +778,7 @@ function makeInterruptableAsyncInterval(fn, options) {
769778
if (immediate) {
770779
executeAndReschedule();
771780
} else {
772-
lastCallTime = now();
781+
lastCallTime = clock();
773782
reschedule();
774783
}
775784

test/unit/utils.test.js

+46
Original file line numberDiff line numberDiff line change
@@ -116,5 +116,51 @@ describe('utils', function() {
116116

117117
this.clock.tick(250);
118118
});
119+
120+
it("should immediately schedule if the clock is unreliable", function (done) {
121+
let clockCalled = 0;
122+
let lastTime = now();
123+
const marks = [];
124+
const executor = makeInterruptableAsyncInterval(
125+
(callback) => {
126+
marks.push(now() - lastTime);
127+
lastTime = now();
128+
callback();
129+
},
130+
{
131+
interval: 50,
132+
minInterval: 10,
133+
immediate: true,
134+
clock() {
135+
clockCalled += 1;
136+
137+
// needs to happen on the third call because `wake` checks
138+
// the `currentTime` at the beginning of the function
139+
if (clockCalled === 3) {
140+
return now() - 100000;
141+
}
142+
143+
return now();
144+
},
145+
}
146+
);
147+
148+
// force mark at 20ms, and then the unreliable system clock
149+
// will report a very stale `lastCallTime` on this mark.
150+
setTimeout(() => executor.wake(), 10);
151+
152+
// try to wake again in another `minInterval + immediate`, now
153+
// using a very old `lastCallTime`. This should result in an
154+
// immediate scheduling: 0ms (immediate), 20ms (wake with minIterval)
155+
// and then 10ms for another immediate.
156+
setTimeout(() => executor.wake(), 30);
157+
158+
setTimeout(() => {
159+
executor.stop();
160+
expect(marks).to.eql([0, 20, 10, 50, 50, 50, 50]);
161+
done();
162+
}, 250);
163+
this.clock.tick(250);
164+
});
119165
});
120166
});

0 commit comments

Comments
 (0)