@matter/nodejs 0.17.3 → 0.17.4-alpha.0-20260621-81ba50a6a
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/cjs/fs/lock-utils.d.ts.map +1 -1
- package/dist/cjs/fs/lock-utils.js +44 -11
- package/dist/cjs/fs/lock-utils.js.map +1 -1
- package/dist/esm/fs/lock-utils.d.ts.map +1 -1
- package/dist/esm/fs/lock-utils.js +44 -11
- package/dist/esm/fs/lock-utils.js.map +1 -1
- package/package.json +9 -9
- package/src/fs/lock-utils.ts +68 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lock-utils.d.ts","sourceRoot":"","sources":["../../../src/fs/lock-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"lock-utils.d.ts","sourceRoot":"","sources":["../../../src/fs/lock-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAsEH;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CA0BzG"}
|
|
@@ -23,6 +23,7 @@ __export(lock_utils_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(lock_utils_exports);
|
|
24
24
|
var import_general = require("@matter/general");
|
|
25
25
|
var import_node_crypto = require("node:crypto");
|
|
26
|
+
var import_node_fs = require("node:fs");
|
|
26
27
|
var import_promises = require("node:fs/promises");
|
|
27
28
|
var import_node_path = require("node:path");
|
|
28
29
|
/**
|
|
@@ -34,6 +35,34 @@ const logger = import_general.Logger.get("NodeJsDirectoryLock");
|
|
|
34
35
|
const LOCK_FILE = "matter.lock";
|
|
35
36
|
const PID_FILE = "matter.pid";
|
|
36
37
|
const PROCESS_TOKEN = (0, import_node_crypto.randomBytes)(8).toString("hex");
|
|
38
|
+
const activeLocks = /* @__PURE__ */ new Set();
|
|
39
|
+
let exitHandlerInstalled = false;
|
|
40
|
+
function trackLock(lock) {
|
|
41
|
+
activeLocks.add(lock);
|
|
42
|
+
if (!exitHandlerInstalled) {
|
|
43
|
+
exitHandlerInstalled = true;
|
|
44
|
+
process.on("exit", removeOrphanedLocks);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function removeOrphanedLocks() {
|
|
48
|
+
for (const { lockPath, pidPath, name } of activeLocks) {
|
|
49
|
+
try {
|
|
50
|
+
logger.warn(
|
|
51
|
+
`Storage "${name}" was not closed before process exit; removing orphaned lock.`,
|
|
52
|
+
"Please close any opened storage during shutdown."
|
|
53
|
+
);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
unlinkSyncSafe(lockPath);
|
|
57
|
+
unlinkSyncSafe(pidPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function unlinkSyncSafe(path) {
|
|
61
|
+
try {
|
|
62
|
+
(0, import_node_fs.unlinkSync)(path);
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
}
|
|
37
66
|
async function acquireDirectoryLock(dirPath, dirName) {
|
|
38
67
|
const lockPath = (0, import_node_path.resolve)(dirPath, LOCK_FILE);
|
|
39
68
|
const pidPath = (0, import_node_path.resolve)(dirPath, PID_FILE);
|
|
@@ -41,16 +70,19 @@ async function acquireDirectoryLock(dirPath, dirName) {
|
|
|
41
70
|
return async () => {
|
|
42
71
|
};
|
|
43
72
|
}
|
|
44
|
-
await acquireLock(lockPath, pidPath);
|
|
73
|
+
await acquireLock(lockPath, pidPath, dirName);
|
|
45
74
|
await (0, import_promises.writeFile)(pidPath, `${process.pid} ${PROCESS_TOKEN}`);
|
|
75
|
+
const tracked = { lockPath, pidPath, name: dirName };
|
|
76
|
+
trackLock(tracked);
|
|
46
77
|
logger.debug("Acquired storage lock for", dirName, "pid", process.pid);
|
|
47
78
|
return async () => {
|
|
48
|
-
await safeUnlink(pidPath);
|
|
49
79
|
await safeUnlink(lockPath);
|
|
80
|
+
await safeUnlink(pidPath);
|
|
81
|
+
activeLocks.delete(tracked);
|
|
50
82
|
logger.debug("Released storage lock for", dirName);
|
|
51
83
|
};
|
|
52
84
|
}
|
|
53
|
-
async function acquireLock(lockPath, pidPath) {
|
|
85
|
+
async function acquireLock(lockPath, pidPath, dirName) {
|
|
54
86
|
try {
|
|
55
87
|
const fd = await (0, import_promises.open)(lockPath, "wx");
|
|
56
88
|
await fd.close();
|
|
@@ -59,8 +91,9 @@ async function acquireLock(lockPath, pidPath) {
|
|
|
59
91
|
throw error;
|
|
60
92
|
}
|
|
61
93
|
const info = await readLockInfo(pidPath);
|
|
62
|
-
|
|
63
|
-
|
|
94
|
+
const reason = staleReason(info);
|
|
95
|
+
if (reason !== void 0) {
|
|
96
|
+
logger.info("Cleaning stale storage lock for", dirName, "-", reason);
|
|
64
97
|
await safeUnlink(pidPath);
|
|
65
98
|
await safeUnlink(lockPath);
|
|
66
99
|
try {
|
|
@@ -79,21 +112,21 @@ async function acquireLock(lockPath, pidPath) {
|
|
|
79
112
|
}
|
|
80
113
|
}
|
|
81
114
|
}
|
|
82
|
-
function
|
|
115
|
+
function staleReason(info) {
|
|
83
116
|
if (info === void 0) {
|
|
84
|
-
return
|
|
117
|
+
return "no PID file (owner crashed before writing it)";
|
|
85
118
|
}
|
|
86
119
|
if (info.pid === process.pid) {
|
|
87
|
-
return info.token !== PROCESS_TOKEN;
|
|
120
|
+
return info.token !== PROCESS_TOKEN ? `PID ${info.pid} reused (token mismatch)` : void 0;
|
|
88
121
|
}
|
|
89
122
|
try {
|
|
90
123
|
process.kill(info.pid, 0);
|
|
91
|
-
return
|
|
124
|
+
return void 0;
|
|
92
125
|
} catch (error) {
|
|
93
126
|
if (error.code === "ESRCH") {
|
|
94
|
-
return
|
|
127
|
+
return `owner process ${info.pid} no longer exists`;
|
|
95
128
|
}
|
|
96
|
-
return
|
|
129
|
+
return void 0;
|
|
97
130
|
}
|
|
98
131
|
}
|
|
99
132
|
async function readLockInfo(pidPath) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/fs/lock-utils.ts"],
|
|
4
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,qBAAyC;AACzC,yBAA4B;AAC5B,sBAAkE;AAClE,uBAAwB;
|
|
4
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,qBAAyC;AACzC,yBAA4B;AAC5B,qBAA2B;AAC3B,sBAAkE;AAClE,uBAAwB;AAVxB;AAAA;AAAA;AAAA;AAAA;AAYA,MAAM,SAAS,sBAAO,IAAI,qBAAqB;AAE/C,MAAM,YAAY;AAClB,MAAM,WAAW;AAMjB,MAAM,oBAAgB,gCAAY,CAAC,EAAE,SAAS,KAAK;AAiBnD,MAAM,cAAc,oBAAI,IAAgB;AACxC,IAAI,uBAAuB;AAE3B,SAAS,UAAU,MAAkB;AACjC,cAAY,IAAI,IAAI;AACpB,MAAI,CAAC,sBAAsB;AACvB,2BAAuB;AAEvB,YAAQ,GAAG,QAAQ,mBAAmB;AAAA,EAC1C;AACJ;AAEA,SAAS,sBAAsB;AAC3B,aAAW,EAAE,UAAU,SAAS,KAAK,KAAK,aAAa;AAEnD,QAAI;AACA,aAAO;AAAA,QACH,YAAY,IAAI;AAAA,QAChB;AAAA,MACJ;AAAA,IACJ,QAAQ;AAAA,IAER;AACA,mBAAe,QAAQ;AACvB,mBAAe,OAAO;AAAA,EAC1B;AACJ;AAEA,SAAS,eAAe,MAAc;AAClC,MAAI;AACA,mCAAW,IAAI;AAAA,EACnB,QAAQ;AAAA,EAER;AACJ;AAOA,eAAsB,qBAAqB,SAAiB,SAA+C;AACvG,QAAM,eAAW,0BAAQ,SAAS,SAAS;AAC3C,QAAM,cAAU,0BAAQ,SAAS,QAAQ;AAIzC,MAAI,CAAE,MAAM,UAAU,OAAO,GAAI;AAC7B,WAAO,YAAY;AAAA,IAAC;AAAA,EACxB;AAEA,QAAM,YAAY,UAAU,SAAS,OAAO;AAC5C,YAAM,2BAAU,SAAS,GAAG,QAAQ,GAAG,IAAI,aAAa,EAAE;AAE1D,QAAM,UAAsB,EAAE,UAAU,SAAS,MAAM,QAAQ;AAC/D,YAAU,OAAO;AAEjB,SAAO,MAAM,6BAA6B,SAAS,OAAO,QAAQ,GAAG;AAErE,SAAO,YAAY;AAGf,UAAM,WAAW,QAAQ;AACzB,UAAM,WAAW,OAAO;AACxB,gBAAY,OAAO,OAAO;AAC1B,WAAO,MAAM,6BAA6B,OAAO;AAAA,EACrD;AACJ;AAEA,eAAe,YAAY,UAAkB,SAAiB,SAAiB;AAC3E,MAAI;AACA,UAAM,KAAK,UAAM,gBAAAA,MAAO,UAAU,IAAI;AACtC,UAAM,GAAG,MAAM;AAAA,EACnB,SAAS,OAAO;AACZ,QAAK,MAAgC,SAAS,UAAU;AACpD,YAAM;AAAA,IACV;AAGA,UAAM,OAAO,MAAM,aAAa,OAAO;AACvC,UAAM,SAAS,YAAY,IAAI;AAE/B,QAAI,WAAW,QAAW;AACtB,aAAO,KAAK,mCAAmC,SAAS,KAAK,MAAM;AACnE,YAAM,WAAW,OAAO;AACxB,YAAM,WAAW,QAAQ;AAGzB,UAAI;AACA,cAAM,KAAK,UAAM,gBAAAA,MAAO,UAAU,IAAI;AACtC,cAAM,GAAG,MAAM;AAAA,MACnB,SAAS,YAAY;AACjB,YAAK,WAAqC,SAAS,UAAU;AACzD,gBAAM,IAAI,gCAAiB,oEAAoE;AAAA,QACnG;AACA,cAAM;AAAA,MACV;AAAA,IACJ,WAAW,MAAM,QAAQ,QAAQ,KAAK;AAClC,YAAM,IAAI,gCAAiB,2CAA2C;AAAA,IAC1E,OAAO;AACH,YAAM,IAAI,gCAAiB,6CAA6C,MAAM,GAAG,GAAG;AAAA,IACxF;AAAA,EACJ;AACJ;AAMA,SAAS,YAAY,MAAgD;AACjE,MAAI,SAAS,QAAW;AACpB,WAAO;AAAA,EACX;AAEA,MAAI,KAAK,QAAQ,QAAQ,KAAK;AAG1B,WAAO,KAAK,UAAU,gBAAgB,OAAO,KAAK,GAAG,6BAA6B;AAAA,EACtF;AAEA,MAAI;AACA,YAAQ,KAAK,KAAK,KAAK,CAAC;AAExB,WAAO;AAAA,EACX,SAAS,OAAO;AACZ,QAAK,MAAgC,SAAS,SAAS;AACnD,aAAO,iBAAiB,KAAK,GAAG;AAAA,IACpC;AAEA,WAAO;AAAA,EACX;AACJ;AAEA,eAAe,aAAa,SAAgD;AACxE,MAAI;AACA,UAAM,UAAU,UAAM,0BAAS,SAAS,OAAO;AAC/C,UAAM,QAAQ,QAAQ,KAAK,EAAE,MAAM,KAAK;AACxC,UAAM,MAAM,SAAS,MAAM,CAAC,GAAG,EAAE;AACjC,QAAI,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,GAAG;AACnC,aAAO;AAAA,IACX;AACA,WAAO,EAAE,KAAK,OAAO,MAAM,CAAC,EAAE;AAAA,EAClC,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAEA,eAAe,UAAU,MAAgC;AACrD,MAAI;AACA,UAAM,IAAI,UAAM,sBAAK,IAAI;AACzB,WAAO,EAAE,YAAY;AAAA,EACzB,SAAS,GAAG;AACR,QAAK,EAA4B,SAAS,UAAU;AAChD,aAAO;AAAA,IACX;AACA,UAAM;AAAA,EACV;AACJ;AAEA,eAAe,WAAW,MAAc;AACpC,MAAI;AACA,cAAM,wBAAO,IAAI;AAAA,EACrB,SAAS,OAAO;AACZ,QAAK,MAAgC,SAAS,UAAU;AACpD,YAAM;AAAA,IACV;AAAA,EACJ;AACJ;",
|
|
5
5
|
"names": ["fsOpen"]
|
|
6
6
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lock-utils.d.ts","sourceRoot":"","sources":["../../../src/fs/lock-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"lock-utils.d.ts","sourceRoot":"","sources":["../../../src/fs/lock-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAsEH;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CA0BzG"}
|
|
@@ -5,12 +5,41 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Logger, StorageLockError } from "@matter/general";
|
|
7
7
|
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { unlinkSync } from "node:fs";
|
|
8
9
|
import { open as fsOpen, readFile, stat, unlink, writeFile } from "node:fs/promises";
|
|
9
10
|
import { resolve } from "node:path";
|
|
10
11
|
const logger = Logger.get("NodeJsDirectoryLock");
|
|
11
12
|
const LOCK_FILE = "matter.lock";
|
|
12
13
|
const PID_FILE = "matter.pid";
|
|
13
14
|
const PROCESS_TOKEN = randomBytes(8).toString("hex");
|
|
15
|
+
const activeLocks = /* @__PURE__ */ new Set();
|
|
16
|
+
let exitHandlerInstalled = false;
|
|
17
|
+
function trackLock(lock) {
|
|
18
|
+
activeLocks.add(lock);
|
|
19
|
+
if (!exitHandlerInstalled) {
|
|
20
|
+
exitHandlerInstalled = true;
|
|
21
|
+
process.on("exit", removeOrphanedLocks);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function removeOrphanedLocks() {
|
|
25
|
+
for (const { lockPath, pidPath, name } of activeLocks) {
|
|
26
|
+
try {
|
|
27
|
+
logger.warn(
|
|
28
|
+
`Storage "${name}" was not closed before process exit; removing orphaned lock.`,
|
|
29
|
+
"Please close any opened storage during shutdown."
|
|
30
|
+
);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
unlinkSyncSafe(lockPath);
|
|
34
|
+
unlinkSyncSafe(pidPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function unlinkSyncSafe(path) {
|
|
38
|
+
try {
|
|
39
|
+
unlinkSync(path);
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
14
43
|
async function acquireDirectoryLock(dirPath, dirName) {
|
|
15
44
|
const lockPath = resolve(dirPath, LOCK_FILE);
|
|
16
45
|
const pidPath = resolve(dirPath, PID_FILE);
|
|
@@ -18,16 +47,19 @@ async function acquireDirectoryLock(dirPath, dirName) {
|
|
|
18
47
|
return async () => {
|
|
19
48
|
};
|
|
20
49
|
}
|
|
21
|
-
await acquireLock(lockPath, pidPath);
|
|
50
|
+
await acquireLock(lockPath, pidPath, dirName);
|
|
22
51
|
await writeFile(pidPath, `${process.pid} ${PROCESS_TOKEN}`);
|
|
52
|
+
const tracked = { lockPath, pidPath, name: dirName };
|
|
53
|
+
trackLock(tracked);
|
|
23
54
|
logger.debug("Acquired storage lock for", dirName, "pid", process.pid);
|
|
24
55
|
return async () => {
|
|
25
|
-
await safeUnlink(pidPath);
|
|
26
56
|
await safeUnlink(lockPath);
|
|
57
|
+
await safeUnlink(pidPath);
|
|
58
|
+
activeLocks.delete(tracked);
|
|
27
59
|
logger.debug("Released storage lock for", dirName);
|
|
28
60
|
};
|
|
29
61
|
}
|
|
30
|
-
async function acquireLock(lockPath, pidPath) {
|
|
62
|
+
async function acquireLock(lockPath, pidPath, dirName) {
|
|
31
63
|
try {
|
|
32
64
|
const fd = await fsOpen(lockPath, "wx");
|
|
33
65
|
await fd.close();
|
|
@@ -36,8 +68,9 @@ async function acquireLock(lockPath, pidPath) {
|
|
|
36
68
|
throw error;
|
|
37
69
|
}
|
|
38
70
|
const info = await readLockInfo(pidPath);
|
|
39
|
-
|
|
40
|
-
|
|
71
|
+
const reason = staleReason(info);
|
|
72
|
+
if (reason !== void 0) {
|
|
73
|
+
logger.info("Cleaning stale storage lock for", dirName, "-", reason);
|
|
41
74
|
await safeUnlink(pidPath);
|
|
42
75
|
await safeUnlink(lockPath);
|
|
43
76
|
try {
|
|
@@ -56,21 +89,21 @@ async function acquireLock(lockPath, pidPath) {
|
|
|
56
89
|
}
|
|
57
90
|
}
|
|
58
91
|
}
|
|
59
|
-
function
|
|
92
|
+
function staleReason(info) {
|
|
60
93
|
if (info === void 0) {
|
|
61
|
-
return
|
|
94
|
+
return "no PID file (owner crashed before writing it)";
|
|
62
95
|
}
|
|
63
96
|
if (info.pid === process.pid) {
|
|
64
|
-
return info.token !== PROCESS_TOKEN;
|
|
97
|
+
return info.token !== PROCESS_TOKEN ? `PID ${info.pid} reused (token mismatch)` : void 0;
|
|
65
98
|
}
|
|
66
99
|
try {
|
|
67
100
|
process.kill(info.pid, 0);
|
|
68
|
-
return
|
|
101
|
+
return void 0;
|
|
69
102
|
} catch (error) {
|
|
70
103
|
if (error.code === "ESRCH") {
|
|
71
|
-
return
|
|
104
|
+
return `owner process ${info.pid} no longer exists`;
|
|
72
105
|
}
|
|
73
|
-
return
|
|
106
|
+
return void 0;
|
|
74
107
|
}
|
|
75
108
|
}
|
|
76
109
|
async function readLockInfo(pidPath) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/fs/lock-utils.ts"],
|
|
4
|
-
"mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAS,QAAQ,wBAAwB;AACzC,SAAS,mBAAmB;AAC5B,SAAS,QAAQ,QAAQ,UAAU,MAAM,QAAQ,iBAAiB;AAClE,SAAS,eAAe;AAExB,MAAM,SAAS,OAAO,IAAI,qBAAqB;AAE/C,MAAM,YAAY;AAClB,MAAM,WAAW;AAMjB,MAAM,gBAAgB,YAAY,CAAC,EAAE,SAAS,KAAK;
|
|
4
|
+
"mappings": "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",
|
|
5
5
|
"names": []
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matter/nodejs",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.4-alpha.0-20260621-81ba50a6a",
|
|
4
4
|
"description": "Node.js platform support for matter.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iot",
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"#*": "./src/*"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@matter/general": "0.17.
|
|
42
|
-
"@matter/node": "0.17.
|
|
43
|
-
"@matter/protocol": "0.17.
|
|
44
|
-
"@matter/types": "0.17.
|
|
41
|
+
"@matter/general": "0.17.4-alpha.0-20260621-81ba50a6a",
|
|
42
|
+
"@matter/node": "0.17.4-alpha.0-20260621-81ba50a6a",
|
|
43
|
+
"@matter/protocol": "0.17.4-alpha.0-20260621-81ba50a6a",
|
|
44
|
+
"@matter/types": "0.17.4-alpha.0-20260621-81ba50a6a"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@matter/model": "0.17.
|
|
48
|
-
"@matter/protocol": "0.17.
|
|
49
|
-
"@matter/testing": "0.17.
|
|
50
|
-
"@project-chip/matter.js": "0.17.
|
|
47
|
+
"@matter/model": "0.17.4-alpha.0-20260621-81ba50a6a",
|
|
48
|
+
"@matter/protocol": "0.17.4-alpha.0-20260621-81ba50a6a",
|
|
49
|
+
"@matter/testing": "0.17.4-alpha.0-20260621-81ba50a6a",
|
|
50
|
+
"@project-chip/matter.js": "0.17.4-alpha.0-20260621-81ba50a6a",
|
|
51
51
|
"@types/bytebuffer": "^5.0.49"
|
|
52
52
|
},
|
|
53
53
|
"files": [
|
package/src/fs/lock-utils.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { Logger, StorageLockError } from "@matter/general";
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
9
|
+
import { unlinkSync } from "node:fs";
|
|
9
10
|
import { open as fsOpen, readFile, stat, unlink, writeFile } from "node:fs/promises";
|
|
10
11
|
import { resolve } from "node:path";
|
|
11
12
|
|
|
@@ -25,6 +26,52 @@ interface LockInfo {
|
|
|
25
26
|
token?: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
interface ActiveLock {
|
|
30
|
+
lockPath: string;
|
|
31
|
+
pidPath: string;
|
|
32
|
+
name: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Locks held by this process that have not yet been released. A `process.exit` backstop removes the files for any
|
|
37
|
+
* survivors so a caller that forgets to close storage does not orphan a lock that blocks the next startup.
|
|
38
|
+
*/
|
|
39
|
+
const activeLocks = new Set<ActiveLock>();
|
|
40
|
+
let exitHandlerInstalled = false;
|
|
41
|
+
|
|
42
|
+
function trackLock(lock: ActiveLock) {
|
|
43
|
+
activeLocks.add(lock);
|
|
44
|
+
if (!exitHandlerInstalled) {
|
|
45
|
+
exitHandlerInstalled = true;
|
|
46
|
+
// 'exit' is synchronous-only, so cleanup is limited to unlinking the lock files; it cannot flush storage.
|
|
47
|
+
process.on("exit", removeOrphanedLocks);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function removeOrphanedLocks() {
|
|
52
|
+
for (const { lockPath, pidPath, name } of activeLocks) {
|
|
53
|
+
// Cleanup is the critical action; logging is advisory and must not prevent it if the logger throws
|
|
54
|
+
try {
|
|
55
|
+
logger.warn(
|
|
56
|
+
`Storage "${name}" was not closed before process exit; removing orphaned lock.`,
|
|
57
|
+
"Please close any opened storage during shutdown.",
|
|
58
|
+
);
|
|
59
|
+
} catch {
|
|
60
|
+
// ignore
|
|
61
|
+
}
|
|
62
|
+
unlinkSyncSafe(lockPath);
|
|
63
|
+
unlinkSyncSafe(pidPath);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function unlinkSyncSafe(path: string) {
|
|
68
|
+
try {
|
|
69
|
+
unlinkSync(path);
|
|
70
|
+
} catch {
|
|
71
|
+
// Best-effort cleanup during exit
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
28
75
|
/**
|
|
29
76
|
* Acquire an exclusive lock on a directory using O_EXCL and a PID file for stale-lock detection.
|
|
30
77
|
*
|
|
@@ -40,19 +87,25 @@ export async function acquireDirectoryLock(dirPath: string, dirName: string): Pr
|
|
|
40
87
|
return async () => {};
|
|
41
88
|
}
|
|
42
89
|
|
|
43
|
-
await acquireLock(lockPath, pidPath);
|
|
90
|
+
await acquireLock(lockPath, pidPath, dirName);
|
|
44
91
|
await writeFile(pidPath, `${process.pid} ${PROCESS_TOKEN}`);
|
|
45
92
|
|
|
93
|
+
const tracked: ActiveLock = { lockPath, pidPath, name: dirName };
|
|
94
|
+
trackLock(tracked);
|
|
95
|
+
|
|
46
96
|
logger.debug("Acquired storage lock for", dirName, "pid", process.pid);
|
|
47
97
|
|
|
48
98
|
return async () => {
|
|
49
|
-
|
|
99
|
+
// Remove the lock (the gate) first: if the second unlink fails, a leftover pid file is harmless (the next
|
|
100
|
+
// start reacquires and overwrites it), whereas a leftover lock file would force stale detection
|
|
50
101
|
await safeUnlink(lockPath);
|
|
102
|
+
await safeUnlink(pidPath);
|
|
103
|
+
activeLocks.delete(tracked);
|
|
51
104
|
logger.debug("Released storage lock for", dirName);
|
|
52
105
|
};
|
|
53
106
|
}
|
|
54
107
|
|
|
55
|
-
async function acquireLock(lockPath: string, pidPath: string) {
|
|
108
|
+
async function acquireLock(lockPath: string, pidPath: string, dirName: string) {
|
|
56
109
|
try {
|
|
57
110
|
const fd = await fsOpen(lockPath, "wx");
|
|
58
111
|
await fd.close();
|
|
@@ -63,9 +116,10 @@ async function acquireLock(lockPath: string, pidPath: string) {
|
|
|
63
116
|
|
|
64
117
|
// Lock file exists — check if the owning process is still alive
|
|
65
118
|
const info = await readLockInfo(pidPath);
|
|
119
|
+
const reason = staleReason(info);
|
|
66
120
|
|
|
67
|
-
if (
|
|
68
|
-
logger.info("Cleaning stale storage lock");
|
|
121
|
+
if (reason !== undefined) {
|
|
122
|
+
logger.info("Cleaning stale storage lock for", dirName, "-", reason);
|
|
69
123
|
await safeUnlink(pidPath);
|
|
70
124
|
await safeUnlink(lockPath);
|
|
71
125
|
|
|
@@ -88,34 +142,30 @@ async function acquireLock(lockPath: string, pidPath: string) {
|
|
|
88
142
|
}
|
|
89
143
|
|
|
90
144
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* - There is no PID file (crash between lock creation and PID write)
|
|
94
|
-
* - The owning process no longer exists
|
|
95
|
-
* - The PID matches ours but the token differs (PID reuse, e.g. Docker container restart)
|
|
145
|
+
* Returns why a lock is stale, or `undefined` if it is held by a live owner. The reason is logged so the cause of a
|
|
146
|
+
* reclaim (and any failure to release) is visible in the storage logs.
|
|
96
147
|
*/
|
|
97
|
-
function
|
|
148
|
+
function staleReason(info: LockInfo | undefined): string | undefined {
|
|
98
149
|
if (info === undefined) {
|
|
99
|
-
return
|
|
150
|
+
return "no PID file (owner crashed before writing it)";
|
|
100
151
|
}
|
|
101
152
|
|
|
102
153
|
if (info.pid === process.pid) {
|
|
103
154
|
// Same PID — check whether it's actually us via the token. If the token matches, the lock is held by another
|
|
104
155
|
// call site in this process. If it differs (or is missing from an old-format file), the PID was reused.
|
|
105
|
-
return info.token !== PROCESS_TOKEN;
|
|
156
|
+
return info.token !== PROCESS_TOKEN ? `PID ${info.pid} reused (token mismatch)` : undefined;
|
|
106
157
|
}
|
|
107
158
|
|
|
108
159
|
try {
|
|
109
160
|
process.kill(info.pid, 0);
|
|
110
|
-
// Process exists and we have permission to signal it — lock is not stale
|
|
111
|
-
return
|
|
161
|
+
// Process exists and we have permission to signal it (or EPERM below) — lock is not stale
|
|
162
|
+
return undefined;
|
|
112
163
|
} catch (error) {
|
|
113
164
|
if ((error as NodeJS.ErrnoException).code === "ESRCH") {
|
|
114
|
-
|
|
115
|
-
return true;
|
|
165
|
+
return `owner process ${info.pid} no longer exists`;
|
|
116
166
|
}
|
|
117
167
|
// EPERM — process exists but we can't signal it — lock is not stale
|
|
118
|
-
return
|
|
168
|
+
return undefined;
|
|
119
169
|
}
|
|
120
170
|
}
|
|
121
171
|
|