From 1120c7e357a12a67b1e301694fc5399c628f4949 Mon Sep 17 00:00:00 2001 From: Matt Nathan Date: Thu, 19 May 2022 12:58:48 +0100 Subject: [PATCH 1/4] fix: EPERM error on Windows when processing dependencies --- packages/vite/src/node/optimizer/index.ts | 3 ++- packages/vite/src/node/utils.ts | 28 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index de09f978cd505c..b31cd7a1b595bf 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -15,6 +15,7 @@ import { lookupFile, normalizeId, normalizePath, + removeDir, removeDirSync, renameDir, writeFile @@ -534,7 +535,7 @@ export async function runOptimizeDeps( async function commitProcessingDepsCacheSync() { // Processing is done, we can now replace the depsCacheDir with processingCacheDir // Rewire the file paths from the temporal processing dir to the final deps cache dir - removeDirSync(depsCacheDir) + await removeDir(depsCacheDir) await renameDir(processingCacheDir, depsCacheDir) } diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 70eb3fb8229b81..ecd0cd06a02f93 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -534,6 +534,34 @@ export function removeDirSync(dir: string) { } } +const REMOVE_DIR_TIMEOUT = 5000 +const rmdir = promisify(fs.rm ?? fs.rmdir) // TODO: Remove after support for Node 12 is dropped +export async function removeDir(dir: string) { + const t0 = Date.now() + const attempt = async () => { + try { + await rmdir(dir, { recursive: true }) + } catch (e) { + if (e.code === 'ENOENT') { + return + } + if (e.code === 'ENOTEMPTY' || e.code === 'EPERM') { + if (Date.now() - t0 < REMOVE_DIR_TIMEOUT) { + // There's no delay here, testing showed it wasn't required. + // If the remove doesn't succeed the first time, it almost always succeed immediately afterwards. + // In rare cases two or three attempts are needed before the dir is successfully removed, + // typically taking <1s total. + await attempt() + return + } + } + + throw e + } + } + await attempt() +} + export const renameDir = isWindows ? promisify(gracefulRename) : fs.renameSync export function ensureWatchedFile( From 8c9d2efd7c85507c09921224a7113e8e090d87d2 Mon Sep 17 00:00:00 2001 From: Matt Nathan Date: Thu, 19 May 2022 15:41:30 +0100 Subject: [PATCH 2/4] fix: add a delay between removeDir retries so we don't spam in real error cases --- packages/vite/src/node/utils.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index ecd0cd06a02f93..37ee2d00b93d69 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -538,7 +538,9 @@ const REMOVE_DIR_TIMEOUT = 5000 const rmdir = promisify(fs.rm ?? fs.rmdir) // TODO: Remove after support for Node 12 is dropped export async function removeDir(dir: string) { const t0 = Date.now() + let backoff = 0 const attempt = async () => { + const attemptStart = Date.now() try { await rmdir(dir, { recursive: true }) } catch (e) { @@ -546,11 +548,13 @@ export async function removeDir(dir: string) { return } if (e.code === 'ENOTEMPTY' || e.code === 'EPERM') { - if (Date.now() - t0 < REMOVE_DIR_TIMEOUT) { - // There's no delay here, testing showed it wasn't required. - // If the remove doesn't succeed the first time, it almost always succeed immediately afterwards. - // In rare cases two or three attempts are needed before the dir is successfully removed, - // typically taking <1s total. + const now = Date.now() + if (now - t0 < REMOVE_DIR_TIMEOUT) { + const delay = backoff - (now - attemptStart) + if (delay > 0) { + await new Promise((resolve) => setTimeout(resolve, delay)) + } + backoff = Math.min(backoff + 10, 100) await attempt() return } From fad9650f75f70c013669e3a67c6f091121e5da31 Mon Sep 17 00:00:00 2001 From: Matt Nathan Date: Fri, 20 May 2022 07:13:57 +0100 Subject: [PATCH 3/4] refactor: make removeDir look more like gracefulRename to reduce cognitive load --- packages/vite/src/node/utils.ts | 66 +++++++++++++++++---------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 37ee2d00b93d69..dd144530fbb362 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -534,38 +534,8 @@ export function removeDirSync(dir: string) { } } -const REMOVE_DIR_TIMEOUT = 5000 -const rmdir = promisify(fs.rm ?? fs.rmdir) // TODO: Remove after support for Node 12 is dropped -export async function removeDir(dir: string) { - const t0 = Date.now() - let backoff = 0 - const attempt = async () => { - const attemptStart = Date.now() - try { - await rmdir(dir, { recursive: true }) - } catch (e) { - if (e.code === 'ENOENT') { - return - } - if (e.code === 'ENOTEMPTY' || e.code === 'EPERM') { - const now = Date.now() - if (now - t0 < REMOVE_DIR_TIMEOUT) { - const delay = backoff - (now - attemptStart) - if (delay > 0) { - await new Promise((resolve) => setTimeout(resolve, delay)) - } - backoff = Math.min(backoff + 10, 100) - await attempt() - return - } - } - - throw e - } - } - await attempt() -} - +const rmdirSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped +export const removeDir = isWindows ? promisify(gracefulRemoveDir) : rmdirSync export const renameDir = isWindows ? promisify(gracefulRename) : fs.renameSync export function ensureWatchedFile( @@ -847,6 +817,38 @@ function gracefulRename( }) } +const GRACEFUL_REMOVE_DIR_TIMEOUT = 5000 +function gracefulRemoveDir( + dir: string, + cb: (error: NodeJS.ErrnoException | null) => void +) { + const rmdir = fs.rm ?? fs.rmdir // TODO: Remove after support for Node 12 is dropped + const start = Date.now() + let backoff = 0 + rmdir(dir, { recursive: true }, function CB(er) { + if (er) { + if ( + (er.code === 'ENOTEMPTY' || + er.code === 'EACCES' || + er.code === 'EPERM') && + Date.now() - start < GRACEFUL_REMOVE_DIR_TIMEOUT + ) { + setTimeout(function () { + rmdir(dir, { recursive: true }, CB) + }, backoff) + if (backoff < 100) backoff += 10 + return + } + + if (er.code === 'ENOENT') { + er = null + } + } + + if (cb) cb(er) + }) +} + export function emptyCssComments(raw: string) { return raw.replace(multilineCommentsRE, (s) => ' '.repeat(s.length)) } From b11d53ca1e7a66be5c763e43a3231d796aaea426 Mon Sep 17 00:00:00 2001 From: patak-dev Date: Fri, 20 May 2022 23:56:41 +0200 Subject: [PATCH 4/4] fix: removeDir isWindows fallback --- packages/vite/src/node/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index dd144530fbb362..048193e09f6537 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -534,8 +534,9 @@ export function removeDirSync(dir: string) { } } -const rmdirSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped -export const removeDir = isWindows ? promisify(gracefulRemoveDir) : rmdirSync +export const removeDir = isWindows + ? promisify(gracefulRemoveDir) + : removeDirSync export const renameDir = isWindows ? promisify(gracefulRename) : fs.renameSync export function ensureWatchedFile(