total-recall-brain 3.23.0 → 3.23.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/package.json
CHANGED
package/src/core/daemon-loop.mjs
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import fs from 'fs';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { readProcessCommand, entryPathHint, shouldHonorPidLock } from './pid-lock.mjs';
|
|
16
18
|
import { createScheduler, updateTaskStatus, persistTaskToDisk } from './scheduler.mjs';
|
|
17
19
|
import { scanAndIngest } from './session-watcher.mjs';
|
|
18
20
|
import { syncAllRepos } from './repo-sync.mjs';
|
|
@@ -111,20 +113,36 @@ export function acquirePidLock() {
|
|
|
111
113
|
if (fs.existsSync(PID_FILE)) {
|
|
112
114
|
const existingPid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
|
|
113
115
|
if (existingPid && !isNaN(existingPid)) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
// Liveness alone cannot tell our daemon from whatever else inherited
|
|
117
|
+
// the number after a reboot — observed here holding a PID that had
|
|
118
|
+
// since been handed to an unrelated desktop app, which would refuse
|
|
119
|
+
// every start from then on.
|
|
120
|
+
const verdict = shouldHonorPidLock(existingPid, {
|
|
121
|
+
isAlive: (pid) => {
|
|
122
|
+
try {
|
|
123
|
+
process.kill(pid, 0); // signal 0 = no-op liveness check
|
|
124
|
+
return true;
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
readCommand: readProcessCommand,
|
|
130
|
+
entryHint: entryPathHint(fileURLToPath(import.meta.url)),
|
|
131
|
+
});
|
|
132
|
+
if (verdict.honor) {
|
|
116
133
|
logger.error({
|
|
117
134
|
subsystem: 'daemon-loop',
|
|
118
135
|
message: `Another daemon is already running (PID: ${existingPid}). Exiting.`,
|
|
119
136
|
});
|
|
120
137
|
process.exit(1);
|
|
121
|
-
} catch {
|
|
122
|
-
// Process is dead — stale PID file
|
|
123
|
-
logger.info({
|
|
124
|
-
subsystem: 'daemon-loop',
|
|
125
|
-
message: `Stale PID file found (PID: ${existingPid} is dead). Overwriting.`,
|
|
126
|
-
});
|
|
127
138
|
}
|
|
139
|
+
logger.info({
|
|
140
|
+
subsystem: 'daemon-loop',
|
|
141
|
+
message:
|
|
142
|
+
verdict.reason === 'pid-reused'
|
|
143
|
+
? `Stale PID file found (PID ${existingPid} now belongs to another program). Overwriting.`
|
|
144
|
+
: `Stale PID file found (PID: ${existingPid} is dead). Overwriting.`,
|
|
145
|
+
});
|
|
128
146
|
}
|
|
129
147
|
}
|
|
130
148
|
} catch {
|
|
@@ -11,19 +11,20 @@
|
|
|
11
11
|
* already running", and it did so on every boot from then on — an outage with
|
|
12
12
|
* an error message that sent you looking for a process that was never there.
|
|
13
13
|
*
|
|
14
|
-
* So liveness is necessary but not sufficient: the process has to look like
|
|
15
|
-
*
|
|
14
|
+
* So liveness is necessary but not sufficient: the process has to look like the
|
|
15
|
+
* program that wrote the lock before the lock is honored. Shared by the server
|
|
16
|
+
* and the daemon, which had the same check and therefore the same bug.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import { spawnSync } from 'node:child_process';
|
|
19
20
|
import path from 'node:path';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
* The tail of
|
|
23
|
-
*
|
|
24
|
-
* hardcodes
|
|
23
|
+
* The tail of an entry path (`server/index.mjs`, `core/daemon-loop.mjs`),
|
|
24
|
+
* derived from the caller's own module URL rather than written down, so it stays
|
|
25
|
+
* correct wherever the package is installed and hardcodes no checkout location.
|
|
25
26
|
*/
|
|
26
|
-
export function
|
|
27
|
+
export function entryPathHint(entryPath) {
|
|
27
28
|
return path.join(path.basename(path.dirname(entryPath)), path.basename(entryPath));
|
|
28
29
|
}
|
|
29
30
|
|
package/src/server/index.mjs
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
requireScope
|
|
29
29
|
} from './auth.mjs';
|
|
30
30
|
import { logger, logEvents } from "../core/logger.mjs";
|
|
31
|
-
import { readProcessCommand,
|
|
31
|
+
import { readProcessCommand, entryPathHint, shouldHonorPidLock } from '../core/pid-lock.mjs';
|
|
32
32
|
import { drainActiveEmbeddings } from './routes/sessions.mjs';
|
|
33
33
|
import { agentDir as configAgentDir, brainDir as configBrainDir, port as configPort, host as configHost, nodeEnv } from '../core/config.mjs';
|
|
34
34
|
import { getDaemonStatus, ensureDaemonRunning } from '../core/daemon-control.mjs';
|
|
@@ -69,7 +69,7 @@ function acquireServerPidLock() {
|
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
readCommand: readProcessCommand,
|
|
72
|
-
entryHint:
|
|
72
|
+
entryHint: entryPathHint(fileURLToPath(import.meta.url)),
|
|
73
73
|
});
|
|
74
74
|
if (verdict.honor) {
|
|
75
75
|
logger.error('server', `Another server instance is already running (PID: ${existingPid}). Exiting.`);
|