conmonrs/streaming_server/
attach.rs1use super::{
4 SessionCommon, StreamingServer,
5 session::{ClientRx, InputEvent, SessionInput},
6};
7use crate::{
8 attach::SharedContainerAttach,
9 child_reaper::RegisteredContainer,
10 container_io::{Drain, Lag, Message as IOMessage, Pipe},
11 streaming_server::remotecommand::{MESSAGE_TOO_LARGE, Output, Status},
12};
13use anyhow::{Result, bail};
14use tokio::sync::broadcast::{self, error::RecvError};
15use tokio_util::sync::CancellationToken;
16use tracing::debug;
17
18#[derive(Debug)]
20pub(crate) struct AttachSession {
21 pub(crate) child: RegisteredContainer,
22 pub(crate) stop_after_stdin_eof: bool,
23 pub(crate) common: SessionCommon,
24}
25
26impl StreamingServer {
27 pub(super) async fn attach_loop(
31 session: AttachSession,
32 sender: &mut Output,
33 client: ClientRx,
34 status: &mut Option<Status>,
35 ) -> Result<()> {
36 let AttachSession {
37 child,
38 stop_after_stdin_eof,
39 common:
40 SessionCommon {
41 stdin: stdin_enabled,
42 stdout: stdout_enabled,
43 stderr: stderr_enabled,
44 ..
45 },
46 } = session;
47 let io = child.io().clone();
48 let token = child.token().clone();
49
50 let attach = io.attach().clone();
55 let terminal = attach.is_terminal();
56 let mut input = SessionInput::new(io, attach.clone(), client, stdin_enabled);
58
59 let mut teardown = AttachTeardown {
63 attach: attach.clone(),
64 token: token.clone(),
65 close_stdin: stdin_enabled && stop_after_stdin_eof,
66 };
67
68 let (mut attach_rx, mut done) = attach.subscribe();
71 if done {
72 while let Ok(message) = attach_rx.try_recv() {
73 Self::forward_output(message, stdout_enabled, stderr_enabled, sender).await?;
74 }
75 }
76
77 let mut lag = Lag::default();
79 while !done || input.stdin_open {
80 tokio::select! {
81 res = attach_rx.recv(), if !done => match res {
82 Ok(IOMessage::Done) => done = true,
83 Ok(message) => {
84 lag.received(&attach_rx);
85 Self::forward_output(message, stdout_enabled, stderr_enabled, sender).await?;
86 }
87 Err(RecvError::Lagged(n)) => lag.skipped(n),
88 Err(RecvError::Closed) => {
89 debug!("Exiting streaming attach because container I/O is gone");
90 break
91 }
92 },
93
94 event = input.next() => match event {
95 InputEvent::Resize(data) => input.resize(&data),
96 InputEvent::Ping => sender.ping().await?,
97 InputEvent::StdinClosed if !terminal && !stop_after_stdin_eof => break,
102 InputEvent::StdinClosed => {
103 if stop_after_stdin_eof {
104 teardown.close_stdin = false;
105 input.close_container_stdin();
106 }
107 }
108 InputEvent::Ended => return Ok(()),
111 InputEvent::TooLarge => bail!(MESSAGE_TOO_LARGE),
112 },
113
114 () = token.cancelled() => {
115 debug!("Exiting streaming attach because token cancelled");
116 if !done {
117 Self::drain_attach(&attach, &mut attach_rx, &mut lag, stdout_enabled, stderr_enabled, sender).await?;
118 }
119 break
120 }
121 }
122 }
123
124 *status = Some(Status::Success);
125 Ok(())
126 }
127
128 async fn forward_output(
130 message: IOMessage,
131 stdout_enabled: bool,
132 stderr_enabled: bool,
133 sender: &mut Output,
134 ) -> Result<()> {
135 match message {
136 IOMessage::Data(data, pipe @ Pipe::StdOut) if stdout_enabled => {
137 sender.data(pipe, &data).await
138 }
139 IOMessage::Data(data, pipe @ Pipe::StdErr) if stderr_enabled => {
140 sender.data(pipe, &data).await
141 }
142 IOMessage::Data(..) | IOMessage::Done => Ok(()),
143 }
144 }
145
146 async fn drain_attach(
150 attach: &SharedContainerAttach,
151 attach_rx: &mut broadcast::Receiver<IOMessage>,
152 lag: &mut Lag,
153 stdout_enabled: bool,
154 stderr_enabled: bool,
155 sender: &mut Output,
156 ) -> Result<()> {
157 let mut drain = Drain::default();
158 while let Some(message) = drain.recv_attach(attach, attach_rx, Some(lag)).await {
159 Self::forward_output(message, stdout_enabled, stderr_enabled, sender).await?;
160 }
161 Ok(())
162 }
163}
164
165struct AttachTeardown {
170 attach: SharedContainerAttach,
171
172 token: CancellationToken,
174
175 close_stdin: bool,
177}
178
179impl Drop for AttachTeardown {
180 fn drop(&mut self) {
181 if self.close_stdin && !self.token.is_cancelled() && self.attach.close_container_stdin() {
182 debug!("Closed the container stdin after the end of the attach session");
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests;