From 80a6fe16e7952102cd83f3f916d96f77b4b32063 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 19 Apr 2024 20:57:40 +0530 Subject: [PATCH] timeouts --- desktop/src/main/services/app-update.ts | 6 +-- desktop/src/main/services/ffmpeg.ts | 39 ++++++++----------- web/apps/photos/src/services/export/index.ts | 4 +- .../photos/src/services/export/migration.ts | 4 +- .../src/services/upload/uploadService.ts | 6 +-- web/apps/photos/src/services/wasm/ffmpeg.ts | 4 +- .../photos/src/utils/upload/uploadRetrier.ts | 4 +- .../accounts/components/ChangeEmail.tsx | 4 +- .../components/two-factor/VerifyForm.tsx | 4 +- web/packages/shared/utils/index.ts | 37 ++++++++---------- 10 files changed, 51 insertions(+), 61 deletions(-) diff --git a/desktop/src/main/services/app-update.ts b/desktop/src/main/services/app-update.ts index a3f4d3bed8..e20d42fb70 100644 --- a/desktop/src/main/services/app-update.ts +++ b/desktop/src/main/services/app-update.ts @@ -58,17 +58,17 @@ const checkForUpdatesAndNotify = async (mainWindow: BrowserWindow) => { log.debug(() => "Attempting auto update"); autoUpdater.downloadUpdate(); - let timeout: NodeJS.Timeout; + let timeoutId: ReturnType; const fiveMinutes = 5 * 60 * 1000; autoUpdater.on("update-downloaded", () => { - timeout = setTimeout( + timeoutId = setTimeout( () => showUpdateDialog({ autoUpdatable: true, version }), fiveMinutes, ); }); autoUpdater.on("error", (error) => { - clearTimeout(timeout); + clearTimeout(timeoutId); log.error("Auto update failed", error); showUpdateDialog({ autoUpdatable: false, version }); }); diff --git a/desktop/src/main/services/ffmpeg.ts b/desktop/src/main/services/ffmpeg.ts index 2597bae60f..01406d8ae4 100644 --- a/desktop/src/main/services/ffmpeg.ts +++ b/desktop/src/main/services/ffmpeg.ts @@ -91,11 +91,8 @@ export async function runFFmpegCmd_( } }); - if (dontTimeout) { - await execAsync(cmd); - } else { - await promiseWithTimeout(execAsync(cmd), 30 * 1000); - } + if (dontTimeout) await execAsync(cmd); + else await withTimeout(execAsync(cmd), 30 * 1000); if (!existsSync(tempOutputFilePath)) { throw new Error("ffmpeg output file not found"); @@ -136,26 +133,22 @@ export async function deleteTempFile(tempFilePath: string) { await fs.rm(tempFilePath, { force: true }); } -const promiseWithTimeout = async ( - request: Promise, - timeout: number, -): Promise => { - const timeoutRef: { - current: NodeJS.Timeout; - } = { current: null }; - const rejectOnTimeout = new Promise((_, reject) => { - timeoutRef.current = setTimeout( +/** + * Await the given {@link promise} for {@link timeoutMS} milliseconds. If it + * does not resolve within {@link timeoutMS}, then reject with a timeout error. + */ +export const withTimeout = async (promise: Promise, ms: number) => { + let timeoutId: ReturnType; + const rejectOnTimeout = new Promise((_, reject) => { + timeoutId = setTimeout( () => reject(new Error("Operation timed out")), - timeout, + ms, ); }); - const requestWithTimeOutCancellation = async () => { - const resp = await request; - clearTimeout(timeoutRef.current); - return resp; + const promiseAndCancelTimeout = async () => { + const result = await promise; + clearTimeout(timeoutId); + return result; }; - return await Promise.race([ - requestWithTimeOutCancellation(), - rejectOnTimeout, - ]); + return Promise.race([promiseAndCancelTimeout(), rejectOnTimeout]); }; diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index 882c36f9ba..dc7d40c70c 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -6,7 +6,7 @@ import { Events, eventBus } from "@ente/shared/events"; import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; import { formatDateTimeShort } from "@ente/shared/time/format"; import { User } from "@ente/shared/user/types"; -import { sleep } from "@ente/shared/utils"; +import { wait } from "@ente/shared/utils"; import QueueProcessor, { CancellationStatus, RequestCanceller, @@ -919,7 +919,7 @@ class ExportService { e.message === CustomError.EXPORT_RECORD_JSON_PARSING_FAILED && retry ) { - await sleep(1000); + await wait(1000); return await this.getExportRecord(folder, false); } if (e.message !== CustomError.EXPORT_FOLDER_DOES_NOT_EXIST) { diff --git a/web/apps/photos/src/services/export/migration.ts b/web/apps/photos/src/services/export/migration.ts index 3f471b5399..a8c4e50689 100644 --- a/web/apps/photos/src/services/export/migration.ts +++ b/web/apps/photos/src/services/export/migration.ts @@ -3,7 +3,7 @@ import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import { User } from "@ente/shared/user/types"; -import { sleep } from "@ente/shared/utils"; +import { wait } from "@ente/shared/utils"; import { FILE_TYPE } from "constants/file"; import { getLocalCollections } from "services/collectionService"; import downloadManager from "services/download"; @@ -305,7 +305,7 @@ async function getFileExportNamesFromExportedFiles( ); let success = 0; for (const file of exportedFiles) { - await sleep(0); + await wait(0); const collectionPath = exportedCollectionPaths.get(file.collectionID); log.debug( () => diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index a2880ac38c..b15ec4f3c4 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -10,7 +10,7 @@ import { EncryptionResult, } from "@ente/shared/crypto/types"; import { CustomError, handleUploadError } from "@ente/shared/error"; -import { sleep } from "@ente/shared/utils"; +import { wait } from "@ente/shared/utils"; import { Remote } from "comlink"; import { FILE_READER_CHUNK_SIZE, @@ -405,7 +405,7 @@ export async function extractFileMetadata( fileTypeInfo: FileTypeInfo, rawFile: File | ElectronFile | string, ): Promise { - const rawFileName = getFileName(rawFile) + const rawFileName = getFileName(rawFile); let key = getMetadataJSONMapKeyForFile(collectionID, rawFileName); let googleMetadata: ParsedMetadataJSON = parsedMetadataJSONMap.get(key); @@ -543,7 +543,7 @@ export async function uploader( log.info(`uploader called for ${fileNameSize}`); UIService.setFileProgress(localID, 0); - await sleep(0); + await wait(0); let fileTypeInfo: FileTypeInfo; let fileSize: number; try { diff --git a/web/apps/photos/src/services/wasm/ffmpeg.ts b/web/apps/photos/src/services/wasm/ffmpeg.ts index 10c5a5c05c..7c7bf4b463 100644 --- a/web/apps/photos/src/services/wasm/ffmpeg.ts +++ b/web/apps/photos/src/services/wasm/ffmpeg.ts @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { promiseWithTimeout } from "@ente/shared/utils"; +import { withTimeout } from "@ente/shared/utils"; import QueueProcessor from "@ente/shared/utils/queueProcessor"; import { generateTempName } from "@ente/shared/utils/temp"; import { createFFmpeg, FFmpeg } from "ffmpeg-wasm"; @@ -41,7 +41,7 @@ export class WasmFFmpeg { if (dontTimeout) { return this.execute(cmd, inputFile, outputFileName); } else { - return promiseWithTimeout( + return withTimeout( this.execute(cmd, inputFile, outputFileName), FFMPEG_EXECUTION_WAIT_TIME, ); diff --git a/web/apps/photos/src/utils/upload/uploadRetrier.ts b/web/apps/photos/src/utils/upload/uploadRetrier.ts index 3d314fd141..ca2764f3f1 100644 --- a/web/apps/photos/src/utils/upload/uploadRetrier.ts +++ b/web/apps/photos/src/utils/upload/uploadRetrier.ts @@ -1,4 +1,4 @@ -import { sleep } from "@ente/shared/utils"; +import { wait } from "@ente/shared/utils"; const retrySleepTimeInMilliSeconds = [2000, 5000, 10000]; @@ -18,7 +18,7 @@ export async function retryHTTPCall( checkForBreakingError(e); } if (attemptNumber < retrySleepTimeInMilliSeconds.length) { - await sleep(retrySleepTimeInMilliSeconds[attemptNumber]); + await wait(retrySleepTimeInMilliSeconds[attemptNumber]); return await retrier(func, attemptNumber + 1); } else { throw e; diff --git a/web/packages/accounts/components/ChangeEmail.tsx b/web/packages/accounts/components/ChangeEmail.tsx index 3f47be8a11..ec647e6712 100644 --- a/web/packages/accounts/components/ChangeEmail.tsx +++ b/web/packages/accounts/components/ChangeEmail.tsx @@ -6,7 +6,7 @@ import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer"; import LinkButton from "@ente/shared/components/LinkButton"; import SubmitButton from "@ente/shared/components/SubmitButton"; import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; -import { sleep } from "@ente/shared/utils"; +import { wait } from "@ente/shared/utils"; import { Alert, Box, TextField } from "@mui/material"; import { Formik, FormikHelpers } from "formik"; import { t } from "i18next"; @@ -59,7 +59,7 @@ function ChangeEmailForm({ appName }: PageProps) { setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email }); setLoading(false); setSuccess(true); - await sleep(1000); + await wait(1000); goToApp(); } catch (e) { setLoading(false); diff --git a/web/packages/accounts/components/two-factor/VerifyForm.tsx b/web/packages/accounts/components/two-factor/VerifyForm.tsx index 810a6c010f..b7f7fc2781 100644 --- a/web/packages/accounts/components/two-factor/VerifyForm.tsx +++ b/web/packages/accounts/components/two-factor/VerifyForm.tsx @@ -9,7 +9,7 @@ import { VerticallyCentered, } from "@ente/shared/components/Container"; import SubmitButton from "@ente/shared/components/SubmitButton"; -import { sleep } from "@ente/shared/utils"; +import { wait } from "@ente/shared/utils"; import { Box, Typography } from "@mui/material"; interface formValues { @@ -33,7 +33,7 @@ export default function VerifyTwoFactor(props: Props) { const markSuccessful = async () => { setWaiting(false); setSuccess(true); - await sleep(1000); + await wait(1000); }; const submitForm = async ( diff --git a/web/packages/shared/utils/index.ts b/web/packages/shared/utils/index.ts index c027b6cb62..568ec5cc40 100644 --- a/web/packages/shared/utils/index.ts +++ b/web/packages/shared/utils/index.ts @@ -4,9 +4,8 @@ * This function is a promisified `setTimeout`. It returns a promise that * resolves after {@link ms} milliseconds. */ -export async function sleep(ms: number) { - await new Promise((resolve) => setTimeout(resolve, ms)); -} +export const wait = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); export function downloadAsFile(filename: string, content: string) { const file = new Blob([content], { @@ -49,29 +48,27 @@ export async function retryAsyncFunction( if (attemptNumber === waitTimeBeforeNextTry.length) { throw e; } - await sleep(waitTimeBeforeNextTry[attemptNumber]); + await wait(waitTimeBeforeNextTry[attemptNumber]); } } } -export const promiseWithTimeout = async ( - request: Promise, - timeout: number, -): Promise => { - const timeoutRef = { current: null }; - const rejectOnTimeout = new Promise((_, reject) => { - timeoutRef.current = setTimeout( +/** + * Await the given {@link promise} for {@link timeoutMS} milliseconds. If it + * does not resolve within {@link timeoutMS}, then reject with a timeout error. + */ +export const withTimeout = async (promise: Promise, ms: number) => { + let timeoutId: ReturnType; + const rejectOnTimeout = new Promise((_, reject) => { + timeoutId = setTimeout( () => reject(new Error("Operation timed out")), - timeout, + ms, ); }); - const requestWithTimeOutCancellation = async () => { - const resp = await request; - clearTimeout(timeoutRef.current); - return resp; + const promiseAndCancelTimeout = async () => { + const result = await promise; + clearTimeout(timeoutId); + return result; }; - return await Promise.race([ - requestWithTimeOutCancellation(), - rejectOnTimeout, - ]); + return Promise.race([promiseAndCancelTimeout(), rejectOnTimeout]); };