Skip to content

Commit bff4ae0

Browse files
committed
fix(arborist): retry bin-links on Windows EPERM during linked strategy install
On Windows, antivirus and search indexer can transiently lock files, causing write-file-atomic's fs.rename to fail with EPERM during the bin-linking phase. The linked strategy amplifies this by writing many store entries in parallel. Add retry with backoff (up to 5 attempts) for EPERM/EACCES/EBUSY errors in #createBinLinks, Windows only.
1 parent 4426411 commit bff4ae0

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

workspaces/arborist/lib/arborist/rebuild.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,30 @@ const { resolve } = require('node:path')
1515
const boolEnv = b => b ? '1' : ''
1616
const sortNodes = (a, b) => (a.depth - b.depth) || localeCompare(a.path, b.path)
1717

18+
// On Windows, antivirus/indexer can transiently lock files, causing
19+
// EPERM/EACCES/EBUSY on the rename inside write-file-atomic (used by
20+
// bin-links/fix-bin.js). Retry with backoff for up to ~7.5 seconds.
21+
/* istanbul ignore next */
22+
const retryBinLinks = async (binLinks, node, opts, retries = 4) => {
23+
const delay = (5 - retries) * 500
24+
await new Promise(r => setTimeout(r, delay))
25+
try {
26+
return await binLinks({
27+
pkg: node.package,
28+
path: node.path,
29+
top: !!(node.isTop || node.globalTop),
30+
force: opts.force,
31+
global: !!node.globalTop,
32+
})
33+
} catch (err) {
34+
if (retries > 0 &&
35+
(err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'EBUSY')) {
36+
return retryBinLinks(binLinks, node, opts, retries - 1)
37+
}
38+
throw err
39+
}
40+
}
41+
1842
const _checkBins = Symbol.for('checkBins')
1943

2044
// defined by reify mixin
@@ -387,6 +411,12 @@ module.exports = cls => class Builder extends cls {
387411
top: !!(node.isTop || node.globalTop),
388412
force: this.options.force,
389413
global: !!node.globalTop,
414+
}).catch(/* istanbul ignore next - Windows retry for antivirus locks */ err => {
415+
if (process.platform === 'win32' &&
416+
(err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'EBUSY')) {
417+
return retryBinLinks(binLinks, node, this.options)
418+
}
419+
throw err
390420
})
391421

392422
await (this.#doHandleOptionalFailure

0 commit comments

Comments
 (0)