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
//! Periodic reaper for the observe-only agent registry (`~/.car/registry/*.json`).
//!
//! Agents self-register and heartbeat into `car_registry::AgentRegistry`; one
//! that dies without calling `registry.unregister` leaves its entry behind.
//! Every *reader* already filters by heartbeat freshness
//! (`REGISTRY_STALE_AFTER_SECS`), so a stale entry never changes behavior — but
//! nothing ever deleted the files, so they accumulated indefinitely (a solver
//! agent that stopped ~16 days earlier was still on disk). `reap_stale` was
//! built for exactly this and documented as "call from the menubar every ~30s",
//! yet no scheduled caller was ever wired — headless or otherwise.
//!
//! This is that caller, daemon-side so it runs even with no menubar. Reaping is
//! safe: the threshold sits far beyond any live agent's heartbeat interval;
//! `reap_stale` re-reads each entry immediately before the unlink, so an agent
//! that beat again after the snapshot is kept (it does NOT rely on a reaped
//! agent re-registering — `heartbeat` won't recreate a deleted file); and
//! readers ignore anything past the freshness window regardless — so a reap only
//! ever deletes a file a reader was already treating as absent. Note this
//! touches only the observe-only self-report registry; the supervised-agent list
//! (`agents.list`, what CarHost renders) is a separate source and untouched.
//!
//! Mirrors [`crate::spawn_command_scheduler`]: one non-overlapping task that
//! dies with the runtime.
use Duration;
/// How often the reaper wakes. One small directory read, so a modest cadence
/// keeps the directory clean at negligible cost.
const REAP_POLL_SECS: u64 = 300;
/// Delete registry entries whose last heartbeat is older than this. Set well
/// above the consumer freshness window (`REGISTRY_STALE_AFTER_SECS = 60`): a
/// live agent that briefly missed a beat is never reaped out from under a reader
/// — it would already read as stale, but this avoids needlessly churning the
/// file. An entry idle this long is definitively dead.
const REAP_MAX_AGE_SECS: u64 = 900;
/// Spawn the observe-only registry reaper. Call once at boot; the task lives for
/// the process lifetime. Best-effort — a read/delete failure is logged and the
/// next tick retries. The interval's first tick fires immediately, so this also
/// serves as a boot-time sweep of entries orphaned while the daemon was down.