@pnpm/fs.graceful-fs 1100.2.1 → 1100.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +34 -10
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @pnpm/fs.graceful-fs
|
|
2
2
|
|
|
3
|
+
## 1100.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Installs in different projects that share a global virtual store no longer fail on Windows with `Access is denied` while repairing the same slot [#15114](https://github.com/pnpm/pnpm/issues/15114).
|
|
8
|
+
|
|
3
9
|
## 1100.2.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/lib/index.d.ts
CHANGED
|
@@ -31,3 +31,17 @@ export default _default;
|
|
|
31
31
|
* that was there or the whole file replacing it.
|
|
32
32
|
*/
|
|
33
33
|
export declare function renameFileWithRetry(src: string, dest: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Reads `target`'s stats without following it, with the retry policy of
|
|
36
|
+
* {@link renameFileWithRetry}.
|
|
37
|
+
*
|
|
38
|
+
* A Windows path another process has just unlinked stays delete-pending until
|
|
39
|
+
* the last handle on it closes, and inspecting it fails with EPERM for as long
|
|
40
|
+
* as that lasts. Retrying lets the unlink land, so a caller that reads ENOENT
|
|
41
|
+
* as an absent entry sees the same absence POSIX shows it at once.
|
|
42
|
+
*/
|
|
43
|
+
export declare function lstatWithRetry(target: string): fs.Stats;
|
|
44
|
+
/**
|
|
45
|
+
* Removes a file, with the retry policy of {@link renameFileWithRetry}.
|
|
46
|
+
*/
|
|
47
|
+
export declare function unlinkWithRetry(target: string): void;
|
package/lib/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import util, { promisify } from 'node:util';
|
|
3
3
|
import gfs from 'graceful-fs';
|
|
4
|
-
const
|
|
4
|
+
const FILE_LOCK_RETRY_BUDGET_MS = 60_000;
|
|
5
5
|
const PERMISSION_DENIED_RETRY_BUDGET_MS = 1_000;
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const FILE_LOCK_RETRY_BACKOFF_CAP_MS = 100;
|
|
7
|
+
const fileLockRetrySleepBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
8
8
|
export default {
|
|
9
9
|
chmod: promisify(gfs.chmod),
|
|
10
10
|
copyFile: promisify(gfs.copyFile),
|
|
@@ -57,16 +57,40 @@ function withEagainRetry(fn, maxRetries = 15) {
|
|
|
57
57
|
* that was there or the whole file replacing it.
|
|
58
58
|
*/
|
|
59
59
|
export function renameFileWithRetry(src, dest) {
|
|
60
|
+
withFileLockRetry(() => {
|
|
61
|
+
fs.renameSync(src, dest);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Reads `target`'s stats without following it, with the retry policy of
|
|
66
|
+
* {@link renameFileWithRetry}.
|
|
67
|
+
*
|
|
68
|
+
* A Windows path another process has just unlinked stays delete-pending until
|
|
69
|
+
* the last handle on it closes, and inspecting it fails with EPERM for as long
|
|
70
|
+
* as that lasts. Retrying lets the unlink land, so a caller that reads ENOENT
|
|
71
|
+
* as an absent entry sees the same absence POSIX shows it at once.
|
|
72
|
+
*/
|
|
73
|
+
export function lstatWithRetry(target) {
|
|
74
|
+
return withFileLockRetry(() => fs.lstatSync(target));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Removes a file, with the retry policy of {@link renameFileWithRetry}.
|
|
78
|
+
*/
|
|
79
|
+
export function unlinkWithRetry(target) {
|
|
80
|
+
withFileLockRetry(() => {
|
|
81
|
+
fs.unlinkSync(target);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function withFileLockRetry(operation) {
|
|
60
85
|
const startedAt = Date.now();
|
|
61
86
|
let backoffMs = 0;
|
|
62
|
-
let budgetMs =
|
|
87
|
+
let budgetMs = FILE_LOCK_RETRY_BUDGET_MS;
|
|
63
88
|
for (;;) {
|
|
64
89
|
try {
|
|
65
|
-
|
|
66
|
-
return;
|
|
90
|
+
return operation();
|
|
67
91
|
}
|
|
68
92
|
catch (err) {
|
|
69
|
-
if (!
|
|
93
|
+
if (!isTransientFileLockError(err))
|
|
70
94
|
throw err;
|
|
71
95
|
if (err.code === 'EPERM' || err.code === 'EACCES')
|
|
72
96
|
budgetMs = Math.min(budgetMs, PERMISSION_DENIED_RETRY_BUDGET_MS);
|
|
@@ -74,14 +98,14 @@ export function renameFileWithRetry(src, dest) {
|
|
|
74
98
|
if (remainingMs <= 0)
|
|
75
99
|
throw err;
|
|
76
100
|
if (backoffMs > 0)
|
|
77
|
-
Atomics.wait(
|
|
101
|
+
Atomics.wait(fileLockRetrySleepBuffer, 0, 0, Math.min(backoffMs, remainingMs));
|
|
78
102
|
if (Date.now() - startedAt >= budgetMs)
|
|
79
103
|
throw err;
|
|
80
|
-
backoffMs = Math.min(backoffMs + 10,
|
|
104
|
+
backoffMs = Math.min(backoffMs + 10, FILE_LOCK_RETRY_BACKOFF_CAP_MS);
|
|
81
105
|
}
|
|
82
106
|
}
|
|
83
107
|
}
|
|
84
|
-
function
|
|
108
|
+
function isTransientFileLockError(err) {
|
|
85
109
|
return process.platform === 'win32' &&
|
|
86
110
|
util.types.isNativeError(err) &&
|
|
87
111
|
'code' in err &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/fs.graceful-fs",
|
|
3
|
-
"version": "1100.2.
|
|
3
|
+
"version": "1100.2.2",
|
|
4
4
|
"description": "Promisified graceful-fs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@jest/globals": "30.4.1",
|
|
34
|
-
"@pnpm/fs.graceful-fs": "1100.2.
|
|
34
|
+
"@pnpm/fs.graceful-fs": "1100.2.2",
|
|
35
35
|
"@types/graceful-fs": "^4.1.9"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|