@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.
@@ -1 +1 @@
1
- {"version":3,"file":"lock-utils.d.ts","sourceRoot":"","sources":["../../../src/fs/lock-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAuBH;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAoBzG"}
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
- if (isStale(info)) {
63
- logger.info("Cleaning stale storage lock");
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 isStale(info) {
115
+ function staleReason(info) {
83
116
  if (info === void 0) {
84
- return true;
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 false;
124
+ return void 0;
92
125
  } catch (error) {
93
126
  if (error.code === "ESRCH") {
94
- return true;
127
+ return `owner process ${info.pid} no longer exists`;
95
128
  }
96
- return false;
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": "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",
4
+ "mappings": "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",
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;AAuBH;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAoBzG"}
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
- if (isStale(info)) {
40
- logger.info("Cleaning stale storage lock");
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 isStale(info) {
92
+ function staleReason(info) {
60
93
  if (info === void 0) {
61
- return true;
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 false;
101
+ return void 0;
69
102
  } catch (error) {
70
103
  if (error.code === "ESRCH") {
71
- return true;
104
+ return `owner process ${info.pid} no longer exists`;
72
105
  }
73
- return false;
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": "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",
4
+ "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAS,QAAQ,wBAAwB;AACzC,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,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;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,eAAW,IAAI;AAAA,EACnB,QAAQ;AAAA,EAER;AACJ;AAOA,eAAsB,qBAAqB,SAAiB,SAA+C;AACvG,QAAM,WAAW,QAAQ,SAAS,SAAS;AAC3C,QAAM,UAAU,QAAQ,SAAS,QAAQ;AAIzC,MAAI,CAAE,MAAM,UAAU,OAAO,GAAI;AAC7B,WAAO,YAAY;AAAA,IAAC;AAAA,EACxB;AAEA,QAAM,YAAY,UAAU,SAAS,OAAO;AAC5C,QAAM,UAAU,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,MAAM,OAAO,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,MAAM,OAAO,UAAU,IAAI;AACtC,cAAM,GAAG,MAAM;AAAA,MACnB,SAAS,YAAY;AACjB,YAAK,WAAqC,SAAS,UAAU;AACzD,gBAAM,IAAI,iBAAiB,oEAAoE;AAAA,QACnG;AACA,cAAM;AAAA,MACV;AAAA,IACJ,WAAW,MAAM,QAAQ,QAAQ,KAAK;AAClC,YAAM,IAAI,iBAAiB,2CAA2C;AAAA,IAC1E,OAAO;AACH,YAAM,IAAI,iBAAiB,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,MAAM,SAAS,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,MAAM,KAAK,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,UAAM,OAAO,IAAI;AAAA,EACrB,SAAS,OAAO;AACZ,QAAK,MAAgC,SAAS,UAAU;AACpD,YAAM;AAAA,IACV;AAAA,EACJ;AACJ;",
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",
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.3",
42
- "@matter/node": "0.17.3",
43
- "@matter/protocol": "0.17.3",
44
- "@matter/types": "0.17.3"
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.3",
48
- "@matter/protocol": "0.17.3",
49
- "@matter/testing": "0.17.3",
50
- "@project-chip/matter.js": "0.17.3",
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": [
@@ -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
- await safeUnlink(pidPath);
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 (isStale(info)) {
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
- * A lock is stale if:
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 isStale(info: LockInfo | undefined): boolean {
148
+ function staleReason(info: LockInfo | undefined): string | undefined {
98
149
  if (info === undefined) {
99
- return true;
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 false;
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
- // Process does not exist lock is stale
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 false;
168
+ return undefined;
119
169
  }
120
170
  }
121
171