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
//! The fire-and-forget detached spawn (DESIGN §8.1): a long-lived driver yog's
//! own exit can never kill. Split from [`super`] so that shape — no pipe, no
//! signal, and a status nobody reads, in contrast to the piped
//! [`run`](super::Cli::run) family whose [`Stream`](super::Stream) drop SIGTERMs
//! — keeps [`super`] under the 300-line cap.
//!
//! **Fire-and-forget is not the same as parentless (bl-3016).** Dropping a
//! `std::process::Child` neither signals nor reparents: yog stays the parent for
//! as long as it lives, so a driver that exits first sits in the process table
//! as a zombie until yog itself dies. Reaping is an obligation separate from
//! detachment, and it is discharged here by a [`reap`] thread per spawn.
//!
//! **stdin/stdout are null; stderr is not.** A detached child has no waitable
//! status, so if its stderr went to `/dev/null` too, a driver that dies right
//! after launch would be indistinguishable from a clean launch (§13.3 as
//! amended). The caller names a per-spawn **sink file** instead; the ops-row
//! projection folds its tail back in at read time
//! ([`opslog::detached`](crate::opslog::detached)). This crate stays generic —
//! it opens whatever path it is handed and knows nothing of the naming.
use fs;
use Path;
use ;
use thread;
use ;
/// Hand `child` to a thread that blocks in `wait` until it exits, then drops the
/// status on the floor (bl-3016). This is the *only* thing yog does with a
/// detached child after launch, and it is not a leash: `wait` posts no signal
/// and holds no pipe, so the driver runs exactly as long as it wants, and yog's
/// own exit destroys the thread rather than the child (init adopts it). Without
/// it yog leaks one zombie per fire — a `Child` drop does not reparent, so the
/// kernel holds every exited driver's status for a parent that never asks.
///
/// A thread rather than the two alternatives, both worse: `SA_NOCLDWAIT` is
/// process-global and makes `waitpid` fail `ECHILD`, which would destroy the
/// exit statuses [`run`](Cli::run) and [`Streamed`](super::Streamed) read; a
/// double fork buys a truly parentless driver at the price of an `unsafe`
/// `pre_exec` whose body — running only between fork and exec, in a process that
/// execs away before writing coverage — can never be tested (AGENTS.md: "if it
/// can't be tested, it mustn't be built"). One parked thread per live driver is
/// the cheap, testable, entirely-safe discharge; the same shape [`Stream`]'s
/// drain threads already use.
///
/// [`Stream`]: super::Stream
/// Open `path` (creating its parent chain) as the child's stderr, **degrading to
/// `/dev/null`** when it cannot be created: an unwritable sink loses the capture,
/// but it must never block the launch — the driver is the point, the log is the
/// diagnosis.