Coverage Report

Created: 2026-07-21 15:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}