@pnpm/fs.graceful-fs 1100.2.4 → 1100.2.5
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 +7 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +33 -20
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -55,10 +55,10 @@ export declare function unlinkWithRetry(target: string): void;
|
|
|
55
55
|
* Runs a filesystem operation with the retry policy of
|
|
56
56
|
* {@link renameFileWithRetry}.
|
|
57
57
|
*/
|
|
58
|
-
export declare function withFileLockRetry<
|
|
58
|
+
export declare function withFileLockRetry<Result>(operation: () => Result): Result;
|
|
59
59
|
/**
|
|
60
60
|
* Asynchronous {@link withFileLockRetry}, which waits between attempts
|
|
61
61
|
* without blocking the event loop.
|
|
62
62
|
*/
|
|
63
|
-
export declare function withFileLockRetryAsync<
|
|
63
|
+
export declare function withFileLockRetryAsync<Result>(operation: () => Promise<Result>): Promise<Result>;
|
|
64
64
|
export declare function isTransientFileLockError(err: unknown): err is NodeJS.ErrnoException;
|
package/lib/index.js
CHANGED
|
@@ -31,23 +31,33 @@ function withEagainRetry(fn, maxRetries = 15) {
|
|
|
31
31
|
return (...args) => {
|
|
32
32
|
let attempts = 0;
|
|
33
33
|
while (attempts <= maxRetries) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (isError(err) && 'code' in err && err.code === 'EAGAIN' && attempts < maxRetries) {
|
|
39
|
-
attempts++;
|
|
40
|
-
// Exponential backoff: wait 2^attempts milliseconds, max 300ms
|
|
41
|
-
const delay = Math.min(Math.pow(2, attempts), 300);
|
|
42
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
34
|
+
const outcome = tryCallWithEagain(fn, args, attempts, maxRetries);
|
|
35
|
+
if (outcome.success)
|
|
36
|
+
return outcome.value;
|
|
37
|
+
attempts = outcome.attempts;
|
|
47
38
|
}
|
|
48
39
|
throw new Error('Unreachable');
|
|
49
40
|
};
|
|
50
41
|
}
|
|
42
|
+
function tryCallWithEagain(fn, args, attempts, maxRetries) {
|
|
43
|
+
try {
|
|
44
|
+
return { success: true, value: fn(...args) };
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (!isEagainError(err) || attempts >= maxRetries)
|
|
48
|
+
throw err;
|
|
49
|
+
const nextAttempts = attempts + 1;
|
|
50
|
+
waitEagainBackoff(nextAttempts);
|
|
51
|
+
return { success: false, attempts: nextAttempts };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function isEagainError(err) {
|
|
55
|
+
return isError(err) && 'code' in err && err.code === 'EAGAIN';
|
|
56
|
+
}
|
|
57
|
+
function waitEagainBackoff(attempts) {
|
|
58
|
+
const delay = Math.min(Math.pow(2, attempts), 300);
|
|
59
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
|
|
60
|
+
}
|
|
51
61
|
/**
|
|
52
62
|
* Renames `src` over `dest`, retrying Windows EBUSY errors for up to a minute.
|
|
53
63
|
* Windows drives mounted into WSL get the same retries.
|
|
@@ -117,19 +127,22 @@ export async function withFileLockRetryAsync(operation) {
|
|
|
117
127
|
const retry = createFileLockRetry();
|
|
118
128
|
for (;;) {
|
|
119
129
|
try {
|
|
120
|
-
// eslint-disable-next-line no-await-in-loop
|
|
130
|
+
// eslint-disable-next-line no-await-in-loop -- the next attempt runs only after this one fails
|
|
121
131
|
return await operation();
|
|
122
132
|
}
|
|
123
133
|
catch (err) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// eslint-disable-next-line no-await-in-loop
|
|
127
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
128
|
-
}
|
|
129
|
-
retry.checkBudgetAfterDelay(err);
|
|
134
|
+
// eslint-disable-next-line no-await-in-loop -- backs off before the next attempt
|
|
135
|
+
await backOffWithFileLockRetryAsync(retry, err);
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
}
|
|
139
|
+
async function backOffWithFileLockRetryAsync(retry, err) {
|
|
140
|
+
const delayMs = retry.delayBeforeNextAttempt(err);
|
|
141
|
+
if (delayMs > 0) {
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
143
|
+
}
|
|
144
|
+
retry.checkBudgetAfterDelay(err);
|
|
145
|
+
}
|
|
133
146
|
function createFileLockRetry() {
|
|
134
147
|
const startedAt = Date.now();
|
|
135
148
|
let backoffMs = 0;
|
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.5",
|
|
4
4
|
"description": "Promisified graceful-fs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"!*.map"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pnpm/error": "1100.2.
|
|
30
|
+
"@pnpm/error": "1100.2.2",
|
|
31
31
|
"graceful-fs": "^4.2.11"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@jest/globals": "30.4.1",
|
|
35
|
-
"@pnpm/fs.graceful-fs": "1100.2.
|
|
35
|
+
"@pnpm/fs.graceful-fs": "1100.2.5",
|
|
36
36
|
"@types/graceful-fs": "^4.1.9"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|