@xiaohhhh1/canvas-agent 0.4.8 → 0.4.9
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,4 +1,5 @@
|
|
|
1
1
|
export declare const AGENT_REPLACED_EXIT_CODE = 75;
|
|
2
2
|
export declare function shouldRestartAgent(code: number | null, stopping: boolean): boolean;
|
|
3
|
+
export declare function shouldReplaceSupervisorLock(ownerPid: number, ageMs: number, ownerAlive: boolean): boolean;
|
|
3
4
|
/** Keep the local HTTP worker alive, but step aside when a newer package replaces it. */
|
|
4
5
|
export declare function startHttpSupervisor(): void;
|
|
@@ -1,17 +1,112 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { CONFIG_DIR } from "../config.js";
|
|
2
5
|
export const AGENT_REPLACED_EXIT_CODE = 75;
|
|
3
6
|
const RESTART_DELAY_MS = 1000;
|
|
7
|
+
const SUPERVISOR_LOCK_FILE = path.join(CONFIG_DIR, "canvas-agent-watch.lock");
|
|
8
|
+
const SUPERVISOR_HEARTBEAT_MS = 5000;
|
|
9
|
+
const SUPERVISOR_LOCK_STALE_MS = 15000;
|
|
10
|
+
const SUPERVISOR_TAKEOVER_WAIT_MS = 6000;
|
|
11
|
+
const SUPERVISOR_TAKEOVER_RETRY_MS = 100;
|
|
4
12
|
export function shouldRestartAgent(code, stopping) {
|
|
5
13
|
return !stopping && code !== AGENT_REPLACED_EXIT_CODE;
|
|
6
14
|
}
|
|
15
|
+
export function shouldReplaceSupervisorLock(ownerPid, ageMs, ownerAlive) {
|
|
16
|
+
return !Number.isInteger(ownerPid) || ownerPid <= 0 || !ownerAlive || ageMs > SUPERVISOR_LOCK_STALE_MS;
|
|
17
|
+
}
|
|
18
|
+
function processIsAlive(pid) {
|
|
19
|
+
try {
|
|
20
|
+
process.kill(pid, 0);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function sleepSync(delayMs) {
|
|
28
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMs);
|
|
29
|
+
}
|
|
30
|
+
function acquireSupervisorLock() {
|
|
31
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
32
|
+
const deadline = Date.now() + SUPERVISOR_TAKEOVER_WAIT_MS;
|
|
33
|
+
while (Date.now() <= deadline) {
|
|
34
|
+
let descriptor;
|
|
35
|
+
try {
|
|
36
|
+
descriptor = fs.openSync(SUPERVISOR_LOCK_FILE, "wx");
|
|
37
|
+
fs.writeFileSync(descriptor, String(process.pid));
|
|
38
|
+
return descriptor;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (descriptor !== undefined) {
|
|
42
|
+
try {
|
|
43
|
+
fs.closeSync(descriptor);
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
try {
|
|
47
|
+
fs.unlinkSync(SUPERVISOR_LOCK_FILE);
|
|
48
|
+
}
|
|
49
|
+
catch { }
|
|
50
|
+
}
|
|
51
|
+
if (error.code !== "EEXIST")
|
|
52
|
+
throw error;
|
|
53
|
+
try {
|
|
54
|
+
const ownerPid = Number(fs.readFileSync(SUPERVISOR_LOCK_FILE, "utf8").trim());
|
|
55
|
+
const ageMs = Date.now() - fs.statSync(SUPERVISOR_LOCK_FILE).mtimeMs;
|
|
56
|
+
if (shouldReplaceSupervisorLock(ownerPid, ageMs, processIsAlive(ownerPid))) {
|
|
57
|
+
fs.unlinkSync(SUPERVISOR_LOCK_FILE);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (readError) {
|
|
62
|
+
if (readError.code !== "ENOENT") {
|
|
63
|
+
try {
|
|
64
|
+
fs.unlinkSync(SUPERVISOR_LOCK_FILE);
|
|
65
|
+
}
|
|
66
|
+
catch { }
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
sleepSync(SUPERVISOR_TAKEOVER_RETRY_MS);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
function releaseSupervisorLock(descriptor) {
|
|
76
|
+
try {
|
|
77
|
+
fs.closeSync(descriptor);
|
|
78
|
+
}
|
|
79
|
+
catch { }
|
|
80
|
+
try {
|
|
81
|
+
if (fs.readFileSync(SUPERVISOR_LOCK_FILE, "utf8").trim() === String(process.pid)) {
|
|
82
|
+
fs.unlinkSync(SUPERVISOR_LOCK_FILE);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
7
87
|
/** Keep the local HTTP worker alive, but step aside when a newer package replaces it. */
|
|
8
88
|
export function startHttpSupervisor() {
|
|
9
89
|
const entry = process.argv[1];
|
|
10
90
|
if (!entry)
|
|
11
91
|
throw new Error("无法定位 Canvas Agent 启动文件");
|
|
92
|
+
const lockDescriptor = acquireSupervisorLock();
|
|
93
|
+
if (lockDescriptor === undefined)
|
|
94
|
+
return;
|
|
12
95
|
let child;
|
|
13
96
|
let stopping = false;
|
|
14
97
|
let restartTimer;
|
|
98
|
+
const lockHeartbeat = setInterval(() => {
|
|
99
|
+
try {
|
|
100
|
+
fs.utimesSync(SUPERVISOR_LOCK_FILE, new Date(), new Date());
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
103
|
+
}, SUPERVISOR_HEARTBEAT_MS);
|
|
104
|
+
lockHeartbeat.unref();
|
|
105
|
+
const exit = () => {
|
|
106
|
+
clearInterval(lockHeartbeat);
|
|
107
|
+
releaseSupervisorLock(lockDescriptor);
|
|
108
|
+
process.exit(0);
|
|
109
|
+
};
|
|
15
110
|
const launch = () => {
|
|
16
111
|
if (stopping)
|
|
17
112
|
return;
|
|
@@ -19,7 +114,7 @@ export function startHttpSupervisor() {
|
|
|
19
114
|
child.once("exit", (code) => {
|
|
20
115
|
child = undefined;
|
|
21
116
|
if (!shouldRestartAgent(code, stopping))
|
|
22
|
-
return
|
|
117
|
+
return exit();
|
|
23
118
|
restartTimer = setTimeout(launch, RESTART_DELAY_MS);
|
|
24
119
|
});
|
|
25
120
|
};
|
|
@@ -30,7 +125,7 @@ export function startHttpSupervisor() {
|
|
|
30
125
|
if (restartTimer)
|
|
31
126
|
clearTimeout(restartTimer);
|
|
32
127
|
child?.kill();
|
|
33
|
-
setTimeout(
|
|
128
|
+
setTimeout(exit, 2000).unref();
|
|
34
129
|
};
|
|
35
130
|
process.once("SIGINT", stop);
|
|
36
131
|
process.once("SIGTERM", stop);
|