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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! Shared graceful-shutdown signal helper for trusty-* daemons.
//!
//! Why: trusty-search, trusty-memory, and trusty-analyze all need to wait for
//! SIGTERM (launchd `bootout`, `kill <pid>`) or SIGINT (Ctrl-C in dev) before
//! cleanly draining in-flight HTTP requests. Centralising the implementation
//! removes three-way duplication and ensures every daemon responds identically
//! to the same signals.
//!
//! What: exposes a single async `shutdown_signal()` function that returns once
//! EITHER SIGTERM (unix) OR SIGINT/Ctrl-C (all platforms) fires. On non-unix
//! platforms only Ctrl-C is watched.
//!
//! Test: `cargo test -p trusty-common -- shutdown` runs the compilation smoke
//! test. Signal delivery itself cannot be triggered inside a unit test without
//! `raise(SIGTERM)`, which is unsafe; the integration tests in trusty-search
//! exercise the full axum `with_graceful_shutdown` path.
/// Seconds a trusty-* daemon is granted between SIGTERM and SIGKILL (#4393).
///
/// Why: this is the ONE number every terminator and every terminated daemon has
/// to agree on, and before #4393 nobody stated it. launchd's `ExitTimeOut`
/// default is documented only as "system-defined" and measures **5 s** on
/// macOS; `trusty-search stop` allowed 5 s; its orphan reaper allowed 3 s.
/// Meanwhile trusty-search's shutdown flush floors each index's budget at 30 s
/// (`service::shutdown_flush::MIN_FLUSH_TIMEOUT_SECS`), so no flush that had
/// real work to do could ever run to completion — it was SIGKILLed mid-write on
/// every path. Publishing the window as a shared constant is what lets the
/// plist renderer, the CLI stop path, the reaper, and the daemon's own flush
/// planner be checked against each other instead of drifting silently.
///
/// What: 60 s. Deliberately modest — long enough to clear the 30 s per-index
/// floor, short enough that a wedged daemon does not stall reboot or
/// `launchctl bootout` for minutes. Not derived from the flush budget's
/// 20-minute ceiling: a multi-minute `ExitTimeOut` in a static plist trades one
/// operator-visible failure for a worse one.
///
/// Test: `termination_grace_clears_the_measured_launchd_default`,
/// `render_plist_declares_exit_timeout`. trusty-search additionally asserts
/// this window covers its own per-index flush floor, in
/// `service::shutdown_budget`, `commands::stop`, and
/// `commands::start::reap_orphans`.
pub const TERMINATION_GRACE_SECS: u64 = 60;
/// Operator override for [`TERMINATION_GRACE_SECS`].
///
/// Why: a host whose launchd `ExitTimeOut` cannot be raised (a stale installed
/// plist, a container supervisor with its own `TimeoutStopSec`) needs to tell
/// the daemon the truth about its window, or the daemon plans a 55 s flush
/// inside a 5 s life and loses more than it would have with an accurate,
/// smaller budget. The env var is how the terminator declares the real number.
/// What: `TRUSTY_TERMINATION_GRACE_SECS`, a positive integer count of seconds.
/// Test: `termination_grace_honours_a_valid_override`,
/// `termination_grace_ignores_junk_overrides`.
pub const TERMINATION_GRACE_ENV: &str = "TRUSTY_TERMINATION_GRACE_SECS";
/// The termination window this process should plan for.
///
/// Why: reads the override at the one place that owns the policy so no caller
/// re-implements the parse. See [`TERMINATION_GRACE_SECS`] for why the number
/// exists at all.
/// What: [`termination_grace_from`] applied to [`TERMINATION_GRACE_ENV`].
/// Test: covered through `termination_grace_from`'s tests; this wrapper only
/// supplies the env read.
/// Pure half of [`termination_grace`], over an already-read env value.
///
/// Why: pure so the parse and its fallbacks are testable without mutating the
/// process environment (which races every other test in the binary).
/// What: a trimmed, positive integer wins; unset, empty, `0`, and unparseable
/// all fall back to [`TERMINATION_GRACE_SECS`]. A junk value must never shorten
/// the window to zero — a zero-length grace makes every flush a no-op.
/// Test: `termination_grace_honours_a_valid_override`,
/// `termination_grace_ignores_junk_overrides`.
/// Await SIGTERM (unix) or SIGINT/Ctrl-C (all platforms), whichever fires first.
///
/// Why: axum's `with_graceful_shutdown` takes an `async fn()` — it polls the
/// future and stops accepting new connections when it resolves. Passing
/// `shutdown_signal()` here lets every daemon drain in-flight requests before
/// the process exits, which is essential for connection-safe daemon upgrades
/// (issue #534). The shared helper guarantees trusty-search, trusty-memory, and
/// trusty-analyze all respond identically to `launchctl bootout` (SIGTERM).
///
/// What: on unix, registers handlers for both `SIGTERM` and `SIGINT` at
/// construction time and resolves when the first one fires. On non-unix
/// platforms (Windows), only Ctrl-C is watched. Signal registration errors
/// are downgraded to a warning; the function then falls back to watching
/// Ctrl-C only so the daemon still responds to interactive interrupts.
///
/// Test: compile with `cargo check -p trusty-common`; end-to-end coverage is
/// in `crates/trusty-search/tests/` which boots an axum daemon and sends SIGTERM.
pub async