1use crate::{ProgressEntry, ProgressListener, Pusher};
5use bytes::{Bytes, BytesMut};
6
7#[derive(Debug)]
33pub struct BufWriterPusher<P> {
34 inner: P,
35 buf: BytesMut,
36 capacity: usize,
37 run_start: u64,
40}
41
42impl<P: Pusher> BufWriterPusher<P> {
43 #[must_use]
45 pub fn new(inner: P, capacity: usize) -> Self {
46 Self {
47 inner,
48 buf: BytesMut::with_capacity(capacity),
49 capacity,
50 run_start: 0,
51 }
52 }
53
54 fn flush_buf(&mut self) -> Result<(), P::Error> {
59 if self.buf.is_empty() {
60 return Ok(());
61 }
62 let start = self.run_start;
63 let len = self.buf.len();
64 let chunk: Bytes = self.buf.split().freeze();
67 match self.inner.push(&(start..start + len as u64), chunk) {
68 Ok(()) => Ok(()),
69 Err((e, rem)) => {
70 let written = len.saturating_sub(rem.len());
71 self.buf.extend_from_slice(&rem);
72 self.run_start = start + written as u64;
73 Err(e)
74 }
75 }
76 }
77}
78
79impl<P: Pusher> Pusher for BufWriterPusher<P> {
80 type Error = P::Error;
81
82 fn set_listener(&mut self, cb: ProgressListener) {
83 self.inner.set_listener(cb);
84 }
85
86 fn push(&mut self, range: &ProgressEntry, bytes: Bytes) -> Result<(), (Self::Error, Bytes)> {
87 if bytes.is_empty() {
88 return Ok(());
89 }
90
91 if !self.buf.is_empty()
94 && (range.start != self.run_start + self.buf.len() as u64
95 || self.buf.len() + bytes.len() > self.capacity)
96 && let Err(e) = self.flush_buf()
97 {
98 return Err((e, bytes));
101 }
102
103 if self.buf.is_empty() {
104 self.run_start = range.start;
106 }
107
108 if bytes.len() >= self.capacity {
110 return self.inner.push(range, bytes);
111 }
112
113 self.buf.extend_from_slice(bytes.as_ref());
114 Ok(())
115 }
116
117 fn flush(&mut self) -> Result<(), Self::Error> {
118 self.flush_buf()?;
119 self.inner.flush()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 #![allow(clippy::unwrap_used)]
126 use super::*;
127 use std::sync::{Arc, Mutex};
128
129 type PushLog = Arc<Mutex<Vec<(u64, u64, Vec<u8>)>>>;
131
132 #[derive(Clone, Debug, Default)]
134 struct RecordingPusher {
135 log: PushLog,
136 }
137 impl Pusher for RecordingPusher {
138 type Error = std::io::Error;
139 fn push(
140 &mut self,
141 range: &ProgressEntry,
142 bytes: Bytes,
143 ) -> Result<(), (Self::Error, Bytes)> {
144 self.log
145 .lock()
146 .unwrap()
147 .push((range.start, range.end, bytes.to_vec()));
148 Ok(())
149 }
150 }
151
152 #[derive(Clone, Debug)]
154 struct FlakyPusher {
155 log: PushLog,
156 did_fail: Arc<std::sync::atomic::AtomicBool>,
157 }
158 impl Pusher for FlakyPusher {
159 type Error = std::io::Error;
160 fn push(
161 &mut self,
162 range: &ProgressEntry,
163 bytes: Bytes,
164 ) -> Result<(), (Self::Error, Bytes)> {
165 if self.did_fail.load(std::sync::atomic::Ordering::Relaxed) {
166 self.log
167 .lock()
168 .unwrap()
169 .push((range.start, range.end, bytes.to_vec()));
170 return Ok(());
171 }
172 self.did_fail
173 .store(true, std::sync::atomic::Ordering::Relaxed);
174 Err((std::io::Error::other("boom"), bytes))
175 }
176 }
177
178 #[derive(Clone, Debug)]
181 struct PartialPusher {
182 log: PushLog,
183 wrote_partial: Arc<std::sync::atomic::AtomicBool>,
184 }
185 impl Pusher for PartialPusher {
186 type Error = std::io::Error;
187 fn push(
188 &mut self,
189 range: &ProgressEntry,
190 bytes: Bytes,
191 ) -> Result<(), (Self::Error, Bytes)> {
192 self.log
193 .lock()
194 .unwrap()
195 .push((range.start, range.end, bytes.to_vec()));
196 if !self
197 .wrote_partial
198 .swap(true, std::sync::atomic::Ordering::Relaxed)
199 {
200 let rem = bytes.slice(2..);
202 return Err((std::io::Error::other("partial"), rem));
203 }
204 Ok(())
205 }
206 }
207
208 #[derive(Default)]
211 struct ListenerRecordingPusher {
212 fired: Arc<Mutex<Vec<(u64, u64)>>>,
213 listener: Option<ProgressListener>,
214 }
215 impl Pusher for ListenerRecordingPusher {
216 type Error = std::io::Error;
217 fn set_listener(&mut self, cb: ProgressListener) {
218 self.listener = Some(cb);
219 }
220 fn push(
221 &mut self,
222 range: &ProgressEntry,
223 _bytes: Bytes,
224 ) -> Result<(), (Self::Error, Bytes)> {
225 if let Some(cb) = &mut self.listener {
226 cb(range.clone());
227 }
228 self.fired.lock().unwrap().push((range.start, range.end));
229 Ok(())
230 }
231 }
232
233 #[test]
234 fn contiguous_writes_coalesce_into_one_push() {
235 let log = Arc::new(Mutex::new(Vec::new()));
236 let mut bp = BufWriterPusher::new(RecordingPusher { log: log.clone() }, 1024);
237 bp.push(&(0..4), Bytes::from_static(b"abcd")).unwrap();
238 bp.push(&(4..8), Bytes::from_static(b"efgh")).unwrap();
239 bp.flush().unwrap();
240
241 let l = log.lock().unwrap();
242 assert_eq!(l.len(), 1, "expected a single coalesced push");
243 assert_eq!(l[0], (0, 8, b"abcdefgh".to_vec()));
244 drop(l);
245 }
246
247 #[test]
248 fn noncontiguous_write_flushes_existing_run() {
249 let log = Arc::new(Mutex::new(Vec::new()));
250 let mut bp = BufWriterPusher::new(RecordingPusher { log: log.clone() }, 1024);
251 bp.push(&(0..4), Bytes::from_static(b"abcd")).unwrap();
252 bp.push(&(10..14), Bytes::from_static(b"efgh")).unwrap();
254 bp.flush().unwrap();
255
256 let l = log.lock().unwrap();
257 assert_eq!(l.len(), 2);
258 assert_eq!(l[0], (0, 4, b"abcd".to_vec()));
259 assert_eq!(l[1], (10, 14, b"efgh".to_vec()));
260 drop(l);
261 }
262
263 #[test]
264 fn capacity_overflow_flushes() {
265 let log = Arc::new(Mutex::new(Vec::new()));
267 let mut bp = BufWriterPusher::new(RecordingPusher { log: log.clone() }, 4);
268 bp.push(&(0..4), Bytes::from_static(b"abcd")).unwrap();
269 bp.push(&(4..8), Bytes::from_static(b"efgh")).unwrap();
270 bp.flush().unwrap();
271
272 let l = log.lock().unwrap();
273 assert_eq!(l.len(), 2);
274 assert_eq!(l[0], (0, 4, b"abcd".to_vec()));
275 assert_eq!(l[1], (4, 8, b"efgh".to_vec()));
276 drop(l);
277 }
278
279 #[test]
280 fn large_write_bypasses_buffer() {
281 let log = Arc::new(Mutex::new(Vec::new()));
282 let mut bp = BufWriterPusher::new(RecordingPusher { log: log.clone() }, 4);
284 bp.push(&(0..8), Bytes::from_static(b"abcdefgh")).unwrap();
285 bp.flush().unwrap();
286
287 let l = log.lock().unwrap();
288 assert_eq!(l.len(), 1);
289 assert_eq!(l[0], (0, 8, b"abcdefgh".to_vec()));
290 drop(l);
291 }
292
293 #[test]
294 fn random_access_write_is_correct_with_mem_pusher() {
295 let mem = crate::MemPusher::with_capacity(16);
296 let mut bp = BufWriterPusher::new(mem, 8 * 1024);
297 bp.push(&(2..5), Bytes::from_static(b"234")).unwrap();
298 bp.flush().unwrap();
299
300 let content = bp.inner.receive.lock().clone();
301 assert_eq!(content, b"\0\x00234");
303 }
304
305 #[test]
306 fn failed_inner_push_is_retained_and_retried() {
307 let log = Arc::new(Mutex::new(Vec::new()));
308 let mut bp = BufWriterPusher::new(
309 FlakyPusher {
310 log: log.clone(),
311 did_fail: Arc::new(std::sync::atomic::AtomicBool::new(false)),
312 },
313 1024,
314 );
315
316 bp.push(&(0..4), Bytes::from_static(b"abcd")).unwrap();
317 assert!(bp.flush().is_err());
319 bp.flush().unwrap();
321
322 let l = log.lock().unwrap();
323 assert_eq!(l.len(), 1);
324 assert_eq!(l[0], (0, 4, b"abcd".to_vec()));
325 drop(l);
326 }
327
328 #[test]
329 fn empty_push_is_a_noop() {
330 let log = Arc::new(Mutex::new(Vec::new()));
332 let mut bp = BufWriterPusher::new(RecordingPusher { log: log.clone() }, 1024);
333 bp.push(&(0..0), Bytes::new()).unwrap();
334 assert!(log.lock().unwrap().is_empty());
335 }
336
337 #[test]
338 fn flush_failure_during_push_returns_caller_bytes() {
339 let log = Arc::new(Mutex::new(Vec::new()));
342 let mut bp = BufWriterPusher::new(
343 FlakyPusher {
344 log,
345 did_fail: Arc::new(std::sync::atomic::AtomicBool::new(false)),
346 },
347 1024,
348 );
349 bp.push(&(0..4), Bytes::from_static(b"abcd")).unwrap();
351 let res = bp.push(&(10..14), Bytes::from_static(b"efgh"));
354 assert!(res.is_err());
355 let (_e, remaining) = res.unwrap_err();
356 assert_eq!(&remaining[..], b"efgh");
357 }
358
359 #[test]
360 fn flush_buf_partial_write_is_retained_and_retried() {
361 let log = Arc::new(Mutex::new(Vec::new()));
364 let mut bp = BufWriterPusher::new(
365 PartialPusher {
366 log: log.clone(),
367 wrote_partial: Arc::new(std::sync::atomic::AtomicBool::new(false)),
368 },
369 1024,
370 );
371 bp.push(&(0..4), Bytes::from_static(b"abcd")).unwrap();
372 assert!(bp.flush().is_err());
374 bp.flush().unwrap();
376
377 let l = log.lock().unwrap();
378 assert_eq!(l.len(), 2);
380 assert_eq!(l[0], (0, 4, b"abcd".to_vec()));
381 assert_eq!(l[1], (2, 4, b"cd".to_vec()));
382 }
383
384 #[test]
385 fn buf_writer_forwards_set_listener_and_fires_on_flush() {
386 let sink = ListenerRecordingPusher::default();
387 let fired = sink.fired.clone();
388 let mut bp = BufWriterPusher::new(sink, 1024);
389 bp.set_listener(Box::new(|_r: ProgressEntry| {}));
390 bp.push(&(0..4), Bytes::from_static(b"abcd")).unwrap();
391 assert!(
393 fired.lock().unwrap().is_empty(),
394 "buffered write must not reach the inner listener before flush"
395 );
396 bp.flush().unwrap();
397 assert_eq!(fired.lock().unwrap().as_slice(), &[(0, 4)]);
399 }
400
401 #[test]
402 fn capacity_full_does_not_flush_prematurely() {
403 let log = Arc::new(Mutex::new(Vec::new()));
406 let mut bp = BufWriterPusher::new(RecordingPusher { log: log.clone() }, 4);
407 bp.push(&(0..2), Bytes::from_static(b"ab")).unwrap();
408 bp.push(&(2..4), Bytes::from_static(b"cd")).unwrap();
409 assert!(
410 log.lock().unwrap().is_empty(),
411 "reaching exactly capacity must not flush"
412 );
413 bp.flush().unwrap();
414 let l = log.lock().unwrap();
415 assert_eq!(l.len(), 1);
416 assert_eq!(l[0], (0, 4, b"abcd".to_vec()));
417 }
418
419 #[test]
420 fn failed_noncontiguous_flush_keeps_old_run_for_retry() {
421 let log = Arc::new(Mutex::new(Vec::new()));
425 let mut bp = BufWriterPusher::new(
426 FlakyPusher {
427 log: log.clone(),
428 did_fail: Arc::new(std::sync::atomic::AtomicBool::new(false)),
429 },
430 1024,
431 );
432 bp.push(&(0..4), Bytes::from_static(b"abcd")).unwrap();
433 let res = bp.push(&(10..14), Bytes::from_static(b"efgh"));
434 assert!(res.is_err());
435 bp.flush().unwrap();
437 let l = log.lock().unwrap();
438 assert_eq!(l.len(), 1);
439 assert_eq!(l[0], (0, 4, b"abcd".to_vec()));
440 }
441}