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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! Cooperative shutdown coordination for `SIGINT`, `SIGTERM`, and `SIGHUP`.
//!
//! On Unix, the default action for these signals is to terminate the
//! process *without unwinding the stack*, which means
//! [`Drop`](std::ops::Drop) impls — including
//! [`crate::storage::SqliteStorage::drop`] — never run, and WAL frames
//! that haven't been checkpointed yet are left stranded on disk
//! (issue #270).
//!
//! This module installs a small handler that translates those signals
//! into a single atomic "shutdown requested" flag, then lets the main
//! thread complete its current operation, return from `main`, and run
//! every destructor on the way out. If the user signals again while the
//! main thread is still inside a long operation we escalate to an
//! immediate `_exit`, matching the muscle-memory of "press Ctrl-C
//! twice."
//!
//! On Windows we currently rely on the default Ctrl-C behaviour and the
//! [`Drop`] / `panic = "abort"` interaction; the public surface here is
//! a no-op so callers don't need `cfg(unix)` at every call site.
use OnceLock;
use ;
/// Set when one of the registered termination signals has been
/// observed. Public callers should use [`is_requested`] /
/// [`exit_code`].
static SHUTDOWN_REQUESTED: AtomicBool = new;
/// `128 + signo` of the signal that triggered the shutdown, encoding
/// the conventional Unix exit code. Stored as `i32` so the relaxed
/// load is wait-free; only the *first* signal wins, which keeps the
/// reported exit code stable when multiple signals race.
static SHUTDOWN_EXIT_CODE: AtomicI32 = new;
/// Tracks whether [`install`] has already wired the background thread,
/// so callers can invoke it safely from `main` without worrying about
/// double-registration in test harnesses or library re-entry.
static INSTALLED: = new;
/// Install signal handlers for `SIGINT`, `SIGTERM`, and `SIGHUP` (Unix
/// only). On non-Unix targets this is a no-op.
///
/// # Behaviour
///
/// * The first signal records the exit code `128 + signo` and flips
/// [`is_requested`]. The main thread is responsible for noticing the
/// flag at a safe checkpoint and returning from `main`.
/// * The second matching signal calls
/// [`signal_hook::low_level::exit`] (an async-signal-safe `_exit`
/// wrapper) immediately so a user can always escape a hung command
/// by hitting Ctrl-C twice.
///
/// Idempotent: subsequent calls return without re-installing.
/// Restore the kernel's default `SIGPIPE` disposition (terminate the
/// process) for filter-style text output (#434). No-op on non-Unix targets.
///
/// The Rust runtime ignores `SIGPIPE` before `main` runs, so a write to a
/// closed pipe returns `EPIPE`, which `println!` turns into a panic — and
/// `panic = "abort"` turns that panic into `SIGABRT` plus a core dump, after
/// the requested output was already delivered (`br list | head`). With the
/// default disposition the kernel ends the process on the first such write,
/// exactly like `cat`, `grep`, or `rg` in the same pipeline (exit status
/// `128 + 13`).
///
/// Callers decide *whether* this applies: structured JSON/TOON output
/// streams through writers that already classify a broken pipe as a
/// non-error, and `br serve` needs `EPIPE` as an error so its stdio
/// transport can shut down cooperatively, so both keep `SIGPIPE` ignored.
/// Like the second-strike path above, an immediate kill skips `Drop`; the
/// abort it replaces never ran destructors either.
/// The crate denies `unsafe_code`; this is the second sanctioned carve-out
/// (after `sync::db_inode_lock`). Installing `SIG_DFL` keeps no handler alive
/// and manages no memory, so `signal(2)` cannot violate a Rust invariant here.
/// Returns `true` once any registered signal has been observed.
/// Returns the conventional Unix exit code (`128 + signo`) for the
/// signal that triggered shutdown, or `None` if no signal has fired.
/// Terminate the process with `code`, guaranteeing the exit code survives
/// teardown. This is the single exit funnel for every deliberate process
/// exit; call it only after the caller has dropped (or deliberately
/// forfeited) any [`crate::storage::SqliteStorage`] whose WAL should be
/// checkpointed, exactly as with [`std::process::exit`].
///
/// # Why not `std::process::exit` on Windows (GitHub #439)
///
/// On Windows, `std::process::exit` reaches the CRT `exit()` path, which
/// eventually calls `ExitProcess`. `ExitProcess` first terminates every
/// other thread in the process and only then runs atexit callbacks and
/// TLS/FLS destructors on the calling thread. Any of those destructors that
/// joins one of the just-terminated threads finds a thread that never ran
/// its Rust epilogue, which trips std's `JoinInner::join` integrity check
/// ("threads should not terminate unexpectedly") and aborts with
/// `0xC0000409` — corrupting the exit code of otherwise-successful
/// commands. `TerminateProcess` on our own handle sets the exit code and
/// skips that teardown entirely. Rust destructors were never going to run
/// on this path anyway (they don't under `std::process::exit` either), so
/// the only cleanup we owe here is flushing the std stream buffers, which
/// this function does on every platform before exiting.
// The single `unsafe` block below is the `#439` Windows `TerminateProcess`
// carve-out sanctioned in Cargo.toml's `[lints.rust]` note (alongside
// `sync::db_inode_lock` and `restore_default_sigpipe_unix`); without this
// attribute the crate-level `#![deny(unsafe_code)]` fails every
// `x86_64-pc-windows-msvc` build.
!