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
//! A process that leaves a grandchild behind whose parent is already dead — the
//! fixture the tree-kill test needs.
//!
//! `examples/tick.rs` proves a handle can be killed. It cannot prove the case
//! that actually breaks a kill, because it has no descendants: killing its pid
//! is the whole job. The shape that breaks a kill is the ordinary one a dev
//! server has — a process starts a second, the second starts a third, the second
//! exits — and it breaks it because every kill built on the process table walks
//! parent/child links, and the middle link is gone. The third process is still
//! the run's responsibility and there is no longer any path from the run to it.
//!
//! This program is that shape, on purpose, and it is written to be *hostile* to
//! a table walk rather than merely to happen to defeat one:
//!
//! - `orphan <pidfile>` is the top process. It starts the middle and then runs
//! until it is killed, so the pid the harness recorded stays live and a test
//! can tell "the kill reached the top" from "the kill reached the leaf".
//! - `orphan --middle <pidfile>` starts the leaf and returns immediately. It is
//! alive for milliseconds and its only job is to stop being alive.
//! - `orphan --leaf <pidfile> <middle-pid>` waits until the middle really is
//! gone (see [`orphaned`], which asks a different question on each platform)
//! and only then writes its own pid to `<pidfile>` and runs forever. The wait
//! is what makes the fixture deterministic: when the pid file appears, the
//! parent/child link a walk would have followed is provably gone, so a test
//! that kills after reading it is not racing the middle's exit.
//!
//! The middle's pid is passed down rather than read with `getppid` at startup,
//! because the middle is usually gone *before the leaf runs its first line* —
//! which is the whole design working, and which would make a leaf that compared
//! against whatever `getppid` said at startup wait for a change that had already
//! happened. Comparing against the pid it was told to expect has no such state
//! in it: the leaf is orphaned exactly when its parent is no longer that pid.
//!
//! Both platforms since 0.26.0, because both have the gap and neither has the
//! same mechanism for it. The shape of the chain is identical; only the leaf's
//! definition of "my parent is gone" differs, and it differs because the
//! operating systems genuinely do:
//!
//! - **unix** — the leaf is *reparented* to init, so `getppid` changing away from
//! the middle's pid is a direct observation of the event that breaks a table
//! walk. The containment that closes it is a process group.
//! - **Windows** — there is no reparenting and no process group. A process
//! carries the pid of whatever created it forever, dead or not, which is
//! exactly why `taskkill /T` cannot reach the leaf: it walks from the top to a
//! middle that is no longer in the table and stops there. So the leaf waits for
//! the middle to *leave the process table* instead, which is the same moment
//! observed a different way. The containment that closes it is a Job Object.
//!
//! Run directly rather than as a test: `cargo run --example orphan /tmp/leaf.pid`.
/// How often the leaf asks whether it has been reparented yet, and how long the
/// two surviving processes sleep between doing nothing. Short enough that a test
/// is not waiting on it, long enough that neither process is a busy loop.
const POLL_MS: u64 = 20;
/// Start this same binary again with `args`, inheriting stdout, stderr and the
/// working directory — so a handle's capture file sees the whole family and a
/// kill that misses one of them is visible in the log as well as in the process
/// table.
///
/// Never waited on, which is the fixture's entire subject: the middle process
/// exits without anyone reaping it and the leaf is never awaited by anybody at
/// all. The zombie that leaves behind is welcome — a table walk from the top
/// process finds a dead middle with no children, which is exactly the misleading
/// picture a real dev-server tree presents to the kill this test is about.
/// Write this process's pid where the test can read it, atomically.
///
/// Written to a neighbouring path and renamed rather than written in place: a
/// reader polling for the file would otherwise be able to open it between the
/// create and the write and parse an empty string as a pid. `rename` within one
/// directory is atomic, so the file either is not there or is complete.
/// Run until something kills this process, or until the ceiling gives up on it.
///
/// The ceiling exists because this fixture's whole job is to outlive its caller,
/// so it will also outlive a caller that dies badly — a `SIGKILL`ed test binary
/// runs no `Drop` and kills nothing it started. The negative control here is
/// worse than most: it asserts that a grandchild SURVIVES a naive kill, so a
/// failure between that assertion and its cleanup leaks by construction. Left
/// alone these accumulate across runs. Far longer than any test that uses this,
/// short enough that a leak is measured in minutes rather than until reboot.
!
/// Has the process that started this one gone?
///
/// The two implementations observe the same instant by different means, because
/// the platforms disagree about what happens to a child whose parent dies. See
/// the module docs.