Skip to content

Commit a29aeee

Browse files
fix(arborist): retry bin-links on Windows EPERM (#9028)
1 parent 10d5302 commit a29aeee

File tree

4 files changed

+13
-2
lines changed

4 files changed

+13
-2
lines changed

DEPENDENCIES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ graph LR;
532532
npmcli-arborist-->bin-links;
533533
npmcli-arborist-->cacache;
534534
npmcli-arborist-->common-ancestor-path;
535+
npmcli-arborist-->gar-promise-retry["@gar/promise-retry"];
535536
npmcli-arborist-->hosted-git-info;
536537
npmcli-arborist-->isaacs-string-locale-compare["@isaacs/string-locale-compare"];
537538
npmcli-arborist-->json-stringify-nice;

package-lock.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14440,6 +14440,7 @@
1444014440
"version": "9.4.0",
1444114441
"license": "ISC",
1444214442
"dependencies": {
14443+
"@gar/promise-retry": "^1.0.0",
1444314444
"@isaacs/string-locale-compare": "^1.1.0",
1444414445
"@npmcli/fs": "^5.0.0",
1444514446
"@npmcli/installed-package-contents": "^4.0.0",

workspaces/arborist/lib/arborist/rebuild.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const runScript = require('@npmcli/run-script')
99
const { callLimit: promiseCallLimit } = require('promise-call-limit')
1010
const { depth: dfwalk } = require('treeverse')
1111
const { isNodeGypPackage, defaultGypInstallScript } = require('@npmcli/node-gyp')
12+
const { promiseRetry } = require('@gar/promise-retry')
1213
const { log, time } = require('proc-log')
1314
const { resolve } = require('node:path')
1415

@@ -381,13 +382,20 @@ module.exports = cls => class Builder extends cls {
381382

382383
const timeEnd = time.start(`build:link:${node.location}`)
383384

384-
const p = binLinks({
385+
// On Windows, antivirus/indexer can transiently lock files, causing EPERM/EACCES/EBUSY on the rename inside write-file-atomic (used by bin-links/fix-bin.js), so, retry with backoff.
386+
const p = promiseRetry((retry) => binLinks({
385387
pkg: node.package,
386388
path: node.path,
387389
top: !!(node.isTop || node.globalTop),
388390
force: this.options.force,
389391
global: !!node.globalTop,
390-
})
392+
}).catch(/* istanbul ignore next - Windows-only transient antivirus locks */ err => {
393+
if (process.platform === 'win32' &&
394+
(err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'EBUSY')) {
395+
return retry(err)
396+
}
397+
throw err
398+
}), { retries: 5, minTimeout: 500 })
391399

392400
await (this.#doHandleOptionalFailure
393401
? this[_handleOptionalFailure](node, p)

workspaces/arborist/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "9.4.0",
44
"description": "Manage node_modules trees",
55
"dependencies": {
6+
"@gar/promise-retry": "^1.0.0",
67
"@isaacs/string-locale-compare": "^1.1.0",
78
"@npmcli/fs": "^5.0.0",
89
"@npmcli/installed-package-contents": "^4.0.0",

0 commit comments

Comments
 (0)