1use super::{ContainerIO, Message, Pipe, READ_BUFFER_SIZE, StdioSender};
4use crate::{
5 attach::{AttachOutput, SharedContainerAttach},
6 container_log::SharedContainerLog,
7 util::{sleep_until, with_scratch},
8};
9use anyhow::{Context, Result};
10use bytes::Bytes;
11use nix::errno::Errno;
12use std::{
13 future,
14 io::{self, ErrorKind},
15 pin::Pin,
16 sync::Arc,
17 task::{Poll, ready},
18};
19use tokio::{
20 io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadBuf},
21 process::{ChildStderr, ChildStdin, ChildStdout},
22 select, task,
23 time::{Duration, Instant},
24};
25use tokio_util::sync::CancellationToken;
26use tracing::{Instrument, debug, debug_span, error, info, trace};
27
28#[derive(Debug, Default)]
31pub(super) struct LogErrors {
32 failing: bool,
33}
34
35impl LogErrors {
36 fn check(&mut self, pipe: Pipe, res: Result<()>) {
37 match res {
38 Ok(()) if self.failing => {
39 self.failing = false;
40 info!("Writing the log of {pipe} works again");
41 }
42 Ok(()) => {}
43 Err(e) if !self.failing => {
44 self.failing = true;
45 error!("Unable to write the log of {pipe}, dropping output until it works: {e:#}");
46 }
47 Err(e) => trace!("Unable to write the log of {pipe}: {e:#}"),
48 }
49 }
50}
51
52impl ContainerIO {
53 pub(crate) fn connect_streams(
55 &mut self,
56 stdin: Option<ChildStdin>,
57 stdout: Option<ChildStdout>,
58 stderr: Option<ChildStderr>,
59 token: CancellationToken,
60 ) {
61 debug!("Start reading from IO streams");
62 let output = self.attach.output();
64 self.spawn_stdin_loop(stdin, token);
65 if let Some(stdout) = stdout {
66 self.spawn_read_loop(stdout, Pipe::StdOut, output.clone());
67 }
68 if let Some(stderr) = stderr {
69 self.spawn_read_loop(stderr, Pipe::StdErr, output);
70 }
71 }
72
73 pub(crate) async fn connect_terminal(
76 &mut self,
77 stdin: bool,
78 token: CancellationToken,
79 ) -> Result<()> {
80 let terminal = self
81 .terminal
82 .as_mut()
83 .context("container has no terminal")?;
84 let tty = match terminal.connect().await {
85 Ok(tty) => tty,
86 Err(e) => {
87 self.attach.close_stdin();
90 return Err(e);
91 }
92 };
93 match tty.open_peer() {
98 Ok(peer) => {
99 let exited = token.clone();
100 task::spawn(async move {
101 exited.cancelled().await;
102 drop(peer);
103 });
104 }
105 Err(e) => debug!("Unable to keep the terminal open until the exit: {e}"),
106 }
107 let output = self.attach.output();
108 self.spawn_read_loop(tty.clone(), Pipe::StdOut, output);
109 self.spawn_stdin_loop(stdin.then_some(tty), token);
110 Ok(())
111 }
112
113 pub(super) fn spawn_read_loop<T>(&mut self, reader: T, pipe: Pipe, output: Arc<AttachOutput>)
116 where
117 T: AsyncRead + Unpin + Send + 'static,
118 {
119 let channel = match pipe {
120 Pipe::StdOut => &mut self.stdout,
121 Pipe::StdErr => &mut self.stderr,
122 };
123 let read_loop = Self::read_loop(
124 reader,
125 pipe,
126 self.logger.clone(),
127 channel.tx.take(),
128 self.attach.clone(),
129 output,
130 );
131 task::spawn(
132 async move {
133 if let Err(e) = read_loop.await {
134 error!("Read loop of {pipe} failed: {e:#}");
135 }
136 }
137 .instrument(debug_span!("read_loop", %pipe))
138 .in_current_span(),
139 );
140 }
141
142 fn spawn_stdin_loop<T>(&self, writer: Option<T>, token: CancellationToken)
145 where
146 T: AsyncWrite + Unpin + Send + 'static,
147 {
148 let Some(writer) = writer else {
149 self.attach.close_stdin();
150 return;
151 };
152 let attach = self.attach.clone();
153 task::spawn(
154 async move {
155 if let Err(e) = Self::read_loop_stdin(writer, attach, token).await {
156 error!("Stdin read loop failure: {e:#}");
157 }
158 }
159 .instrument(debug_span!("read_loop_stdin"))
160 .in_current_span(),
161 );
162 }
163
164 pub(super) const IDLE_FLUSH: Duration = Duration::from_millis(500);
167
168 pub(crate) async fn read_loop<T>(
171 reader: T,
172 pipe: Pipe,
173 logger: SharedContainerLog,
174 message_tx: Option<StdioSender>,
175 attach: SharedContainerAttach,
176 attach_output: Arc<AttachOutput>,
177 ) -> Result<()>
178 where
179 T: AsyncRead + Unpin,
180 {
181 let log_has_drivers = logger.lock().await.has_drivers();
182 let res =
183 Self::read_pipe(reader, pipe, &logger, log_has_drivers, message_tx, &attach).await;
184 if log_has_drivers {
185 let _logging = attach.logging();
188 if let Err(e) = logger.lock().await.finish(pipe).await {
189 error!("Unable to finish the log of {pipe}: {e:#}");
190 }
191 }
192 drop(attach_output);
195 res
196 }
197
198 async fn read_pipe<T>(
199 mut reader: T,
200 pipe: Pipe,
201 logger: &SharedContainerLog,
202 log_has_drivers: bool,
203 mut message_tx: Option<StdioSender>,
204 attach: &SharedContainerAttach,
205 ) -> Result<()>
206 where
207 T: AsyncRead + Unpin,
208 {
209 let mut log_errors = LogErrors::default();
210 let stopped = attach.output_stopped().clone();
211
212 let mut flush_at = None;
217
218 loop {
219 let read = select! {
221 biased;
222 () = stopped.cancelled() => {
223 debug!("Stopping read loop, nothing takes the output anymore");
224 Self::forward(&mut message_tx, Message::Done).await;
225 return Ok(());
226 }
227 read = Self::read_chunk(&mut reader) => read,
228 () = sleep_until(flush_at) => {
230 let _logging = attach.logging();
231 let res = logger.lock().await.flush(pipe).await;
232 log_errors.check(pipe, res.context("flush paused log line"));
233 flush_at = None;
234 continue;
235 }
236 () = Self::closed(message_tx.as_ref()),
241 if message_tx.as_ref().is_some_and(|tx| !tx.consumed()) =>
242 {
243 trace!("Stdio channel closed, dropping sender");
244 message_tx = None;
245 continue;
246 }
247 };
248
249 match read {
250 Ok(data) if data.is_empty() => {
251 debug!("Nothing more to read");
252
253 Self::forward(&mut message_tx, Message::Done).await;
254
255 return Ok(());
256 }
257
258 Ok(data) => {
259 trace!("Read {} bytes", data.len());
260
261 let logging = log_has_drivers.then(|| attach.logging());
264
265 if log_has_drivers {
270 let mut locked_logger = logger.lock().await;
271 let res = locked_logger.write(pipe, &data).await;
272 let pending = locked_logger.has_pending(pipe);
273 drop(locked_logger);
274 log_errors.check(pipe, res.context("write to log file"));
275
276 flush_at = match flush_at {
279 _ if !pending => None,
280 Some(at) if memchr::memchr(b'\n', &data).is_none() => Some(at),
281 _ => Some(Instant::now() + Self::IDLE_FLUSH),
282 };
283 }
284
285 attach.write(Message::Data(data.clone(), pipe));
290 drop(logging);
291
292 Self::forward(&mut message_tx, Message::Data(data, pipe)).await;
293 }
294
295 Err(e) if e.raw_os_error() == Some(Errno::EIO as i32) => {
299 debug!("Stopping read loop");
300 Self::forward(&mut message_tx, Message::Done).await;
301
302 return Ok(());
303 }
304 Err(e) => return Err(e).context("read from container file descriptor"),
305 }
306 }
307 }
308
309 async fn read_chunk<T>(reader: &mut T) -> io::Result<Bytes>
313 where
314 T: AsyncRead + Unpin,
315 {
316 future::poll_fn(|cx| {
317 with_scratch(READ_BUFFER_SIZE, |scratch| {
318 let mut buf = ReadBuf::new(scratch);
319 ready!(Pin::new(&mut *reader).poll_read(cx, &mut buf))?;
320 Poll::Ready(Ok(Bytes::copy_from_slice(buf.filled())))
321 })
322 })
323 .await
324 }
325
326 async fn closed(message_tx: Option<&StdioSender>) {
328 match message_tx {
329 Some(tx) => tx.tx.closed().await,
330 None => future::pending().await,
331 }
332 }
333
334 async fn forward(message_tx: &mut Option<StdioSender>, message: Message) {
339 if let Some(tx) = message_tx
340 && !tx.send(message).await
341 {
342 trace!("Stdio channel closed, dropping sender");
343 *message_tx = None;
344 }
345 }
346
347 pub(crate) async fn read_loop_stdin(
350 mut writer: impl AsyncWrite + Unpin,
351 attach: SharedContainerAttach,
352 token: CancellationToken,
353 ) -> Result<()> {
354 let res = select! {
359 res = Self::forward_stdin(&mut writer, &attach) => res,
360 () = token.cancelled() => Ok(()),
361 };
362
363 attach.close_stdin();
366 res
367 }
368
369 async fn forward_stdin(
370 mut writer: impl AsyncWrite + Unpin,
371 attach: &SharedContainerAttach,
372 ) -> Result<()> {
373 let stdin_closed = attach.stdin_closed().clone();
374 loop {
375 select! {
376 res = attach.read() => {
377 let data = res.context("read from stdin attach endpoints")?;
378 if !Self::handle_stdin_data(&data, &mut writer).await? {
379 return Ok(());
380 }
381 }
382 () = stdin_closed.cancelled() => break,
383 }
384 }
385
386 attach.close_stdin();
389 while let Ok(data) = attach.try_read() {
390 if !Self::handle_stdin_data(&data, &mut writer).await? {
391 break;
392 }
393 }
394 Ok(())
395 }
396
397 async fn handle_stdin_data(data: &[u8], mut writer: impl AsyncWrite + Unpin) -> Result<bool> {
404 debug!("Got {} attach bytes", data.len());
405
406 match writer.write_all(data).await {
407 Ok(()) => Ok(true),
408 Err(e)
409 if e.kind() == ErrorKind::BrokenPipe
410 || e.raw_os_error() == Some(Errno::EIO as i32) =>
411 {
412 debug!("Stopping stdin, the process closed it: {e}");
413 Ok(false)
414 }
415 Err(e) => Err(e).context("write attach stdin to stream"),
416 }
417 }
418}