Skip to content

Commit

Permalink
clear pending timeout after promise.race (#5701)
Browse files Browse the repository at this point in the history
* clear pending timeout after promise.race

* Create cool-toys-marry.md

* fix lint errors

* formatting
  • Loading branch information
Feiyang1 committed Nov 10, 2021
1 parent 1583a82 commit 3b338db
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/cool-toys-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@firebase/functions": patch
---

Clear pending timeout after promise.race. It allows the process to exit immediately in case the SDK is used in Node.js, otherwise the process will wait for the timeout to finish before exiting.
34 changes: 27 additions & 7 deletions packages/functions/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,34 @@ export interface HttpResponseBody {
};
}

interface CancellablePromise<T> {
promise: Promise<T>;
cancel: () => void;
}

/**
* Returns a Promise that will be rejected after the given duration.
* The error will be of type FunctionsError.
*
* @param millis Number of milliseconds to wait before rejecting.
*/
function failAfter(millis: number): Promise<never> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new FunctionsError('deadline-exceeded', 'deadline-exceeded'));
}, millis);
});
function failAfter(millis: number): CancellablePromise<never> {
// Node timers and browser timers are fundamentally incompatible, but we
// don't care about the value here
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let timer: any | null = null;
return {
promise: new Promise((_, reject) => {
timer = setTimeout(() => {
reject(new FunctionsError('deadline-exceeded', 'deadline-exceeded'));
}, millis);
}),
cancel: () => {
if (timer) {
clearTimeout(timer);
}
}
};
}

/**
Expand Down Expand Up @@ -247,12 +263,16 @@ async function call(
// Default timeout to 70s, but let the options override it.
const timeout = options.timeout || 70000;

const failAfterHandle = failAfter(timeout);
const response = await Promise.race([
postJSON(url, body, headers, functionsInstance.fetchImpl),
failAfter(timeout),
failAfterHandle.promise,
functionsInstance.cancelAllRequests
]);

// Always clear the failAfter timeout
failAfterHandle.cancel();

// If service was deleted, interrupted response throws an error.
if (!response) {
throw new FunctionsError(
Expand Down

0 comments on commit 3b338db

Please sign in to comment.