/build/source/nativelink-util/src/buf_channel.rs
Line | Count | Source |
1 | | // Copyright 2024 Trace Machina, Inc. All rights reserved. |
2 | | // |
3 | | // Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // See LICENSE file for details |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | use core::pin::Pin; |
16 | | use core::sync::atomic::{AtomicBool, Ordering}; |
17 | | use core::task::Poll; |
18 | | use std::collections::VecDeque; |
19 | | use std::sync::Arc; |
20 | | |
21 | | use bytes::{Bytes, BytesMut}; |
22 | | use futures::task::Context; |
23 | | use futures::{Future, Stream, TryFutureExt}; |
24 | | use nativelink_error::{Code, Error, ResultExt, error_if, make_err, make_input_err}; |
25 | | use tokio::sync::mpsc; |
26 | | use tracing::warn; |
27 | | |
28 | | const ZERO_DATA: Bytes = Bytes::new(); |
29 | | |
30 | | /// Create a channel pair that can be used to transport buffer objects around to |
31 | | /// different components. This wrapper is used because the streams give some |
32 | | /// utility like managing EOF in a more friendly way, ensure if no EOF is received |
33 | | /// it will send an error to the receiver channel before shutting down and count |
34 | | /// the number of bytes sent. |
35 | | #[must_use] |
36 | 52.1k | pub fn make_buf_channel_pair() -> (DropCloserWriteHalf, DropCloserReadHalf) { |
37 | | // We allow up to 2 items in the buffer at any given time. There is no major |
38 | | // reason behind this magic number other than thinking it will be nice to give |
39 | | // a little time for another thread to wake up and consume data if another |
40 | | // thread is pumping large amounts of data into the channel. |
41 | 52.1k | let (tx, rx) = mpsc::channel(2); |
42 | 52.1k | let eof_sent = Arc::new(AtomicBool::new(false)); |
43 | 52.1k | ( |
44 | 52.1k | DropCloserWriteHalf { |
45 | 52.1k | tx: Some(tx), |
46 | 52.1k | bytes_written: 0, |
47 | 52.1k | eof_sent: eof_sent.clone(), |
48 | 52.1k | }, |
49 | 52.1k | DropCloserReadHalf { |
50 | 52.1k | rx, |
51 | 52.1k | queued_data: VecDeque::new(), |
52 | 52.1k | replay_data: VecDeque::new(), |
53 | 52.1k | last_err: None, |
54 | 52.1k | eof_sent, |
55 | 52.1k | bytes_received: 0, |
56 | 52.1k | recent_data: Vec::new(), |
57 | 52.1k | max_recent_data_size: 0, |
58 | 52.1k | }, |
59 | 52.1k | ) |
60 | 52.1k | } |
61 | | |
62 | | /// Writer half of the pair. |
63 | | #[derive(Debug)] |
64 | | pub struct DropCloserWriteHalf { |
65 | | tx: Option<mpsc::Sender<Bytes>>, |
66 | | bytes_written: u64, |
67 | | eof_sent: Arc<AtomicBool>, |
68 | | } |
69 | | |
70 | | impl DropCloserWriteHalf { |
71 | | /// Sends data over the channel to the receiver. |
72 | 175k | pub fn send(&mut self, buf: Bytes) -> impl Future<Output = Result<(), Error>> + '_ { |
73 | 175k | self.send_get_bytes_on_error(buf).map_err(|err| err.0) |
74 | 175k | } |
75 | | |
76 | | /// Sends data over the channel to the receiver from a blocking thread. |
77 | 0 | pub fn blocking_send(&mut self, buf: Bytes) -> Result<(), Error> { |
78 | 0 | let tx = self |
79 | 0 | .tx |
80 | 0 | .as_ref() |
81 | 0 | .ok_or_else(|| make_err!(Code::Internal, "Tried to send while stream is closed"))?; |
82 | 0 | let buf_len = u64::try_from(buf.len()).err_tip(|| "Could not convert usize to u64")?; |
83 | 0 | if buf_len == 0 { |
84 | 0 | return Err(make_input_err!( |
85 | 0 | "Cannot send EOF in blocking_send(). Instead use send_eof()" |
86 | 0 | )); |
87 | 0 | } |
88 | 0 | if let Err(err) = tx.blocking_send(buf) { |
89 | 0 | self.tx = None; |
90 | 0 | return Err(make_err!( |
91 | 0 | Code::Internal, |
92 | 0 | "Failed to write to data, receiver disconnected: {} bytes", |
93 | 0 | err.0.len() |
94 | 0 | )); |
95 | 0 | } |
96 | 0 | self.bytes_written += buf_len; |
97 | 0 | Ok(()) |
98 | 0 | } |
99 | | |
100 | | /// Sends data over the channel to the receiver. |
101 | | #[inline] |
102 | 175k | async fn send_get_bytes_on_error(&mut self, buf: Bytes) -> Result<(), (Error, Bytes)>0 { |
103 | 175k | let tx = match self |
104 | 175k | .tx |
105 | 175k | .as_ref() |
106 | 175k | .ok_or_else(|| make_err!0 (Code::Internal0 , "Tried to send while stream is closed")) |
107 | | { |
108 | 175k | Ok(tx) => tx, |
109 | 0 | Err(e) => return Err((e, buf)), |
110 | | }; |
111 | 175k | let Ok(buf_len) = u64::try_from(buf.len()) else { |
112 | 0 | return Err(( |
113 | 0 | make_err!(Code::Internal, "Could not convert usize to u64"), |
114 | 0 | buf, |
115 | 0 | )); |
116 | | }; |
117 | 175k | if buf_len == 0 { |
118 | 0 | return Err(( |
119 | 0 | make_input_err!("Cannot send EOF in send(). Instead use send_eof()"), |
120 | 0 | buf, |
121 | 0 | )); |
122 | 175k | } |
123 | 175k | if let Err(err9 ) = tx.send(buf).await { |
124 | | // Close our channel. |
125 | 9 | self.tx = None; |
126 | 9 | return Err(( |
127 | 9 | make_err!( |
128 | 9 | Code::Internal, |
129 | 9 | "Failed to write to data, receiver disconnected" |
130 | 9 | ), |
131 | 9 | err.0, |
132 | 9 | )); |
133 | 175k | } |
134 | 175k | self.bytes_written += buf_len; |
135 | 175k | Ok(()) |
136 | 175k | } |
137 | | |
138 | | /// Binds a reader and a writer together. This will send all the data from the reader |
139 | | /// to the writer until an EOF is received. |
140 | | /// This will always read one message ahead to ensure that if an error happens |
141 | | /// on the EOF message it will not forward on the last payload message and instead |
142 | | /// forward on the error. |
143 | 1 | pub async fn bind_buffered(&mut self, reader: &mut DropCloserReadHalf) -> Result<(), Error> { |
144 | | loop { |
145 | 1 | let chunk = reader |
146 | 1 | .recv() |
147 | 1 | .await |
148 | 1 | .err_tip(|| "In DropCloserWriteHalf::bind_buffered::recv")?0 ; |
149 | 1 | if chunk.is_empty() { |
150 | 0 | self.send_eof() |
151 | 0 | .err_tip(|| "In DropCloserWriteHalf::bind_buffered::send_eof")?; |
152 | 0 | break; // EOF. |
153 | 1 | } |
154 | | // Always read one message ahead so if we get an error on our EOF |
155 | | // we forward it on to the reader. |
156 | 1 | if reader.peek().await.is_err() { |
157 | | // Read our next message for good book keeping. |
158 | 0 | drop( |
159 | 1 | reader |
160 | 1 | .recv() |
161 | 1 | .await |
162 | 1 | .err_tip(|| "In DropCloserWriteHalf::bind_buffered::peek::eof")?, |
163 | | ); |
164 | 0 | return Err(make_err!( |
165 | 0 | Code::Internal, |
166 | 0 | "DropCloserReadHalf::peek() said error, but when data received said Ok. This should never happen." |
167 | 0 | )); |
168 | 0 | } |
169 | 0 | match self.send_get_bytes_on_error(chunk).await { |
170 | 0 | Ok(()) => {} |
171 | 0 | Err(e) => { |
172 | 0 | reader.queued_data.push_front(e.1); |
173 | 0 | return Err(e.0).err_tip(|| "In DropCloserWriteHalf::bind_buffered::send"); |
174 | | } |
175 | | } |
176 | | } |
177 | 0 | Ok(()) |
178 | 1 | } |
179 | | |
180 | | /// Sends an EOF (End of File) message to the receiver which will gracefully let the |
181 | | /// stream know it has no more data. This will close the stream. |
182 | 51.8k | pub fn send_eof(&mut self) -> Result<(), Error> { |
183 | | // Flag that we have sent the EOF. |
184 | 51.8k | let eof_was_sent = self.eof_sent.swap(true, Ordering::Release); |
185 | 51.8k | if eof_was_sent { |
186 | 1 | warn!( |
187 | | "Stream already closed when eof already was sent. This is often ok for retry was triggered, but should not happen on happy path." |
188 | | ); |
189 | 1 | return Ok(()); |
190 | 51.8k | } |
191 | | |
192 | | // Now close our stream. |
193 | 51.8k | self.tx = None; |
194 | 51.8k | Ok(()) |
195 | 51.8k | } |
196 | | |
197 | | /// Returns the number of bytes written so far. This does not mean the receiver received |
198 | | /// all of the bytes written to the stream so far. |
199 | | #[must_use] |
200 | 0 | pub const fn get_bytes_written(&self) -> u64 { |
201 | 0 | self.bytes_written |
202 | 0 | } |
203 | | |
204 | | /// Returns if the pipe was broken. This is good for determining if the reader broke the |
205 | | /// pipe or the writer broke the pipe, since this will only return true if the pipe was |
206 | | /// broken by the writer. |
207 | | #[must_use] |
208 | 19 | pub const fn is_pipe_broken(&self) -> bool { |
209 | 19 | self.tx.is_none() |
210 | 19 | } |
211 | | } |
212 | | |
213 | | /// Reader half of the pair. |
214 | | #[derive(Debug)] |
215 | | pub struct DropCloserReadHalf { |
216 | | rx: mpsc::Receiver<Bytes>, |
217 | | /// Number of bytes received over the stream. |
218 | | bytes_received: u64, |
219 | | eof_sent: Arc<AtomicBool>, |
220 | | /// If there was an error in the stream, this will be set to the last error. |
221 | | last_err: Option<Error>, |
222 | | /// If not empty, this is the data that needs to be sent out before |
223 | | /// data from the underlying channel can should be sent. Already counted |
224 | | /// in `bytes_received`. |
225 | | queued_data: VecDeque<Bytes>, |
226 | | /// Data put back by `try_reset_stream`; counted again as it is read. |
227 | | replay_data: VecDeque<Bytes>, |
228 | | /// As data is being read from the stream, this buffer will be filled |
229 | | /// with the most recent data. Once `max_recent_data_size` is reached |
230 | | /// this buffer will be cleared and no longer be populated. |
231 | | /// This is useful if the caller wants to reset the the reader to before |
232 | | /// any of the data was received if possible (eg: something failed and |
233 | | /// we want to retry). |
234 | | recent_data: Vec<Bytes>, |
235 | | /// Amount of data to keep in the `recent_data` buffer before clearing it |
236 | | /// and no longer populating it. |
237 | | max_recent_data_size: u64, |
238 | | } |
239 | | |
240 | | impl DropCloserReadHalf { |
241 | | /// Returns if the stream has data ready. |
242 | 0 | pub fn is_empty(&self) -> bool { |
243 | 0 | self.rx.is_empty() |
244 | 0 | } |
245 | | |
246 | 227k | fn recv_inner(&mut self, chunk: Bytes) -> Result<Bytes, Error> { |
247 | | // `queued_data` is allowed to have empty bytes that represent EOF |
248 | 227k | if chunk.is_empty() && !51.6k self.eof_sent51.6k .load(Ordering::Acquire) { |
249 | 253 | let err = make_err!(Code::Internal, "Sender dropped before sending EOF"); |
250 | 253 | self.queued_data.clear(); |
251 | 253 | self.replay_data.clear(); |
252 | 253 | self.recent_data.clear(); |
253 | 253 | self.bytes_received = 0; |
254 | 253 | self.last_err = Some(err.clone()); |
255 | 253 | return Err(err); |
256 | 226k | } |
257 | 226k | Ok(self.count_received(chunk)) |
258 | 227k | } |
259 | | |
260 | | /// Counts a chunk handed out for the first time since the last reset. |
261 | 226k | fn count_received(&mut self, chunk: Bytes) -> Bytes { |
262 | 226k | if chunk.is_empty() { |
263 | 51.3k | self.maybe_populate_recent_data(&ZERO_DATA); |
264 | 51.3k | return ZERO_DATA; |
265 | 175k | } |
266 | 175k | self.bytes_received += chunk.len() as u64; |
267 | 175k | self.maybe_populate_recent_data(&chunk); |
268 | 175k | chunk |
269 | 226k | } |
270 | | |
271 | | /// Try to receive a chunk of data, returning `None` if none is available. |
272 | 252k | pub fn try_recv(&mut self) -> Option<Result<Bytes, Error>> { |
273 | 252k | if let Some(err2 ) = &self.last_err { |
274 | 2 | return Some(Err(err.clone())); |
275 | 252k | } |
276 | 252k | if let Some(chunk25.6k ) = self.queued_data.pop_front() { |
277 | 25.6k | return Some(Ok(chunk)); |
278 | 227k | } |
279 | 227k | let chunk17 = self.replay_data.pop_front()?227k ; |
280 | 17 | Some(Ok(self.count_received(chunk))) |
281 | 252k | } |
282 | | |
283 | | /// Receive a chunk of data, waiting asynchronously until some is available. |
284 | 252k | pub async fn recv(&mut self) -> Result<Bytes, Error> { |
285 | 252k | if let Some(result25.6k ) = self.try_recv() { |
286 | 25.6k | result |
287 | | } else { |
288 | | // `None` here indicates EOF, which we represent as Zero data |
289 | 227k | let data227k = self.rx.recv().await.unwrap_or227k (ZERO_DATA227k ); |
290 | 227k | self.recv_inner(data) |
291 | | } |
292 | 252k | } |
293 | | |
294 | | /// Receive a chunk of data from a blocking thread. |
295 | 0 | pub fn blocking_recv(&mut self) -> Result<Bytes, Error> { |
296 | 0 | if let Some(result) = self.try_recv() { |
297 | 0 | result |
298 | | } else { |
299 | | // `None` here indicates EOF, which we represent as Zero data |
300 | 0 | let data = self.rx.blocking_recv().unwrap_or(ZERO_DATA); |
301 | 0 | self.recv_inner(data) |
302 | | } |
303 | 0 | } |
304 | | |
305 | 226k | fn maybe_populate_recent_data(&mut self, chunk: &Bytes) { |
306 | 226k | if self.max_recent_data_size == 0 { |
307 | 111k | return; // Fast path. |
308 | 115k | } |
309 | 115k | if self.bytes_received > self.max_recent_data_size { |
310 | 62.9k | if !self.recent_data.is_empty() { |
311 | 1 | self.recent_data.clear(); |
312 | 62.9k | } |
313 | 62.9k | return; |
314 | 52.7k | } |
315 | 52.7k | self.recent_data.push(chunk.clone()); |
316 | 226k | } |
317 | | |
318 | | /// Sets the maximum size of the `recent_data` buffer. If the number of bytes |
319 | | /// received exceeds this size, the `recent_data` buffer will be cleared and |
320 | | /// no longer populated. |
321 | 0 | pub const fn set_max_recent_data_size(&mut self, size: u64) { |
322 | 0 | self.max_recent_data_size = size; |
323 | 0 | } |
324 | | |
325 | | /// Attempts to reset the stream to before any data was received. This will |
326 | | /// only work if the number of bytes received is less than `max_recent_data_size`. |
327 | | /// |
328 | | /// On error the state of the stream is undefined and the caller should not |
329 | | /// attempt to use the stream again. |
330 | 9 | pub fn try_reset_stream(&mut self) -> Result<(), Error> { |
331 | 9 | if self.bytes_received > self.max_recent_data_size { |
332 | 0 | return Err(make_err!( |
333 | 0 | Code::Internal, |
334 | 0 | "Cannot reset stream, max_recent_data_size exceeded" |
335 | 0 | )); |
336 | 9 | } |
337 | 9 | let data_sum: u64 = self |
338 | 9 | .recent_data |
339 | 9 | .iter() |
340 | 20 | .map9 (|chunk| chunk.len() as u64) |
341 | 9 | .sum(); |
342 | | // Ensure the sum of the bytes in recent_data is equal to the bytes_received. |
343 | 0 | error_if!( |
344 | 9 | data_sum != self.bytes_received, |
345 | | "Sum of recent_data bytes does not equal bytes_received" |
346 | | ); |
347 | | // `queued_data` only holds chunks already in `recent_data`. |
348 | 9 | self.queued_data.clear(); |
349 | 20 | for chunk in self.recent_data9 .drain9 (..9 ).rev9 () { |
350 | 20 | self.replay_data.push_front(chunk); |
351 | 20 | } |
352 | 9 | self.bytes_received = 0; |
353 | 9 | Ok(()) |
354 | 9 | } |
355 | | |
356 | | /// Drains the reader until an EOF is received, but sends data to the void. |
357 | 1.29k | pub async fn drain(&mut self) -> Result<u64, Error>0 { |
358 | 1.29k | let mut total_bytes: u64 = 0; |
359 | | loop { |
360 | 1.39k | let bytes1.38k = self |
361 | 1.39k | .recv() |
362 | 1.39k | .await |
363 | 1.38k | .err_tip(|| "Failed to drain in buf_channel::drain")?3 ; |
364 | 1.38k | if bytes.is_empty() { |
365 | 1.29k | break; // EOF. |
366 | 94 | } |
367 | 94 | total_bytes += bytes.len().try_into().unwrap_or(0); |
368 | | } |
369 | 1.29k | Ok(total_bytes) |
370 | 1.29k | } |
371 | | |
372 | | /// Peek the next set of bytes in the stream without consuming them. |
373 | 49.3k | pub async fn peek(&mut self) -> Result<&Bytes, Error> { |
374 | 49.3k | if self.queued_data.is_empty() { |
375 | 49.3k | let chunk49.3k = self.recv().await.err_tip49.3k (|| "In buf_channel::peek")?64 ; |
376 | 49.3k | self.queued_data.push_front(chunk); |
377 | 0 | } |
378 | 49.3k | Ok(self |
379 | 49.3k | .queued_data |
380 | 49.3k | .front() |
381 | 49.3k | .expect("Should have data in the queue")) |
382 | 49.3k | } |
383 | | |
384 | | /// The number of bytes received over this stream so far. |
385 | 0 | pub const fn get_bytes_received(&self) -> u64 { |
386 | 0 | self.bytes_received |
387 | 0 | } |
388 | | |
389 | | /// Takes exactly `size` number of bytes from the stream and returns them. |
390 | | /// This means the stream will keep polling until either an EOF is received or |
391 | | /// `size` bytes are received and concat them all together then return them. |
392 | | /// This method is optimized to reduce copies when possible. |
393 | | /// If `size` is None, it will take all the bytes in the stream. |
394 | 69.3k | pub async fn consume(&mut self, size: Option<usize>) -> Result<Bytes, Error>0 { |
395 | 69.3k | let size = size.unwrap_or(usize::MAX); |
396 | 1.69k | let first_chunk = { |
397 | 69.3k | let mut chunk69.1k = self |
398 | 69.3k | .recv() |
399 | 69.3k | .await |
400 | 69.2k | .err_tip(|| "During first read of buf_channel::take()")?127 ; |
401 | 69.1k | if chunk.is_empty() { |
402 | 550 | return Ok(chunk); // EOF. |
403 | 68.6k | } |
404 | 68.6k | if chunk.len() > size { |
405 | 21.0k | let remaining = chunk.split_off(size); |
406 | 21.0k | self.queued_data.push_front(remaining); |
407 | | // No need to read EOF if we are a partial chunk. |
408 | 21.0k | return Ok(chunk); |
409 | 47.5k | } |
410 | | // Try to read our EOF to ensure our sender did not error out. |
411 | 47.5k | match self.peek().await { |
412 | 47.5k | Ok(peeked_chunk) => { |
413 | 47.5k | if peeked_chunk.is_empty() || chunk1.88k .len() == size { |
414 | 45.8k | return Ok(chunk); |
415 | 1.69k | } |
416 | | } |
417 | 35 | Err(e) => { |
418 | 35 | return Err(e).err_tip(|| "Failed to check if next chunk is EOF")?; |
419 | | } |
420 | | } |
421 | 1.69k | chunk |
422 | | }; |
423 | 1.69k | let mut output = BytesMut::new(); |
424 | 1.69k | output.extend_from_slice(&first_chunk); |
425 | | |
426 | | loop { |
427 | 124k | let mut chunk124k = self |
428 | 124k | .recv() |
429 | 124k | .await |
430 | 124k | .err_tip(|| "During next read of buf_channel::take()")?5 ; |
431 | 124k | if chunk.is_empty() { |
432 | 761 | break; // EOF. |
433 | 124k | } |
434 | 124k | if output.len() + chunk.len() > size { |
435 | 104 | // Slice off the extra data and put it back into the queue. We are done. |
436 | 104 | let remaining = chunk.split_off(size - output.len()); |
437 | 104 | self.queued_data.push_front(remaining); |
438 | 123k | } |
439 | 124k | output.extend_from_slice(&chunk); |
440 | 124k | if output.len() == size { |
441 | 930 | break; // We are done. |
442 | 123k | } |
443 | | } |
444 | 1.69k | Ok(output.freeze()) |
445 | 69.2k | } |
446 | | } |
447 | | |
448 | | impl Stream for DropCloserReadHalf { |
449 | | type Item = Result<Bytes, std::io::Error>; |
450 | | |
451 | | // TODO(palfrey) This is not very efficient as we are creating a new future on every |
452 | | // poll() call. It might be better to use a waker. |
453 | 110 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { |
454 | 110 | Box::pin(self.recv()) |
455 | 110 | .as_mut() |
456 | 110 | .poll(cx) |
457 | 110 | .map(|result| match result100 { |
458 | 100 | Ok(bytes) => { |
459 | 100 | if bytes.is_empty() { |
460 | 56 | return None; |
461 | 44 | } |
462 | 44 | Some(Ok(bytes)) |
463 | | } |
464 | 0 | Err(e) => Some(Err(e.to_std_err())), |
465 | 100 | }) |
466 | 110 | } |
467 | | } |