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
//! The one wait a test uses for a liveness property, and the one deadline behind it.
//!
//! A liveness property ("this eventually settles", "this child eventually exits") carries
//! no wall-clock bound of its own, so a deadline standing in for one is a proxy: it can
//! only ever be defeated by a machine slow enough, and when it is, the run fails for a
//! reason unrelated to what it was checking. Two rules follow, and this module exists to
//! make both structural rather than per-call-site discipline.
//!
//! First, the number is a backstop, not a budget. [`BACKSTOP`] is sized for "genuinely
//! stuck", never for "how long should this take": every wait in this workspace completes in
//! milliseconds on a healthy machine and never approaches it, so a generous value costs a
//! passing run nothing and only pays out when something is really wedged. It is sized for
//! the most contended machine this suite runs on rather than for the machine it was written
//! on, because those are the two candidates and only one of them defeats a wait that is
//! working. There is still no knob: a per-call-site number is a number guessed against
//! whichever machine its author had.
//!
//! Second, expiry is reported where it happens. [`wait_for`] panics naming the property
//! that never held, so a wait that gives up cannot be mistaken for the assertion three
//! steps downstream of it. [`expired`] is that report, shared with `Core::settle` so the
//! two waits a test can make read the same when they give up.
//!
//! ## A fixture that has to outlive the wait watching it
//!
//! A backstop shared by every wait sets a trap for one kind of test: the sort whose fixture
//! is a process spawned to still be running when the wait gives up, so that the wait
//! succeeding is evidence the thing under test cut the fixture short. Such a fixture must
//! outlive the backstop by a wide margin, or its own natural end satisfies the wait and the
//! test passes on a machine where nothing works. [`FIXTURE_LIFETIME`] is the one length
//! those fixtures sleep, related to [`BACKSTOP`] by a compile-time assertion below rather
//! than by two numbers that happen to differ today.
//!
//! ## The survey this module records
//!
//! Every test-side wall-clock deadline in the workspace at the time this module was
//! written, and whether a loaded machine can defeat it:
//!
//! - **Polling waits for a liveness property** (`wait_until` in `core.rs`, `app.rs` and
//! `app/reload.rs`, `wait_for_process_state` in `executor.rs`, five hand-rolled loops in
//! `repon/tests/terminal_restoration.rs`): at risk, all of them, and all now routed
//! through this module. The five in the pty harness were the worst of the set, since
//! they wait on a freshly built binary claiming a real terminal.
//! - **Blocking receives used as a backstop** (`recv_timeout` in `executor.rs` and the pty
//! harness): at risk on the same terms; each now takes [`BACKSTOP`] rather than its own
//! five seconds. The short `recv_timeout` calls that poll for the next chunk of pty
//! output are intervals, not deadlines, and are left alone.
//! - **`Core::settle` awaiting a Generation the caller already dispatched** (most of its
//! call sites): was at risk, and is the one entry here that has since been fixed. It
//! discarded the `WaitTimeoutResult` its own `wait_timeout_while` returned, so an expiry
//! was indistinguishable from a settle and came back as an unsettled snapshot the caller
//! reported as a wrong value several steps downstream, with nothing naming the wait.
//! `Core::settle` now takes no deadline at all: it waits on [`BACKSTOP`] and panics
//! through [`expired`], so there is no number left at a call site to guess and no expiry
//! left to mistake for an answer. `Core::try_settle` is where a deadline that is itself
//! the claim goes, and it hands back an expiry rather than panicking on one.
//! - **`Core::settle` awaiting an Action's completion Generation**: at risk, and *not* on
//! the deadline. `run_action`'s completion clears `action_running` before it dispatches
//! that Generation, so a settle called in the window between the two finds the gate at
//! zero and returns at once however large its deadline is. A bigger number widens the
//! race rather than removing it; the caller has to wait on what it actually needs, which
//! is what `run_failing_action_on` in `app.rs` now does.
//! - **Deliberately short settles proving a negative** (`app.rs`'s 200ms focus gate,
//! `core.rs`'s 50ms empty-order settle, and the 500ms bound on a Launcher handoff
//! returning promptly): not raised, on purpose. Their number is the claim, not a
//! backstop, so raising it would delete the thing they check. Each goes through
//! `Core::try_settle` and says out loud what it makes of an expiry, since for them an
//! expiry is a reading rather than a failure. They are still weakened by load, in that a
//! probe too slow to land inside the window makes them pass without discriminating; that
//! is a different defect and is left recorded, not fixed.
//! - **Fixed sleeps standing in for a bound** (`app.rs`'s 100ms "nothing ran", `core.rs`'s
//! 1.8s held-step check, `executor.rs`'s 600ms slow-drain): safety claims, not liveness
//! ones, and no deadline can prove them. Load makes them weaker, never flakier.
//!
//! Gated behind `test-util` (on under `cfg(test)` for this crate's own tests) so a
//! test-only affordance never ships on the default published surface, per
//! [ADR 0021](https://github.com/paulchiu/repon/blob/main/docs/adr/0021-a-release-is-what-the-tag-pipeline-publishes.md).
//! The gate is repeated on each public item, and every item here says in its own doc
//! comment that it exists for a test, which is the pair
//! `every_pub_fn_documented_as_test_only_is_either_gated_or_has_a_production_call_site`
//! looks for: remove any one of these gates and that scan fails.
use ;
/// The deadline every wait a test makes goes through.
///
/// Two minutes because nothing here benefits from failing fast: a correct run reaches its
/// condition in milliseconds, and the only event this number decides is how long a wedged
/// suite runs before it reports. Thirty seconds stood here first and was defeated nine
/// times in one day by CI runners executing the whole workspace's tests across every core
/// they have, which is a slow machine rather than a wedged one. Raising it costs a healthy
/// run nothing, because [`wait_for`] returns on its next poll whatever the ceiling is.
pub const BACKSTOP: Duration = from_secs;
/// How long a test's fixture sleeps when the point of it is to still be running once
/// [`BACKSTOP`] expires.
///
/// Ten times the backstop, so the margin is a factor rather than a coincidence: a fixture
/// that ended on its own before the wait watching it gave up would make its test pass
/// without discriminating anything. The cost of the factor is paid only by a run that
/// already failed, which leaves such a fixture sleeping out the rest of this length.
pub const FIXTURE_LIFETIME: Duration = from_secs;
// The relationship the two constants above only mean anything together, checked where it
// cannot be forgotten rather than left to a reader comparing two literals.
const _: = assert!;
/// How often [`wait_for`] re-reads its condition. An interval, never a deadline.
const POLL_INTERVAL: Duration = from_millis;
/// Blocks until `condition` holds, panicking once [`BACKSTOP`] expires. For a test.
///
/// `property` names what was being waited for, as a noun phrase reading after "waiting
/// for": the panic is the report, so a test never has to carry a deadline's expiry
/// downstream as a wrong value.
/// [`wait_for`], plus whatever a hung fixture owes a test before the panic.
///
/// `on_timeout` runs only on expiry: it does its cleanup (killing a child that never
/// exited, say) and returns any extra context to fold into the panic message.
/// [`wait_for_or`] against an explicit deadline, so this module's own tests can exercise
/// the expiry path without waiting out a real backstop.
/// The panic an expired wait reports, wherever the wait itself lives.
///
/// Shared with `Core::settle`, which blocks on a Condvar rather than polling and so cannot
/// go through [`wait_within`]: the two are one wait as far as a reader of a failing run is
/// concerned, and a second wording would make them read as two unrelated defects. `context`
/// is whatever the wait can add about the state it gave up in, or empty.
pub !