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
268
269
270
271
272
273
//! How long [`crate::deploy`] gives a reload before it gives up on it, and
//! how long it then watches the result.
//!
//! Both are sized from the release's own `listen_timeout` and
//! `graceful_timeout`, against shep's reload deadline and its post-drain
//! probe. They live apart from the deploy state machine so that the
//! arithmetic and its reasons sit with the tests that pin it.
use Duration;
use AppConfig;
use crateverify;
/// The slack shep adds to its own reload deadline, per instance.
///
/// Copied from `RELOAD_DEADLINE_SLACK` in shep's `supervisor.rs`, where it
/// is a five-second constant covering "scheduling jitter and nothing else"
/// between the two timeouts below. Named here rather than folded into
/// [`budget`]'s arithmetic so a reader can put the two files side by side.
///
/// `crate::optin`'s cutover budget reads this one rather than copying shep's
/// constant into a second file.
pub const RELOAD_DEADLINE_SLACK: Duration = from_secs;
/// How long a reload of `instances` instances of this app gets before
/// verification gives up on it.
///
/// This is shep's own formula, not an estimate of it. `Actor::arm_reload_
/// deadline` (`supervisor.rs:3581`) bounds each swap at
/// `listen_timeout + graceful_timeout + RELOAD_DEADLINE_SLACK` and ends the
/// swap when that expires, and `advance_reload` walks the instance queue
/// with `pop_front`, one swap at a time, so a whole reload is that per
/// instance.
///
/// # What is measured and what is still read from a file
///
/// `instances` is measured. It comes from [`crate::verify::Generation::instances`], the
/// live pid count, and NOT from `AppConfig::instances`, which is the count
/// the release's Flockfile asks for. Those differ the moment anyone runs
/// `shep stock <sheep> <n>`, a first-class verb that changes what is running
/// without touching any file this crate reads - and the failure that
/// produced is the one this whole window exists to stop: a file saying one
/// instance against two really running gave a budget of 14s for a reload
/// that took about 17, and a healthy release was rolled back.
///
/// A probed `reuse_port` app is the one exception to "shep's own formula":
/// each swap of it probes a second time once the drainee is reaped
/// ([`probes_again_after_drain`]), with `listen_timeout` as that probe's
/// deadline, and the reload deadline does not include it. Without the extra
/// `listen_timeout` per instance the post-drain check could outlast the
/// budget and roll back a healthy release.
///
/// The two timeouts are still read from the release's Flockfile, and that
/// IS an inference, for the same reason
/// `deploy::prepare::refuse_ungated_verification` documents about its own read: nothing
/// re-registers a sheep, and `describe` reports status, pid and uptime but
/// never config, so there is no live source to read them from. The exposure
/// is narrower than the instance count's, because changing either one means
/// editing the Flockfile and redeploying, which is the path that brings the
/// new value here anyway - where `shep stock` is a single command that
/// bypasses it.
///
/// # Why it is copied rather than inferred
///
/// Three rounds of review answered "how long may a healthy reload take" by
/// reasoning about shep's timing from outside, and each answer was wrong
/// somewhere new. The first was fixed at ten seconds. The second derived
/// `listen_timeout * instances` and doubled it, which supplies exactly
/// `listen_timeout` of headroom per instance and so holds only while the
/// drain is shorter than the readiness wait - and shep's own defaults are
/// the other way round, `listen_timeout` three seconds against
/// `graceful_timeout` eight. The drain is not incidental to this crate
/// either: [`crate::verify`] requires every OLD instance to be gone, so the
/// whole of it has to fit inside the window.
///
/// There is no floor. A budget shorter than shep's deadline rolls back
/// healthy releases, and one longer buys nothing, because when that deadline
/// expires shep ends the swap itself - there is nothing left to wait for.
/// The five-second slack is already the margin, and it is shep's number for
/// the same jitter.
///
/// `graceful_timeout` is the drain window and `listen_timeout` bounds
/// readiness for EVERY source: `await_ready` (`probes/ready.rs`) wraps
/// `Channel`, `Probe` and `Heuristic` alike in `tokio::time::timeout` and
/// aborts the swap when it elapses. So a probed release's readiness is not
/// unbounded, as an earlier version of this comment claimed - it is bounded
/// by the same field, and an app that needs a minute to compile its client
/// says so by setting `listen_timeout`, which this reads.
pub
/// Whether shep re-runs the readiness probe after a swap's old instance has
/// drained: a probed app that sets `reuse_port`. The second probe gets the
/// app's `listen_timeout` as its own deadline, after the drain, so it is
/// outside the reload deadline [`budget`] copies from shep and has to be
/// added to it, and it is what [`dwell_for`] watches for a demotion.
/// How long a verified flock is watched after its turnover.
///
/// [`verify::DWELL`] for most apps. Longer for a probed app that sets
/// `reuse_port`: shep reloads such an app with the two instances overlapping
/// and re-runs the readiness probe AFTER the old instance has drained, with
/// the app's own `listen_timeout` as the deadline, because the old instance
/// may have been the one that answered the first probe. A replacement that
/// fails that second probe is demoted to `Starting` and left there, and that
/// can land anywhere inside the deadline, so a ten-second dwell would record
/// a release deployed and have shep demote it a minute later. Read out of
/// shep-daemon's `finish_swap` and `spawn_verify_task` on 2026-09-04.
pub