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
//! Process-wide signal handling and scoped child-process registry.
//!
//! On SIGINT or SIGTERM (Unix) and the equivalent console-control events on
//! Windows, fallow's default unwind drops `std::process::Child` handles
//! without killing the underlying children. The `fallow-cov` sidecar,
//! `npm install -g`, and self-invoked `fallow health` can run for minutes
//! and accumulate as orphan processes when the user hits Ctrl+C.
//!
//! This module installs a single handler (see `install_handlers`) that on
//! signal delivery: kills every `ScopedChild` currently registered, drains
//! them with a bounded budget (500ms Unix, 1500ms Windows), and exits with
//! the conventional 128+signum exit code (130 for SIGINT, 143 for SIGTERM).
//!
//! Watch mode opts into cooperative shutdown via `set_graceful_mode`: the
//! handler then only flips the shutdown flag and returns, letting the watch
//! loop exit cleanly with code 0 because Ctrl+C is its documented
//! termination path. Other commands keep the forceful 128+signum behavior.
//!
//! See `.plans/issue-477-signal-handlers.md` for the design rationale and
//! `crates/lsp/src/main.rs` for the LSP-side cooperative cancellation.
use OnceLock;
use ;
pub use ScopedChild;
/// True once a termination signal has been observed.
static SHUTDOWN: AtomicBool = new;
/// True when a cooperative consumer (`fallow watch`) is active. The handler
/// then flips `SHUTDOWN` and returns instead of killing children and
/// exiting; the consumer is responsible for clean teardown.
static GRACEFUL: AtomicBool = new;
/// Idempotency guard for `install_handlers`. Repeated calls (e.g. when
/// `run_watch` reinstalls the handler) are silently no-ops.
static INSTALLED: = new;
/// Install the signal handler. Idempotent; safe to call multiple times.
/// Returns the original error from the underlying primitive on first call
/// failure.
/// True after a signal has been observed. Read by long-running loops
/// (currently `fallow watch`) to break out cooperatively.
/// Enter cooperative shutdown mode. Subsequent signals set `SHUTDOWN`
/// without killing children or calling `exit()`. The caller is responsible
/// for polling `is_shutting_down()` and exiting cleanly.
/// Leave cooperative shutdown mode. Subsequent signals revert to the
/// forceful behavior (kill registered children, `exit(128 + signum)`).
/// RAII guard that calls `set_graceful_mode` on construction and
/// `clear_graceful_mode` on drop. Used by `run_watch` so any return path
/// (success, panic-with-unwind in debug, early return on config error)
/// restores forceful-exit behavior for the next command.
;
/// Mark shutdown, drain the registry (kills every registered child
/// regardless of mode so in-flight subprocesses do not survive the
/// signal), then either exit (default) or return for cooperative
/// consumers in graceful mode (`fallow watch`).
///
/// Graceful mode MUST still drain children: watch's `analyze_and_
/// report` spawns git subprocesses (via `fallow_core::changed_files`
/// and `fallow_core::churn`) that need reaping mid-analysis. Without
/// drain, a Ctrl+C during analysis would let the parent return from
/// the inner pass only after every git child completed naturally,
/// defeating the entire "Ctrl+C reaps in-flight git work" contract.
/// Invoked by the platform-specific handler thread.