@lmzhen/dsh-evolution-core 0.1.0-rc.64 → 0.1.0-rc.66
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/lib/index.js +38 -10
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -52,12 +52,22 @@ function nodeEvolutionIo() {
|
|
|
52
52
|
const code = error?.code;
|
|
53
53
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
54
54
|
};
|
|
55
|
+
/** True when the pid is alive (EPERM = alive but unowned; ESRCH = gone). */
|
|
56
|
+
const isAlive = (pid) => {
|
|
57
|
+
try {
|
|
58
|
+
process.kill(pid, 0);
|
|
59
|
+
return true;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return error?.code === "EPERM";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
55
64
|
/**
|
|
56
65
|
* Cross-process write lock (claw `withFileLock` parity): an O_EXCL lock file
|
|
57
|
-
* guards the atomic write
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
66
|
+
* guards the atomic write. A >5s-old lock is taken over ONLY after probing
|
|
67
|
+
* the holder pid it carries (rc.66): a LIVE holder is never stolen, so a
|
|
68
|
+
* slow writer no longer loses its lock to a peer at the 5s mark (the
|
|
69
|
+
* takeover is the only best-effort surface; the retry budget fails loud —
|
|
70
|
+
* rc.65 — instead of ever proceeding unlocked).
|
|
61
71
|
*/
|
|
62
72
|
const withWriteLock = async (path, task) => {
|
|
63
73
|
const lock = `${path}.lock`;
|
|
@@ -73,17 +83,20 @@ function nodeEvolutionIo() {
|
|
|
73
83
|
try {
|
|
74
84
|
const st = await stat(lock);
|
|
75
85
|
if (Date.now() - st.mtimeMs > 5e3) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
const holder = Number(await readFile(lock, "utf8").catch(() => ""));
|
|
87
|
+
if (!(Number.isInteger(holder) && holder > 0 && isAlive(holder))) {
|
|
88
|
+
try {
|
|
89
|
+
await rm(lock, { force: true });
|
|
90
|
+
} catch {}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
80
93
|
}
|
|
81
94
|
} catch {
|
|
82
95
|
continue;
|
|
83
96
|
}
|
|
84
97
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
85
98
|
}
|
|
86
|
-
|
|
99
|
+
throw new Error(`could not acquire write lock for ${path} after 10 attempts`);
|
|
87
100
|
};
|
|
88
101
|
return {
|
|
89
102
|
async readText(path) {
|
|
@@ -257,6 +270,11 @@ async function loadUsage(root, io = nodeEvolutionIo()) {
|
|
|
257
270
|
*/
|
|
258
271
|
async function mutateUsage(root, io, task) {
|
|
259
272
|
await transactIo(io, usageFile(root), async (current) => {
|
|
273
|
+
if (current !== null) try {
|
|
274
|
+
JSON.parse(current);
|
|
275
|
+
} catch {
|
|
276
|
+
return current;
|
|
277
|
+
}
|
|
260
278
|
const map = parseUsage(current);
|
|
261
279
|
await task(map);
|
|
262
280
|
return JSON.stringify(Object.fromEntries(map.entries()), null, 2);
|
|
@@ -338,6 +356,11 @@ async function saveSuppressedNames(root, names, io = nodeEvolutionIo()) {
|
|
|
338
356
|
*/
|
|
339
357
|
async function updateSuppressedNames(root, io, task) {
|
|
340
358
|
await transactIo(io, suppressedFile(root), async (current) => {
|
|
359
|
+
if (current !== null) try {
|
|
360
|
+
JSON.parse(current);
|
|
361
|
+
} catch {
|
|
362
|
+
return current;
|
|
363
|
+
}
|
|
341
364
|
const names = parseSuppressed(current);
|
|
342
365
|
await task(names);
|
|
343
366
|
return JSON.stringify({
|
|
@@ -1703,7 +1726,12 @@ async function loadMutations(root, io = nodeEvolutionIo()) {
|
|
|
1703
1726
|
}
|
|
1704
1727
|
/** Append one record, trim to `cap`, and write atomically (versioned shape). */
|
|
1705
1728
|
async function recordMutation(root, io, record, cap = 500) {
|
|
1706
|
-
await transactIo(io, mutationsFile(root), (current) => {
|
|
1729
|
+
await transactIo(io, mutationsFile(root), async (current) => {
|
|
1730
|
+
if (current !== null) try {
|
|
1731
|
+
JSON.parse(current);
|
|
1732
|
+
} catch {
|
|
1733
|
+
return current;
|
|
1734
|
+
}
|
|
1707
1735
|
const existing = parseMutationRecords(current);
|
|
1708
1736
|
existing.push(record);
|
|
1709
1737
|
const trimmed = existing.length > cap ? existing.slice(existing.length - cap) : existing;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-core",
|
|
3
3
|
"description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
|
|
4
|
-
"version": "0.1.0-rc.
|
|
4
|
+
"version": "0.1.0-rc.66",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|