/build/source/nativelink-util/src/buf_channel.rs
Line | Count | Source |
1 | | // Copyright 2024 The NativeLink Authors. 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 | 16.8k | 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 | 16.8k | let (tx, rx) = mpsc::channel(2); |
42 | 16.8k | let eof_sent = Arc::new(AtomicBool::new(false)); |
43 | 16.8k | ( |
44 | 16.8k | DropCloserWriteHalf { |
45 | 16.8k | tx: Some(tx), |
46 | 16.8k | bytes_written: 0, |
47 | 16.8k | eof_sent: eof_sent.clone(), |
48 | 16.8k | }, |
49 | 16.8k | DropCloserReadHalf { |
50 | 16.8k | rx, |
51 | 16.8k | queued_data: VecDeque::new(), |
52 | 16.8k | last_err: None, |
53 | 16.8k | eof_sent, |
54 | 16.8k | bytes_received: 0, |
55 | 16.8k | recent_data: Vec::new(), |
56 | 16.8k | max_recent_data_size: 0, |
57 | 16.8k | }, |
58 | 16.8k | ) |
59 | 16.8k | } |
60 | | |
61 | | /// Writer half of the pair. |
62 | | #[derive(Debug)] |
63 | | pub struct DropCloserWriteHalf { |
64 | | tx: Option<mpsc::Sender<Bytes>>, |
65 | | bytes_written: u64, |
66 | | eof_sent: Arc<AtomicBool>, |
67 | | } |
68 | | |
69 | | impl DropCloserWriteHalf { |
70 | | /// Sends data over the channel to the receiver. |
71 | 136k | pub fn send(&mut self, buf: Bytes) -> impl Future<Output = Result<(), Error>> + '_ { |
72 | 136k | self.send_get_bytes_on_error(buf).map_err(|err| err.0) |
73 | 136k | } |
74 | | |
75 | | /// Sends data over the channel to the receiver from a blocking thread. |
76 | 0 | pub fn blocking_send(&mut self, buf: Bytes) -> Result<(), Error> { |
77 | 0 | let tx = self |
78 | 0 | .tx |
79 | 0 | .as_ref() |
80 | 0 | .ok_or_else(|| make_err!(Code::Internal, "Tried to send while stream is closed"))?; |
81 | 0 | let buf_len = u64::try_from(buf.len()).err_tip(|| "Could not convert usize to u64")?; |
82 | 0 | if buf_len == 0 { |
83 | 0 | return Err(make_input_err!( |
84 | 0 | "Cannot send EOF in blocking_send(). Instead use send_eof()" |
85 | 0 | )); |
86 | 0 | } |
87 | 0 | if let Err(err) = tx.blocking_send(buf) { |
88 | 0 | self.tx = None; |
89 | 0 | return Err(make_err!( |
90 | 0 | Code::Internal, |
91 | 0 | "Failed to write to data, receiver disconnected: {} bytes", |
92 | 0 | err.0.len() |
93 | 0 | )); |
94 | 0 | } |
95 | 0 | self.bytes_written += buf_len; |
96 | 0 | Ok(()) |
97 | 0 | } |
98 | | |
99 | | /// Sends data over the channel to the receiver. |
100 | | #[inline] |
101 | 136k | async fn send_get_bytes_on_error(&mut self, buf: Bytes) -> Result<(), (Error, Bytes)> { |
102 | 136k | let tx = match self |
103 | 136k | .tx |
104 | 136k | .as_ref() |
105 | 136k | .ok_or_else(|| make_err!0 (Code::Internal0 , "Tried to send while stream is closed")) |
106 | | { |
107 | 136k | Ok(tx) => tx, |
108 | 0 | Err(e) => return Err((e, buf)), |
109 | | }; |
110 | 136k | let Ok(buf_len) = u64::try_from(buf.len()) else { |
111 | 0 | return Err(( |
112 | 0 | make_err!(Code::Internal, "Could not convert usize to u64"), |
113 | 0 | buf, |
114 | 0 | )); |
115 | | }; |
116 | 136k | if buf_len == 0 { |
117 | 0 | return Err(( |
118 | 0 | make_input_err!("Cannot send EOF in send(). Instead use send_eof()"), |
119 | 0 | buf, |
120 | 0 | )); |
121 | 136k | } |
122 | 136k | if let Err(err0 ) = tx.send(buf).await { |
123 | | // Close our channel. |
124 | 0 | self.tx = None; |
125 | 0 | return Err(( |
126 | 0 | make_err!( |
127 | 0 | Code::Internal, |
128 | 0 | "Failed to write to data, receiver disconnected" |
129 | 0 | ), |
130 | 0 | err.0, |
131 | 0 | )); |
132 | 136k | } |
133 | 136k | self.bytes_written += buf_len; |
134 | 136k | Ok(()) |
135 | 136k | } |
136 | | |
137 | | /// Binds a reader and a writer together. This will send all the data from the reader |
138 | | /// to the writer until an EOF is received. |
139 | | /// This will always read one message ahead to ensure that if an error happens |
140 | | /// on the EOF message it will not forward on the last payload message and instead |
141 | | /// forward on the error. |
142 | 7 | pub async fn bind_buffered(&mut self, reader: &mut DropCloserReadHalf) -> Result<(), Error> { |
143 | | loop { |
144 | 114 | let chunk = reader |
145 | 114 | .recv() |
146 | 114 | .await |
147 | 114 | .err_tip(|| "In DropCloserWriteHalf::bind_buffered::recv")?0 ; |
148 | 114 | if chunk.is_empty() { |
149 | 6 | self.send_eof() |
150 | 6 | .err_tip(|| "In DropCloserWriteHalf::bind_buffered::send_eof")?0 ; |
151 | 6 | break; // EOF. |
152 | 108 | } |
153 | | // Always read one message ahead so if we get an error on our EOF |
154 | | // we forward it on to the reader. |
155 | 108 | if reader.peek().await.is_err() { |
156 | | // Read our next message for good book keeping. |
157 | 0 | drop( |
158 | 1 | reader |
159 | 1 | .recv() |
160 | 1 | .await |
161 | 1 | .err_tip(|| "In DropCloserWriteHalf::bind_buffered::peek::eof")?, |
162 | | ); |
163 | 0 | return Err(make_err!( |
164 | 0 | Code::Internal, |
165 | 0 | "DropCloserReadHalf::peek() said error, but when data received said Ok. This should never happen." |
166 | 0 | )); |
167 | 107 | } |
168 | 107 | match self.send_get_bytes_on_error(chunk).await { |
169 | 107 | Ok(()) => {} |
170 | 0 | Err(e) => { |
171 | 0 | reader.queued_data.push_front(e.1); |
172 | 0 | return Err(e.0).err_tip(|| "In DropCloserWriteHalf::bind_buffered::send"); |
173 | | } |
174 | | } |
175 | | } |
176 | 6 | Ok(()) |
177 | 7 | } |
178 | | |
179 | | /// Sends an EOF (End of File) message to the receiver which will gracefully let the |
180 | | /// stream know it has no more data. This will close the stream. |
181 | 16.7k | pub fn send_eof(&mut self) -> Result<(), Error> { |
182 | | // Flag that we have sent the EOF. |
183 | 16.7k | let eof_was_sent = self.eof_sent.swap(true, Ordering::Release); |
184 | 16.7k | if eof_was_sent { |
185 | 1 | warn!( |
186 | | "Stream already closed when eof already was sent. This is often ok for retry was triggered, but should not happen on happy path." |
187 | | ); |
188 | 1 | return Ok(()); |
189 | 16.7k | } |
190 | | |
191 | | // Now close our stream. |
192 | 16.7k | self.tx = None; |
193 | 16.7k | Ok(()) |
194 | 16.7k | } |
195 | | |
196 | | /// Returns the number of bytes written so far. This does not mean the receiver received |
197 | | /// all of the bytes written to the stream so far. |
198 | | #[must_use] |
199 | 539 | pub const fn get_bytes_written(&self) -> u64 { |
200 | 539 | self.bytes_written |
201 | 539 | } |
202 | | |
203 | | /// Returns if the pipe was broken. This is good for determining if the reader broke the |
204 | | /// pipe or the writer broke the pipe, since this will only return true if the pipe was |
205 | | /// broken by the writer. |
206 | | #[must_use] |
207 | 2 | pub const fn is_pipe_broken(&self) -> bool { |
208 | 2 | self.tx.is_none() |
209 | 2 | } |
210 | | } |
211 | | |
212 | | /// Reader half of the pair. |
213 | | #[derive(Debug)] |
214 | | pub struct DropCloserReadHalf { |
215 | | rx: mpsc::Receiver<Bytes>, |
216 | | /// Number of bytes received over the stream. |
217 | | bytes_received: u64, |
218 | | eof_sent: Arc<AtomicBool>, |
219 | | /// If there was an error in the stream, this will be set to the last error. |
220 | | last_err: Option<Error>, |
221 | | /// If not empty, this is the data that needs to be sent out before |
222 | | /// data from the underlying channel can should be sent. |
223 | | queued_data: VecDeque<Bytes>, |
224 | | /// As data is being read from the stream, this buffer will be filled |
225 | | /// with the most recent data. Once `max_recent_data_size` is reached |
226 | | /// this buffer will be cleared and no longer be populated. |
227 | | /// This is useful if the caller wants to reset the the reader to before |
228 | | /// any of the data was received if possible (eg: something failed and |
229 | | /// we want to retry). |
230 | | recent_data: Vec<Bytes>, |
231 | | /// Amount of data to keep in the `recent_data` buffer before clearing it |
232 | | /// and no longer populating it. |
233 | | max_recent_data_size: u64, |
234 | | } |
235 | | |
236 | | impl DropCloserReadHalf { |
237 | | /// Returns if the stream has data ready. |
238 | 0 | pub fn is_empty(&self) -> bool { |
239 | 0 | self.rx.is_empty() |
240 | 0 | } |
241 | | |
242 | 152k | fn recv_inner(&mut self, chunk: Bytes) -> Result<Bytes, Error> { |
243 | | // `queued_data` is allowed to have empty bytes that represent EOF |
244 | 152k | if chunk.is_empty() { |
245 | 16.1k | if !self.eof_sent.load(Ordering::Acquire) { |
246 | 52 | let err = make_err!(Code::Internal, "Sender dropped before sending EOF"); |
247 | 52 | self.queued_data.clear(); |
248 | 52 | self.recent_data.clear(); |
249 | 52 | self.bytes_received = 0; |
250 | 52 | self.last_err = Some(err.clone()); |
251 | 52 | return Err(err); |
252 | 16.1k | } |
253 | | |
254 | 16.1k | self.maybe_populate_recent_data(&ZERO_DATA); |
255 | 16.1k | return Ok(ZERO_DATA); |
256 | 136k | } |
257 | | |
258 | 136k | self.bytes_received += chunk.len() as u64; |
259 | 136k | self.maybe_populate_recent_data(&chunk); |
260 | 136k | Ok(chunk) |
261 | 152k | } |
262 | | |
263 | | /// Try to receive a chunk of data, returning `None` if none is available. |
264 | 177k | pub fn try_recv(&mut self) -> Option<Result<Bytes, Error>> { |
265 | 177k | if let Some(err2 ) = &self.last_err { |
266 | 2 | return Some(Err(err.clone())); |
267 | 177k | } |
268 | 177k | self.queued_data.pop_front().map(Ok) |
269 | 177k | } |
270 | | |
271 | | /// Receive a chunk of data, waiting asynchronously until some is available. |
272 | 177k | pub async fn recv(&mut self) -> Result<Bytes, Error> { |
273 | 177k | if let Some(result25.2k ) = self.try_recv() { |
274 | 25.2k | result |
275 | | } else { |
276 | | // `None` here indicates EOF, which we represent as Zero data |
277 | 152k | let data152k = self.rx.recv().await.unwrap_or152k (ZERO_DATA152k ); |
278 | 152k | self.recv_inner(data) |
279 | | } |
280 | 177k | } |
281 | | |
282 | | /// Receive a chunk of data from a blocking thread. |
283 | 0 | pub fn blocking_recv(&mut self) -> Result<Bytes, Error> { |
284 | 0 | if let Some(result) = self.try_recv() { |
285 | 0 | result |
286 | | } else { |
287 | | // `None` here indicates EOF, which we represent as Zero data |
288 | 0 | let data = self.rx.blocking_recv().unwrap_or(ZERO_DATA); |
289 | 0 | self.recv_inner(data) |
290 | | } |
291 | 0 | } |
292 | | |
293 | 152k | fn maybe_populate_recent_data(&mut self, chunk: &Bytes) { |
294 | 152k | if self.max_recent_data_size == 0 { |
295 | 36.8k | return; // Fast path. |
296 | 115k | } |
297 | 115k | if self.bytes_received > self.max_recent_data_size { |
298 | 62.9k | if !self.recent_data.is_empty() { |
299 | 1 | self.recent_data.clear(); |
300 | 62.9k | } |
301 | 62.9k | return; |
302 | 52.5k | } |
303 | 52.5k | self.recent_data.push(chunk.clone()); |
304 | 152k | } |
305 | | |
306 | | /// Sets the maximum size of the `recent_data` buffer. If the number of bytes |
307 | | /// received exceeds this size, the `recent_data` buffer will be cleared and |
308 | | /// no longer populated. |
309 | 8 | pub const fn set_max_recent_data_size(&mut self, size: u64) { |
310 | 8 | self.max_recent_data_size = size; |
311 | 8 | } |
312 | | |
313 | | /// Attempts to reset the stream to before any data was received. This will |
314 | | /// only work if the number of bytes received is less than `max_recent_data_size`. |
315 | | /// |
316 | | /// On error the state of the stream is undefined and the caller should not |
317 | | /// attempt to use the stream again. |
318 | 2 | pub fn try_reset_stream(&mut self) -> Result<(), Error> { |
319 | 2 | if self.bytes_received > self.max_recent_data_size { |
320 | 0 | return Err(make_err!( |
321 | 0 | Code::Internal, |
322 | 0 | "Cannot reset stream, max_recent_data_size exceeded" |
323 | 0 | )); |
324 | 2 | } |
325 | 2 | let mut data_sum = 0; |
326 | 2 | for chunk in self.recent_data.drain(..).rev() { |
327 | 2 | data_sum += chunk.len() as u64; |
328 | 2 | self.queued_data.push_front(chunk); |
329 | 2 | } |
330 | 2 | assert!(self.recent_data.is_empty(), "Recent_data should be empty"); |
331 | | // Ensure the sum of the bytes in recent_data is equal to the bytes_received. |
332 | 0 | error_if!( |
333 | 2 | data_sum != self.bytes_received, |
334 | | "Sum of recent_data bytes does not equal bytes_received" |
335 | | ); |
336 | 2 | self.bytes_received = 0; |
337 | 2 | Ok(()) |
338 | 2 | } |
339 | | |
340 | | /// Drains the reader until an EOF is received, but sends data to the void. |
341 | 1.01k | pub async fn drain(&mut self) -> Result<u64, Error> { |
342 | 1.01k | let mut total_bytes: u64 = 0; |
343 | | loop { |
344 | 1.02k | let bytes1.02k = self |
345 | 1.02k | .recv() |
346 | 1.02k | .await |
347 | 1.02k | .err_tip(|| "Failed to drain in buf_channel::drain")?0 ; |
348 | 1.02k | if bytes.is_empty() { |
349 | 1.01k | break; // EOF. |
350 | 9 | } |
351 | 9 | total_bytes += bytes.len().try_into().unwrap_or(0); |
352 | | } |
353 | 1.01k | Ok(total_bytes) |
354 | 1.01k | } |
355 | | |
356 | | /// Peek the next set of bytes in the stream without consuming them. |
357 | 15.1k | pub async fn peek(&mut self) -> Result<&Bytes, Error> { |
358 | 15.1k | if self.queued_data.is_empty() { |
359 | 15.1k | let chunk15.1k = self.recv().await.err_tip15.1k (|| "In buf_channel::peek")?8 ; |
360 | 15.1k | self.queued_data.push_front(chunk); |
361 | 1 | } |
362 | 15.1k | Ok(self |
363 | 15.1k | .queued_data |
364 | 15.1k | .front() |
365 | 15.1k | .expect("Should have data in the queue")) |
366 | 15.1k | } |
367 | | |
368 | | /// The number of bytes received over this stream so far. |
369 | 3 | pub const fn get_bytes_received(&self) -> u64 { |
370 | 3 | self.bytes_received |
371 | 3 | } |
372 | | |
373 | | /// Takes exactly `size` number of bytes from the stream and returns them. |
374 | | /// This means the stream will keep polling until either an EOF is received or |
375 | | /// `size` bytes are received and concat them all together then return them. |
376 | | /// This method is optimized to reduce copies when possible. |
377 | | /// If `size` is None, it will take all the bytes in the stream. |
378 | 34.9k | pub async fn consume(&mut self, size: Option<usize>) -> Result<Bytes, Error> { |
379 | 34.9k | let size = size.unwrap_or(usize::MAX); |
380 | 1.66k | let first_chunk = { |
381 | 34.9k | let mut chunk34.9k = self |
382 | 34.9k | .recv() |
383 | 34.9k | .await |
384 | 34.9k | .err_tip(|| "During first read of buf_channel::take()")?33 ; |
385 | 34.9k | if chunk.is_empty() { |
386 | 379 | return Ok(chunk); // EOF. |
387 | 34.5k | } |
388 | 34.5k | if chunk.len() > size { |
389 | 20.9k | let remaining = chunk.split_off(size); |
390 | 20.9k | self.queued_data.push_front(remaining); |
391 | | // No need to read EOF if we are a partial chunk. |
392 | 20.9k | return Ok(chunk); |
393 | 13.6k | } |
394 | | // Try to read our EOF to ensure our sender did not error out. |
395 | 13.6k | match self.peek().await { |
396 | 13.6k | Ok(peeked_chunk) => { |
397 | 13.6k | if peeked_chunk.is_empty() || chunk1.84k .len() == size { |
398 | 11.9k | return Ok(chunk); |
399 | 1.66k | } |
400 | | } |
401 | 6 | Err(e) => { |
402 | 6 | return Err(e).err_tip(|| "Failed to check if next chunk is EOF")?; |
403 | | } |
404 | | } |
405 | 1.66k | chunk |
406 | | }; |
407 | 1.66k | let mut output = BytesMut::new(); |
408 | 1.66k | output.extend_from_slice(&first_chunk); |
409 | | |
410 | | loop { |
411 | 120k | let mut chunk120k = self |
412 | 120k | .recv() |
413 | 120k | .await |
414 | 120k | .err_tip(|| "During next read of buf_channel::take()")?2 ; |
415 | 120k | if chunk.is_empty() { |
416 | 728 | break; // EOF. |
417 | 119k | } |
418 | 119k | if output.len() + chunk.len() > size { |
419 | 104 | // Slice off the extra data and put it back into the queue. We are done. |
420 | 104 | let remaining = chunk.split_off(size - output.len()); |
421 | 104 | self.queued_data.push_front(remaining); |
422 | 119k | } |
423 | 119k | output.extend_from_slice(&chunk); |
424 | 119k | if output.len() == size { |
425 | 930 | break; // We are done. |
426 | 118k | } |
427 | | } |
428 | 1.65k | Ok(output.freeze()) |
429 | 34.9k | } |
430 | | } |
431 | | |
432 | | impl Stream for DropCloserReadHalf { |
433 | | type Item = Result<Bytes, std::io::Error>; |
434 | | |
435 | | // TODO(palfrey) This is not very efficient as we are creating a new future on every |
436 | | // poll() call. It might be better to use a waker. |
437 | 152 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { |
438 | 152 | Box::pin(self.recv()) |
439 | 152 | .as_mut() |
440 | 152 | .poll(cx) |
441 | 152 | .map(|result| match result116 { |
442 | 115 | Ok(bytes) => { |
443 | 115 | if bytes.is_empty() { |
444 | 33 | return None; |
445 | 82 | } |
446 | 82 | Some(Ok(bytes)) |
447 | | } |
448 | 1 | Err(e) => Some(Err(e.to_std_err())), |
449 | 116 | }) |
450 | 152 | } |
451 | | } |