wowdump 0.2.0 → 0.2.1
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/dist/process-log-lock.js +28 -14
- package/package.json +1 -1
package/dist/process-log-lock.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdir, readFile, readdir, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
3
3
|
const DEFAULT_RETRY_DELAY_MS = 20;
|
|
4
4
|
const DEFAULT_STALE_AFTER_MS = 2_000;
|
|
5
|
-
const DEFAULT_TRANSIENT_RETRIES =
|
|
5
|
+
const DEFAULT_TRANSIENT_RETRIES = 50;
|
|
6
6
|
const defaultFileSystem = { mkdir, readFile, readdir, rename, rm, stat, utimes, writeFile };
|
|
7
7
|
export async function acquireProcessLogLock(file, options = {}) {
|
|
8
8
|
const fileSystem = { ...defaultFileSystem, ...options.fileSystem };
|
|
@@ -11,6 +11,7 @@ export async function acquireProcessLogLock(file, options = {}) {
|
|
|
11
11
|
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
12
12
|
const staleAfterMs = Math.max(options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS, 2_000);
|
|
13
13
|
const transientRetries = options.transientRetries ?? DEFAULT_TRANSIENT_RETRIES;
|
|
14
|
+
const operationTimeoutMs = options.timeoutMs ?? 10_000;
|
|
14
15
|
const platform = options.platform ?? process.platform;
|
|
15
16
|
const ownerPid = options.ownerPid ?? process.pid;
|
|
16
17
|
const token = (options.tokenFactory ?? randomUUID)();
|
|
@@ -18,7 +19,7 @@ export async function acquireProcessLogLock(file, options = {}) {
|
|
|
18
19
|
const ownerName = `owner.${tokenHash}.json`;
|
|
19
20
|
const ownerPath = `${file}/${ownerName}`;
|
|
20
21
|
const candidate = `${file}.candidate-${ownerPid}-${tokenHash}`;
|
|
21
|
-
const deadline = now() +
|
|
22
|
+
const deadline = now() + operationTimeoutMs;
|
|
22
23
|
const isProcessAlive = options.isProcessAlive ?? processIsAlive;
|
|
23
24
|
while (now() <= deadline) {
|
|
24
25
|
if (await publishCandidate(file, candidate, ownerName, ownerPid, token, fileSystem, now, platform)) {
|
|
@@ -31,15 +32,16 @@ export async function acquireProcessLogLock(file, options = {}) {
|
|
|
31
32
|
released = true;
|
|
32
33
|
clearInterval(heartbeat);
|
|
33
34
|
const claimName = `claim.${tokenHash}.json`;
|
|
35
|
+
const releaseDeadline = now() + operationTimeoutMs;
|
|
34
36
|
try {
|
|
35
|
-
await
|
|
37
|
+
await renameWithTransientWindowsRetry(ownerPath, `${file}/${claimName}`, fileSystem, platform, retryDelayMs, transientRetries, releaseDeadline, now, sleep);
|
|
36
38
|
}
|
|
37
39
|
catch (error) {
|
|
38
40
|
if (errorCode(error) === "ENOENT")
|
|
39
41
|
return;
|
|
40
42
|
throw error;
|
|
41
43
|
}
|
|
42
|
-
await quarantineClaimed(file, ownerPid, tokenHash, "release", fileSystem, platform, retryDelayMs, transientRetries, sleep);
|
|
44
|
+
await quarantineClaimed(file, ownerPid, tokenHash, "release", fileSystem, platform, retryDelayMs, transientRetries, releaseDeadline, now, sleep);
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
const state = await inspectState(file, fileSystem);
|
|
@@ -49,7 +51,7 @@ export async function acquireProcessLogLock(file, options = {}) {
|
|
|
49
51
|
const claimed = now() - state.mtimeMs >= staleAfterMs
|
|
50
52
|
&& await claimLegacy(file, ownerPid, tokenHash, fileSystem);
|
|
51
53
|
if (claimed) {
|
|
52
|
-
await quarantineClaimed(file, ownerPid, tokenHash, "legacy", fileSystem, platform, retryDelayMs, transientRetries, sleep);
|
|
54
|
+
await quarantineClaimed(file, ownerPid, tokenHash, "legacy", fileSystem, platform, retryDelayMs, transientRetries, deadline, now, sleep);
|
|
53
55
|
continue;
|
|
54
56
|
}
|
|
55
57
|
}
|
|
@@ -59,8 +61,8 @@ export async function acquireProcessLogLock(file, options = {}) {
|
|
|
59
61
|
if (reclaimable) {
|
|
60
62
|
const claimName = `claim.${tokenHash}.json`;
|
|
61
63
|
try {
|
|
62
|
-
await
|
|
63
|
-
await quarantineClaimed(file, ownerPid, tokenHash, "stale", fileSystem, platform, retryDelayMs, transientRetries, sleep);
|
|
64
|
+
await renameWithTransientWindowsRetry(`${file}/${state.name}`, `${file}/${claimName}`, fileSystem, platform, retryDelayMs, transientRetries, deadline, now, sleep);
|
|
65
|
+
await quarantineClaimed(file, ownerPid, tokenHash, "stale", fileSystem, platform, retryDelayMs, transientRetries, deadline, now, sleep);
|
|
64
66
|
continue;
|
|
65
67
|
}
|
|
66
68
|
catch (error) {
|
|
@@ -141,22 +143,34 @@ async function claimLegacy(file, ownerPid, tokenHash, fileSystem) {
|
|
|
141
143
|
throw error;
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
|
-
async function quarantineClaimed(file, ownerPid, tokenHash, reason, fileSystem, platform, retryDelayMs, transientRetries, sleep) {
|
|
146
|
+
async function quarantineClaimed(file, ownerPid, tokenHash, reason, fileSystem, platform, retryDelayMs, transientRetries, deadline, now, sleep) {
|
|
145
147
|
const quarantine = `${file}.${reason}-${ownerPid}-${tokenHash}`;
|
|
148
|
+
try {
|
|
149
|
+
await renameWithTransientWindowsRetry(file, quarantine, fileSystem, platform, retryDelayMs, transientRetries, deadline, now, sleep);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (errorCode(error) === "ENOENT")
|
|
153
|
+
return;
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
await fileSystem.rm(quarantine, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
async function renameWithTransientWindowsRetry(oldPath, newPath, fileSystem, platform, retryDelayMs, transientRetries, deadline, now, sleep) {
|
|
159
|
+
let firstTransientError;
|
|
146
160
|
for (let attempt = 0;; attempt += 1) {
|
|
147
161
|
try {
|
|
148
|
-
await fileSystem.rename(
|
|
149
|
-
|
|
162
|
+
await fileSystem.rename(oldPath, newPath);
|
|
163
|
+
return;
|
|
150
164
|
}
|
|
151
165
|
catch (error) {
|
|
152
|
-
if (
|
|
153
|
-
return;
|
|
154
|
-
if (!isTransientWindowsLockError(error, platform) || attempt >= transientRetries)
|
|
166
|
+
if (!isTransientWindowsLockError(error, platform))
|
|
155
167
|
throw error;
|
|
168
|
+
firstTransientError ??= error;
|
|
169
|
+
if (attempt >= transientRetries || now() + retryDelayMs > deadline)
|
|
170
|
+
throw firstTransientError;
|
|
156
171
|
await sleep(retryDelayMs);
|
|
157
172
|
}
|
|
158
173
|
}
|
|
159
|
-
await fileSystem.rm(quarantine, { recursive: true, force: true });
|
|
160
174
|
}
|
|
161
175
|
async function touch(path, fileSystem, now) {
|
|
162
176
|
const date = new Date(now());
|