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
//! The process: spawned once, drained on stderr, killed on drop.
//!
//! # `kill_on_drop` is the whole point of this module
//!
//! `tokio::process::Child` does **not** kill the process it is dropped with.
//! That is not a detail of this crate's design; it is the default in `tokio`,
//! and it is the correct default for a `Child` whose whole job is to be waited
//! on. A stdio client is not that: it holds a server that has no other reason
//! to exist, so dropping the client has to take the server with it. A client
//! that failed halfway through a handshake and returned an error would
//! otherwise leave a process running with nobody reading its stdout — an
//! orphan holding a pipe, holding whatever the user asked it to do, for as
//! long as the machine stays up.
//!
//! So `spawn` sets `kill_on_drop(true)` before the `Command` is ever
//! executed, and it is the only function in the crate that spawns a child. One
//! place to look, one flag to audit.
//!
//! On Unix the flag alone leaves the killed child to be reaped, and that is
//! tokio's own business rather than this crate's: `process` compiles the
//! `SIGCHLD` machinery for a Unix target whether or not the public `signal`
//! feature is enabled (`cfg_signal_internal!` in tokio's `macros/cfg.rs` is
//! `any(feature = "signal", all(unix, feature = "process"))`), so the reaper is
//! always present here and the dependency stays at `process`.
//!
//! # Where the child's stderr goes
//!
//! A server logs to stderr because stdout is the conversation, and a client
//! that discarded that stream would deadlock: a child writing more than a pipe
//! buffer's worth of log output blocks on a full pipe and stops answering. So
//! `forward_stderr` drains it continuously and hands it to
//! [`Diagnostics`], one line at
//! a time.
//!
//! One line at a time is the part worth arguing. A read from a pipe returns
//! whatever happens to be in the buffer, so forwarding the read directly would
//! write log lines in whatever sizes the kernel chose — a diagnostic channel
//! that interleaves its own lines with itself. Accumulating to the next
//! newline costs one `Vec` per child and makes the log the log rather than a
//! stream of fragments.
//!
//! Nothing here writes to the protocol. There is no path from this module's
//! stderr handling to a [`StdioSink`](crate::adapters::primary::stdio::StdioSink),
//! and there is not going to be one.
//!
//! # Examples
//!
//! The whole lifecycle, which is what a caller of
//! [`StdioClient::spawn`](super::StdioClient::spawn) is doing under the hood:
//!
//! ```no_run
//! use protocol_droid::adapters::secondary::stdio::{ChildSpec, ClientConfig, StdioClient};
//! use protocol_droid::adapters::primary::stdio::{Diagnostics, StdioConfig};
//! use protocol_droid::domain::MaxFrameSize;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! let spec = ChildSpec::new("my-server")?;
//! let config = ClientConfig::new(StdioConfig::new(MaxFrameSize::new(1024 * 1024)?));
//!
//! let client = StdioClient::spawn(&spec, config, Diagnostics::stderr()).await?;
//! // Dropping `client` here kills the child: `kill_on_drop(true)` was set
//! // before the process was started, so the server does not outlive it.
//! drop(client);
//! # Ok(())
//! # }
//! ```
use Stdio;
use Arc;
use ;
use ;
use JoinHandle;
use crateDiagnostics;
use ChildSpec;
/// How many bytes one read of the child's stderr may ask for.
///
/// A read from a pipe returns whatever is in the buffer, so this bounds the
/// copy rather than promising a size. Large enough that a chatty server is not
/// read in slivers, small enough that no child carries a kilobyte of log
/// buffer per connection.
const STDERR_READ_CHUNK: usize = 4 * 1024;
/// A freshly spawned child's process handle and the three pipes it was given.
pub
/// Starts a child server, with all three of its standard streams piped.
///
/// The pipes are requested here rather than in [`ChildSpec::to_command`]
/// because a spec describes a program and a client describes a conversation,
/// and the conversation needs a stdin and a stdout to have a conversation on.
/// Both halves are taken out of the returned [`Child`] immediately, so the
/// process handle is left holding nothing but the handle.
///
/// # Errors
///
/// [`SpawnError::NotStarted`] when the program could not be started at all —
/// no such file, not executable, out of processes. The pipe requests cannot
/// fail, so this is the only way the call can.
pub
/// Drains the child's stderr into a diagnostics channel, and keeps draining it.
///
/// Returns the task's handle, which completes when the pipe reaches
/// end-of-file. That is worth having rather than discarding: end-of-file on
/// the child's stderr is the kernel's own account of the child exiting, and a
/// caller that needs to know the process is gone — a test proving a dropped
/// client took its server with it, say — can await this and be told by the
/// operating system rather than by a handle's `Drop` having run.
///
/// The handle is deliberately not awaited here. Nothing else has to happen
/// before the log is written, and a forwarder that blocked the spawn until the
/// child exited would be a child that had to exit before it could be used.
///
/// # Panics
///
/// Never. The task swallows the stream's own failures: a child's stderr that
/// cannot be read, or a diagnostics channel that refuses a write, ends the
/// forwarder. Neither is a fault in the conversation, and a task that could
/// not report them would take the runtime's panic handler with it.
pub
/// Why a child could not be started.