From bc24aaa5ac16f61e00baee27c313c3ea571774c9 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 27 Jan 2022 16:12:28 -0500 Subject: [PATCH] Add error resiliency to waitForPath Signed-off-by: Sebastian Malton --- src/common/utils/wait-for-path.ts | 45 ++++++++++++---- .../user-templates.injectable.ts | 52 ++++++++++++------- 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/common/utils/wait-for-path.ts b/src/common/utils/wait-for-path.ts index b528d20e26..f5a068075b 100644 --- a/src/common/utils/wait-for-path.ts +++ b/src/common/utils/wait-for-path.ts @@ -3,30 +3,53 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { watch } from "chokidar"; +import { FSWatcher } from "chokidar"; import path from "path"; /** * Wait for `filePath` and all parent directories to exist. - * @param filePath The file path to wait until it exists + * @param pathname The file path to wait until it exists + * + * NOTE: There is technically a race condition in this function of the form + * "time-of-check to time-of-use" because we have to wait for each parent + * directory to exist first. */ -export async function waitForPath(filePath: string): Promise { - const dirOfPath = path.dirname(filePath); +export async function waitForPath(pathname: string): Promise { + const dirOfPath = path.dirname(pathname); - if (dirOfPath === filePath) { + if (dirOfPath === pathname) { // The root of this filesystem, assume it exists return; } else { await waitForPath(dirOfPath); } - return new Promise(resolve => { - watch(dirOfPath, { + return new Promise((resolve, reject) => { + const watcher = new FSWatcher({ depth: 0, - }).on("all", (event, path) => { - if ((event === "add" || event === "addDir") && path === filePath) { - resolve(); - } + disableGlobbing: true, }); + const onAddOrAddDir = (filePath: string) => { + if (filePath === pathname) { + watcher.unwatch(dirOfPath); + watcher + .close() + .then(() => resolve()) + .catch(reject); + } + }; + const onError = (error: any) => { + watcher.unwatch(dirOfPath); + watcher + .close() + .then(() => reject(error)) + .catch(() => reject(error)); + }; + + watcher + .on("add", onAddOrAddDir) + .on("addDir", onAddOrAddDir) + .on("error", onError) + .add(dirOfPath); }); } diff --git a/src/renderer/components/dock/create-resource/user-templates.injectable.ts b/src/renderer/components/dock/create-resource/user-templates.injectable.ts index ee4f917ee6..4060191e52 100644 --- a/src/renderer/components/dock/create-resource/user-templates.injectable.ts +++ b/src/renderer/components/dock/create-resource/user-templates.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { computed, IComputedValue, observable } from "mobx"; import path from "path"; import os from "os"; -import { getOrInsert, waitForPath } from "../../../utils"; +import { delay, getOrInsert, waitForPath } from "../../../utils"; import { watch } from "chokidar"; import { readFile } from "fs/promises"; import logger from "../../../../common/logger"; @@ -58,24 +58,38 @@ function watchUserCreateResourceTemplates(): IComputedValue { templates.delete(filePath); }; - waitForPath(userTemplatesFolder) - .then(() => { - console.log("watching", userTemplatesFolder); - watch(userTemplatesFolder, { - disableGlobbing: true, - ignorePermissionErrors: true, - usePolling: false, - awaitWriteFinish: { - pollInterval: 100, - stabilityThreshold: 1000, - }, - ignoreInitial: false, - atomic: 150, // for "atomic writes" - }) - .on("add", onAddOrChange) - .on("change", onAddOrChange) - .on("unlink", onUnlink); - }); + (async () => { + for (let i = 1;; i *= 2) { + try { + await waitForPath(userTemplatesFolder); + break; + } catch (error) { + logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while waiting for ${userTemplatesFolder} to exist, waiting and trying again`, error); + await delay(i * 1000); // exponential backoff in seconds + } + } + + /** + * NOTE: There is technically a race condition here of the form "time-of-check to time-of-use" + */ + watch(userTemplatesFolder, { + disableGlobbing: true, + ignorePermissionErrors: true, + usePolling: false, + awaitWriteFinish: { + pollInterval: 100, + stabilityThreshold: 1000, + }, + ignoreInitial: false, + atomic: 150, // for "atomic writes" + }) + .on("add", onAddOrChange) + .on("change", onAddOrChange) + .on("unlink", onUnlink) + .on("error", error => { + logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while watching files under ${userTemplatesFolder}`, error); + }); + })(); return computed(() => groupTemplates(templates)); }