Skip to content

Commit

Permalink
fix(NODE-3116): reschedule unreliable async interval first (#3006)
Browse files Browse the repository at this point in the history
* fix(NODE-3116): reschedule unreliable async interval first

Co-authored-by: Daria Pardue <daria.pardue@mongodb.com>
  • Loading branch information
durran and dariakp committed Nov 5, 2021
1 parent f696909 commit 33886a7
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 128 deletions.
32 changes: 16 additions & 16 deletions src/utils.ts
Expand Up @@ -954,7 +954,7 @@ export function makeInterruptibleAsyncInterval(
): InterruptibleAsyncInterval {
let timerId: NodeJS.Timeout | undefined;
let lastCallTime: number;
let lastWakeTime: number;
let cannotBeExpedited = false;
let stopped = false;

options = options ?? {};
Expand All @@ -965,34 +965,34 @@ export function makeInterruptibleAsyncInterval(

function wake() {
const currentTime = clock();
const timeSinceLastWake = currentTime - lastWakeTime;
const timeSinceLastCall = currentTime - lastCallTime;
const timeUntilNextCall = interval - timeSinceLastCall;
lastWakeTime = currentTime;
const nextScheduledCallTime = lastCallTime + interval;
const timeUntilNextCall = nextScheduledCallTime - currentTime;

// For the streaming protocol: there is nothing obviously stopping this
// interval from being woken up again while we are waiting "infinitely"
// for `fn` to be called again`. Since the function effectively
// never completes, the `timeUntilNextCall` will continue to grow
// negatively unbounded, so it will never trigger a reschedule here.

// This is possible in virtualized environments like AWS Lambda where our
// clock is unreliable. In these cases the timer is "running" but never
// actually completes, so we want to execute immediately and then attempt
// to reschedule.
if (timeUntilNextCall < 0) {
executeAndReschedule();
return;
}

// debounce multiple calls to wake within the `minInterval`
if (timeSinceLastWake < minInterval) {
if (cannotBeExpedited) {
return;
}

// reschedule a call as soon as possible, ensuring the call never happens
// faster than the `minInterval`
if (timeUntilNextCall > minInterval) {
reschedule(minInterval);
}

// This is possible in virtualized environments like AWS Lambda where our
// clock is unreliable. In these cases the timer is "running" but never
// actually completes, so we want to execute immediately and then attempt
// to reschedule.
if (timeUntilNextCall < 0) {
executeAndReschedule();
cannotBeExpedited = true;
}
}

Expand All @@ -1004,7 +1004,7 @@ export function makeInterruptibleAsyncInterval(
}

lastCallTime = 0;
lastWakeTime = 0;
cannotBeExpedited = false;
}

function reschedule(ms?: number) {
Expand All @@ -1017,7 +1017,7 @@ export function makeInterruptibleAsyncInterval(
}

function executeAndReschedule() {
lastWakeTime = 0;
cannotBeExpedited = false;
lastCallTime = clock();

fn(err => {
Expand Down

0 comments on commit 33886a7

Please sign in to comment.