Repository navigation
Expand file tree
/
Copy pathdaemon-stop.ts
More file actions
127 lines (119 loc) · 4.56 KB
/
Copy pathdaemon-stop.ts
File metadata and controls
127 lines (119 loc) · 4.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { AppError } from '@agent-device/kernel/errors';
import { stopAndRetireDaemon, type DaemonRetirementResult } from '../daemon-registration-owner.ts';
import type { OwnerIdentity } from '@agent-device/host-kit/process';
import type { DaemonPaths } from '../daemon-resolution.ts';
import { readRegisteredDaemonIdentity } from '../daemon-registration.ts';
import type { DeviceClaimRecord, ProviderReleaseRecord } from '../daemon-shutdown-report.ts';
const DAEMON_STOP_GRACE_TIMEOUT_MS = 10_000;
const DAEMON_STOP_KILL_TIMEOUT_MS = 2_000;
export type DaemonStopResult = {
stopped: boolean;
mode: 'graceful' | 'forced' | 'not-running';
cleanupConfidence: 'known' | 'unknown';
/**
* #1320 claim results. Only a graceful stop can carry values: they come from
* the shutdown report the exiting daemon wrote, so a forced kill or a daemon
* that was not running reports none rather than claiming certainty.
*/
claimsReleased: DeviceClaimRecord[];
claimsOrphaned: DeviceClaimRecord[];
/** Claims another owner had already taken over; this daemon released nothing. */
claimsSuperseded: DeviceClaimRecord[];
/**
* Claims whose on-disk record named no owner, so this daemon cannot say whether the device is
* still held. `device release --stale` cannot settle these — it proves staleness from a recorded
* owner — so they are reported apart from {@link DaemonStopResult.claimsOrphaned}.
*/
claimsUnattributable: DeviceClaimRecord[];
providerReleases: {
status: 'completed' | 'unknown';
released: ProviderReleaseRecord[];
pending: ProviderReleaseRecord[] | null;
};
warnings: string[];
};
export async function stopDaemon(params: {
paths: DaemonPaths;
graceTimeoutMs?: number;
killTimeoutMs?: number;
}): Promise<DaemonStopResult> {
const info = readRegisteredDaemonIdentity(params.paths.infoPath);
if (!info) return notRunningResult();
const retirement = await stopAndRetireDaemon({
paths: params.paths,
observed: info,
mode: 'graceful',
termTimeoutMs: params.graceTimeoutMs ?? DAEMON_STOP_GRACE_TIMEOUT_MS,
killTimeoutMs: params.killTimeoutMs ?? DAEMON_STOP_KILL_TIMEOUT_MS,
});
if (retirement.status === 'retained' && retirement.termination?.status === 'not-running')
return notRunningResult();
if (retirement.status !== 'retired') throw daemonRetirementError(info, retirement);
if (retirement.termination.mode === 'already-exited') return notRunningResult();
if (retirement.termination.mode === 'graceful') {
return {
stopped: true,
mode: 'graceful',
cleanupConfidence: 'known',
claimsReleased: [],
claimsOrphaned: [],
claimsSuperseded: [],
claimsUnattributable: [],
providerReleases: { status: 'completed', released: [], pending: [] },
warnings: [],
};
}
return {
stopped: true,
mode: 'forced',
cleanupConfidence: 'unknown',
claimsReleased: [],
claimsOrphaned: [],
claimsSuperseded: [],
claimsUnattributable: [],
providerReleases: { status: 'unknown', released: [], pending: null },
warnings: [
'The daemon was force-killed before provider lease state could be finalized. Provider allocations may remain active.',
],
};
}
function daemonRetirementError(
info: OwnerIdentity,
retirement: Exclude<DaemonRetirementResult, { status: 'retired' }>,
): AppError {
const termination = retirement.status === 'retained' ? retirement.termination : undefined;
const failure = termination?.status === 'retained' ? termination : undefined;
const error = retirement.status === 'retained' ? retirement.error : undefined;
const { hint, diagnosticId, logPath } = error ?? {};
return new AppError('COMMAND_FAILED', 'Daemon retirement could not be confirmed.', {
pid: info.pid,
processStartTime: info.startTime,
reason: failure ? 'daemon_exit_unconfirmed' : 'daemon_retirement_unconfirmed',
terminationReason: failure?.reason,
signal: failure?.signal,
retirement,
hint,
diagnosticId,
logPath,
});
}
export function readDaemonStopIdentity(
infoPath: string,
): { pid: number; processStartTime: string } | null {
const info = readRegisteredDaemonIdentity(infoPath);
if (!info?.startTime) return null;
return { pid: info.pid, processStartTime: info.startTime };
}
function notRunningResult(): DaemonStopResult {
return {
stopped: false,
mode: 'not-running',
cleanupConfidence: 'known',
claimsReleased: [],
claimsOrphaned: [],
claimsSuperseded: [],
claimsUnattributable: [],
providerReleases: { status: 'completed', released: [], pending: [] },
warnings: [],
};
}