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
//! The spawn-budget poll loop. The liveness classification it calls lives in
//! `crate::uds::probe` since #5182 — `bind_singleton_hardened` needs the same
//! answer and is not behind this feature. See there for why ECONNREFUSED is not
//! simply "no listener", and for the accept-promptly REQUIREMENT that fact
//! places on every supervised service.
//!
//! Test: `wait_for_spawn_gives_up_within_the_spawn_budget`,
//! `wait_for_spawn_returns_once_the_socket_binds`,
//! `wait_for_spawn_reports_a_child_that_exited`.
use Path;
use ExitStatus;
use Child;
use cratesocket_is_serving;
use ServiceTimeouts;
/// How a spawned child's first moments ended.
///
/// Why (#6600): "the socket never appeared" and "the process is already dead"
/// are different failures with different remedies — a mistuned `spawn_probe`
/// versus a precondition the child could not meet — and collapsing the second
/// into the first cost the whole budget before saying anything useful.
/// Test: see the module docs.
pub
/// Poll the socket with exponential backoff until it accepts a connection, the
/// child exits, or the service's spawn budget elapses.
///
/// Why: a freshly-spawned child takes an unknown time to bind — a few ms for a
/// service with nothing to load, tens of seconds for one that loads a model.
/// Polling from a short initial interval and doubling gives sub-50 ms detection
/// on the fast case without hammering the kernel on the slow one.
///
/// Why the child is observed too (#6600): a child that dies on a held lock or a
/// missing directory never binds, and waiting out a 20 s budget to say
/// [`SpawnWait::TimedOut`] tells the caller nothing about why. `try_wait` costs
/// one non-blocking `waitpid` per interval and turns that into an answer within
/// one poll.
///
/// 🔴 The socket is asked FIRST on every iteration. A child that bound and then
/// exited must still report [`SpawnWait::Bound`] — the caller's next act is to
/// dial that socket, and something is answering it.
///
/// What: loops [`socket_is_serving`], then `child.try_wait()`, until one of them
/// answers or the cumulative wait exceeds `timeouts.spawn_probe`. Never sleeps
/// past the deadline. A `try_wait` that ERRORS is treated as "still running":
/// the status is unreadable, not zero, and killing the wait on it would report a
/// dead child that may be serving.
/// Test: see the module docs.
pub async