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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Background ticker tasks: status, disk-size, and idle-chunk-eviction.
//!
//! Why: Separating long-running background spawns from handler code keeps
//! the handler files focused on request/response logic.
//! What: Three `pub(super) spawn_*_ticker` functions, each detached as a
//! `tokio::spawn` task holding a `Weak<SearchAppState>`.
//! Test: covered indirectly via handler tests that observe side-effects.
use Arc;
use Duration;
use collect_status_counts;
use ;
/// Spawn a background ticker that emits `StatusChanged` every 2 seconds.
///
/// Why: trusty-memory's pattern is push-driven via mutating handlers, but
/// trusty-search's headline stats (chunk count) change continuously during
/// reindex without a discrete event. A 2s ticker keeps the dashboard's
/// stat cards live (same cadence as the previous poll-based implementation)
/// while still routing through the broadcast channel so the SSE handler
/// stays purely subscription-driven.
/// What: Spawns a detached tokio task holding a `Weak<SearchAppState>` so
/// the ticker terminates automatically when the daemon shuts down (drops the
/// last `Arc`). Each tick recomputes counts and emits one event.
/// Test: subscribe to `/status/stream`, wait > 2s, observe a `status_changed`
/// frame.
pub
/// Spawn a background ticker that recomputes the data-directory size every
/// 10 seconds and stores it in `state.disk_bytes`.
///
/// Why (issue #35): `GET /health` reports `disk_bytes`. Walking the data
/// directory (redb + usearch + snapshot files) on every health request would
/// turn a 2 s health poll into unbounded recursive I/O. Computing it off the
/// request path on a fixed cadence keeps `/health` cheap and bounds the
/// staleness to ~10 s — fine for an at-a-glance footprint figure.
/// What: spawns a detached tokio task holding a `Weak<SearchAppState>` so the
/// ticker stops automatically when the daemon drops its last `Arc`. Each tick
/// runs the (blocking) directory walk on `spawn_blocking` so it never stalls
/// the async runtime, then stores the byte total atomically.
/// Test: covered indirectly — `health_includes_resource_fields` asserts the
/// `disk_bytes` field is present and non-negative.
pub
/// Spawn a background ticker that evicts each index's in-memory `chunks` map
/// after it has been idle past the configured window (issue #83 follow-up).
///
/// Why (idle-memory audit): the durable redb corpus already serves the query
/// hot path, so an index that hasn't been queried or ingested for a while is
/// holding hundreds of MB of `RawChunk` text in the process heap for nothing.
/// `CodeIndexer::evict_chunks_if_idle` reclaims that heap and lazily rehydrates
/// from redb on the next access; this ticker is what drives it on a fixed
/// cadence across every registered index. It mirrors the `spawn_*_ticker`
/// pattern: a detached task holding a `Weak<SearchAppState>` so it stops when
/// the daemon drops its last `Arc`.
/// What: every 60 s, resolves the idle window via
/// `crate::core::indexer::idle_evict_secs()` (env-overridable;
/// `0` disables eviction and the ticker idles), then walks the registry and
/// calls `evict_chunks_if_idle` on each indexer. The per-indexer call is itself
/// a no-op for active indexes, indexes without a durable corpus, and
/// already-empty maps, so the walk is cheap. The eviction acquires each
/// indexer's read lock only to check `corpus`/idle state and a brief write lock
/// only when it actually clears the map.
/// Test: `idle_eviction_drops_and_lazily_rehydrates_chunks` covers the
/// per-indexer logic directly; the ticker is a thin scheduling wrapper.
pub