@nowcrew/daemon 0.6.91-beta.3 → 0.6.91-beta.5
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,3 +1,4 @@
|
|
|
1
|
+
import { dslog } from "./slog.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { isDaemonReleaseVersion } from "./daemon-release-version.js";
|
|
3
4
|
const DaemonUpdateMessageSchema = z.object({
|
|
@@ -42,15 +43,16 @@ export function createDaemonUpdateController(deps) {
|
|
|
42
43
|
failed(message.updateId, preparation.errorCode);
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
|
-
let
|
|
46
|
-
const releaseOnce =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
let releaseTask;
|
|
47
|
+
const releaseOnce = () => {
|
|
48
|
+
releaseTask ??= preparation.release().catch(error => {
|
|
49
|
+
releaseTask = undefined;
|
|
50
|
+
throw error;
|
|
51
|
+
});
|
|
52
|
+
return releaseTask;
|
|
51
53
|
};
|
|
52
|
-
deps.sendStatus({ type: "daemon:update-status", updateId: message.updateId, status: "restarting" });
|
|
53
54
|
try {
|
|
55
|
+
deps.sendStatus({ type: "daemon:update-status", updateId: message.updateId, status: "restarting" });
|
|
54
56
|
const handoff = deps.scheduleRestart(eligibility.serviceSpec);
|
|
55
57
|
const state = {
|
|
56
58
|
release: releaseOnce,
|
|
@@ -92,7 +94,13 @@ export function createDaemonUpdateController(deps) {
|
|
|
92
94
|
failed(message.updateId, "update_in_progress");
|
|
93
95
|
return true;
|
|
94
96
|
}
|
|
95
|
-
const done = execute(message).
|
|
97
|
+
const done = execute(message).catch(error => {
|
|
98
|
+
dslog("daemon.update_failed", "Daemon 更新或重启准备失败", {
|
|
99
|
+
level: "ERROR", update_id: message.updateId, operation: message.type,
|
|
100
|
+
error_message: error instanceof Error ? error.message : String(error),
|
|
101
|
+
});
|
|
102
|
+
failed(message.updateId, message.type === "daemon:restart" ? "restart_failed" : "install_failed");
|
|
103
|
+
}).finally(() => {
|
|
96
104
|
handled.add(message.updateId);
|
|
97
105
|
if (running?.id === message.updateId)
|
|
98
106
|
running = null;
|
package/dist/daemon-updater.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dslog } from "./slog.js";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { systemCommandRunner } from "./computer-service.js";
|
|
@@ -17,6 +18,8 @@ export async function prepareDaemonRestart(input) {
|
|
|
17
18
|
const local = input.localSlots.tryAcquireExclusive();
|
|
18
19
|
if (local === null)
|
|
19
20
|
return { ok: false, errorCode: "runtime_busy" };
|
|
21
|
+
const acquiredAt = Date.now();
|
|
22
|
+
dslog("daemon.maintenance_barrier_acquired", "Daemon 已暂停接收执行,准备维护");
|
|
20
23
|
let released = false;
|
|
21
24
|
return {
|
|
22
25
|
ok: true,
|
|
@@ -25,6 +28,7 @@ export async function prepareDaemonRestart(input) {
|
|
|
25
28
|
return;
|
|
26
29
|
released = true;
|
|
27
30
|
local.release();
|
|
31
|
+
dslog("daemon.maintenance_barrier_released", "维护屏障已释放", { held_ms: Date.now() - acquiredAt });
|
|
28
32
|
},
|
|
29
33
|
};
|
|
30
34
|
}
|
|
@@ -67,16 +71,10 @@ export async function installExactDaemonUpdate(input) {
|
|
|
67
71
|
|| installation.npmPrefix !== input.npmPrefix) {
|
|
68
72
|
return { ok: false, errorCode: "ineligible" };
|
|
69
73
|
}
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
const release = async () => {
|
|
75
|
-
if (released)
|
|
76
|
-
return;
|
|
77
|
-
released = true;
|
|
78
|
-
local.release();
|
|
79
|
-
};
|
|
74
|
+
const barrier = await prepareDaemonRestart(input);
|
|
75
|
+
if (!barrier.ok)
|
|
76
|
+
return barrier;
|
|
77
|
+
const release = barrier.release;
|
|
80
78
|
const lockedInstallation = daemonGlobalInstallation(input.currentEntryPath ?? process.argv[1] ?? "", input.platform ?? process.platform);
|
|
81
79
|
if (lockedInstallation === null
|
|
82
80
|
|| lockedInstallation.packageRoot !== input.packageRoot
|
|
@@ -286,13 +286,19 @@ export function createHostExecutionCoordinator(options = {}) {
|
|
|
286
286
|
...Array.from({ length: executionSlots }, (_, slot) => join(root, "slots", String(slot))),
|
|
287
287
|
join(root, "memory-prune-slots", "0"),
|
|
288
288
|
];
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
289
|
+
try {
|
|
290
|
+
for (const target of targets) {
|
|
291
|
+
const lease = await acquireLease(target);
|
|
292
|
+
if (lease === null) {
|
|
293
|
+
await retryRelease("exclusive_execution_partial", () => Promise.all(leases.map((owned) => owned.close())).then(() => undefined));
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
leases.push(lease);
|
|
294
297
|
}
|
|
295
|
-
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
await retryRelease("exclusive_execution_partial", () => Promise.all(leases.map(owned => owned.close())).then(() => undefined));
|
|
301
|
+
throw error;
|
|
296
302
|
}
|
|
297
303
|
let releasePromise = null;
|
|
298
304
|
return {
|
|
@@ -2,7 +2,7 @@ import { ExecutionCompletedSchema, ExecutionRejectedSchema } from "./execution-p
|
|
|
2
2
|
import { hashExecutionSpec } from "./execution-runner.js";
|
|
3
3
|
import { dslog } from "./slog.js";
|
|
4
4
|
/** Queue rejection is admission work too: own the ID before any asynchronous journal write. */
|
|
5
|
-
export async function rejectQueuedAdmission(context, spec, executionClass, facts) {
|
|
5
|
+
export async function rejectQueuedAdmission(context, spec, executionClass, facts, rejectionReason = "machine_queue_full") {
|
|
6
6
|
const id = spec.executionId;
|
|
7
7
|
const hash = hashExecutionSpec(spec);
|
|
8
8
|
if (context.knownExecutionHashes.has(id) || context.executionRuns.has(id)) {
|
|
@@ -16,10 +16,11 @@ export async function rejectQueuedAdmission(context, spec, executionClass, facts
|
|
|
16
16
|
context.knownExecutionHashes.set(id, hash);
|
|
17
17
|
const rejected = ExecutionRejectedSchema.parse({
|
|
18
18
|
type: "execution:rejected", protocolVersion: 1, executionId: id,
|
|
19
|
-
reason: "resource_limit", message: "Local machine
|
|
19
|
+
reason: "resource_limit", message: rejectionReason === "maintenance" ? "Local machine is temporarily unavailable for maintenance"
|
|
20
|
+
: rejectionReason === "agent_queue_full" ? "Local agent execution queue is full" : "Local machine execution queue is full", at: new Date().toISOString(),
|
|
20
21
|
});
|
|
21
|
-
dslog("execution.machine_queue_rejected", "
|
|
22
|
-
level: "WARN", execution_id: id, agent_handle: spec.agent.handle, execution_class: executionClass, ...facts,
|
|
22
|
+
dslog("execution.machine_queue_rejected", rejectionReason === "maintenance" ? "机器维护期间暂停接收执行" : "执行等待队列已满", {
|
|
23
|
+
level: "WARN", execution_id: id, agent_handle: spec.agent.handle, execution_class: executionClass, admission_reason: rejectionReason, ...facts,
|
|
23
24
|
});
|
|
24
25
|
const rejecting = persistPreAdmissionRejection(context.journal, spec, rejected)
|
|
25
26
|
.then(async (entry) => {
|
package/dist/serve.js
CHANGED
|
@@ -488,7 +488,7 @@ export function serve(config, opts = {}) {
|
|
|
488
488
|
let reservation = sharedSlots.reserve(spec.agent.handle, "execution", executionClass);
|
|
489
489
|
if (!reservation.accepted) {
|
|
490
490
|
await rejectQueuedAdmission({ journal: executionJournal, knownExecutionHashes, executionRuns,
|
|
491
|
-
report: reportExecutionFrame, replay: sendJournalStatus }, spec, executionClass, reservation.facts);
|
|
491
|
+
report: reportExecutionFrame, replay: sendJournalStatus }, spec, executionClass, reservation.facts, reservation.rejectionReason);
|
|
492
492
|
return;
|
|
493
493
|
}
|
|
494
494
|
knownExecutionHashes.set(spec.executionId, hash);
|
|
@@ -59,6 +59,7 @@ export function createSharedSlotManager(limits) {
|
|
|
59
59
|
if (exclusive) {
|
|
60
60
|
return {
|
|
61
61
|
accepted: false,
|
|
62
|
+
rejectionReason: "maintenance",
|
|
62
63
|
facts,
|
|
63
64
|
ready: Promise.resolve(),
|
|
64
65
|
isQueued: () => false,
|
|
@@ -77,6 +78,7 @@ export function createSharedSlotManager(limits) {
|
|
|
77
78
|
|| laneQueuedTotal >= limits.maxQueuedTotal)) {
|
|
78
79
|
return {
|
|
79
80
|
accepted: false,
|
|
81
|
+
rejectionReason: queuedForAgent >= limits.maxQueuedPerAgent ? "agent_queue_full" : "machine_queue_full",
|
|
80
82
|
facts,
|
|
81
83
|
ready: Promise.resolve(),
|
|
82
84
|
isQueued: () => false,
|