1use crate::{ProgressEntry, ProgressListener, Pusher};
4use bytes::Bytes;
5use std::collections::BTreeMap;
6
7#[derive(Debug)]
16pub struct CacheSeqPusher<P> {
17 inner: P,
18 cache: BTreeMap<u64, Bytes>,
19 cache_size: usize,
20 high_watermark: usize,
21 low_watermark: usize,
22}
23
24impl<P: Pusher> CacheSeqPusher<P> {
25 pub const fn new(inner: P, high_watermark: usize, low_watermark: usize) -> Self {
35 Self {
36 inner,
37 cache: BTreeMap::new(),
38 cache_size: 0,
39 high_watermark,
40 low_watermark,
41 }
42 }
43
44 fn evict_until(&mut self, target_size: usize) -> Result<(), P::Error> {
45 let mut expected = None;
46 while let Some(entry) = self.cache.first_entry() {
47 let start = *entry.key();
48 if self.cache_size <= target_size && Some(start) != expected {
49 break;
50 }
51 let chunk = entry.remove();
52 let chunk_len = chunk.len();
53 let next_pos = start + chunk_len as u64;
54 self.cache_size -= chunk_len;
55 if let Err((e, ret)) = self.inner.push(&(start..next_pos), chunk) {
56 if !ret.is_empty() {
57 let written = chunk_len.saturating_sub(ret.len());
58 self.cache_size += ret.len();
59 if let Some(old) = self.cache.insert(start + written as u64, ret) {
60 self.cache_size -= old.len();
61 }
62 }
63 return Err(e);
64 }
65 expected = Some(next_pos);
66 }
67 Ok(())
68 }
69}
70
71impl<P: Pusher> Pusher for CacheSeqPusher<P> {
72 type Error = P::Error;
73
74 fn set_listener(&mut self, cb: ProgressListener) {
75 self.inner.set_listener(cb);
76 }
77
78 fn push(&mut self, range: &ProgressEntry, bytes: Bytes) -> Result<(), (Self::Error, Bytes)> {
79 if bytes.is_empty() {
80 return Ok(());
81 }
82
83 self.cache_size += bytes.len();
84 if let Some(old_bytes) = self.cache.insert(range.start, bytes) {
85 self.cache_size -= old_bytes.len();
86 }
87
88 if self.cache_size >= self.high_watermark
89 && let Err(e) = self.evict_until(self.low_watermark)
90 {
91 return Err((e, Bytes::new()));
92 }
93
94 Ok(())
95 }
96
97 fn flush(&mut self) -> Result<(), Self::Error> {
98 self.evict_until(0)?;
99 self.inner.flush()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 #![allow(clippy::unwrap_used)]
106 use super::*;
107 use std::sync::atomic::{AtomicBool, Ordering};
108 use std::sync::{Arc, Mutex};
109
110 #[derive(Clone)]
113 struct RecordingSink {
114 pushes: Arc<Mutex<Vec<(ProgressEntry, Bytes)>>>,
115 fail_next: Arc<AtomicBool>,
116 listener_set: Arc<AtomicBool>,
117 }
118 impl RecordingSink {
119 fn new() -> Self {
120 Self {
121 pushes: Arc::new(Mutex::new(Vec::new())),
122 fail_next: Arc::new(AtomicBool::new(false)),
123 listener_set: Arc::new(AtomicBool::new(false)),
124 }
125 }
126 }
127 impl Pusher for RecordingSink {
128 type Error = std::io::Error;
129 fn set_listener(&mut self, _: ProgressListener) {
130 self.listener_set.store(true, Ordering::SeqCst);
131 }
132 fn push(
133 &mut self,
134 range: &ProgressEntry,
135 bytes: Bytes,
136 ) -> Result<(), (Self::Error, Bytes)> {
137 if self.fail_next.fetch_and(false, Ordering::SeqCst) {
138 return Err((std::io::Error::other("boom"), bytes));
139 }
140 self.pushes.lock().unwrap().push((range.clone(), bytes));
141 Ok(())
142 }
143 fn flush(&mut self) -> Result<(), Self::Error> {
144 Ok(())
145 }
146 }
147
148 #[derive(Clone)]
152 struct PartialSink {
153 pushes: Arc<Mutex<Vec<(ProgressEntry, Bytes)>>>,
154 partial: Arc<AtomicBool>,
155 }
156 impl Pusher for PartialSink {
157 type Error = std::io::Error;
158 fn push(
159 &mut self,
160 range: &ProgressEntry,
161 bytes: Bytes,
162 ) -> Result<(), (Self::Error, Bytes)> {
163 self.pushes
164 .lock()
165 .unwrap()
166 .push((range.clone(), bytes.clone()));
167 if !self.partial.swap(true, Ordering::SeqCst) {
168 let rem = bytes.slice(2..);
169 return Err((std::io::Error::other("partial"), rem));
170 }
171 Ok(())
172 }
173 }
174
175 fn bb(s: &str) -> Bytes {
176 Bytes::copy_from_slice(s.as_bytes())
177 }
178
179 #[test]
180 fn empty_push_is_noop() {
181 let sink = RecordingSink::new();
183 let mut p = CacheSeqPusher::new(sink.clone(), 100, 0);
184 p.push(&(0..0), Bytes::new()).unwrap();
185 assert!(sink.pushes.lock().unwrap().is_empty());
186 }
187
188 #[test]
189 fn duplicate_start_overwrites_old_bytes() {
190 let sink = RecordingSink::new();
193 let mut p = CacheSeqPusher::new(sink.clone(), 100, 0);
194 p.push(&(0..10), bb(&"A".repeat(10))).unwrap();
195 p.push(&(0..10), bb(&"B".repeat(10))).unwrap();
196 p.flush().unwrap();
197 let pushes = sink.pushes.lock().unwrap();
198 assert_eq!(pushes.len(), 1);
199 assert_eq!(&pushes[0].1[..], b"BBBBBBBBBB");
200 drop(pushes);
201 }
202
203 #[test]
204 fn evict_stops_at_gap_once_below_target() {
205 let sink = RecordingSink::new();
208 let mut p = CacheSeqPusher::new(sink.clone(), 100, 30);
209 p.push(&(0..40), bb(&"A".repeat(40))).unwrap();
210 p.push(&(40..80), bb(&"B".repeat(40))).unwrap();
211 p.push(&(200..220), bb(&"C".repeat(20))).unwrap();
216
217 let pushes = sink.pushes.lock().unwrap();
218 assert_eq!(pushes.len(), 2);
219 assert_eq!(pushes[0].0, 0..40);
220 assert_eq!(pushes[1].0, 40..80);
221 drop(pushes);
222 }
223
224 #[test]
225 fn inner_push_failure_during_push_eviction_propagates() {
226 let sink = RecordingSink::new();
228 sink.fail_next.store(true, Ordering::SeqCst);
229 let mut p = CacheSeqPusher::new(sink, 10, 0);
230 let res = p.push(&(0..10), bb(&"A".repeat(10)));
231 assert!(res.is_err());
232 }
233
234 #[test]
235 fn inner_push_failure_during_flush_propagates() {
236 let sink = RecordingSink::new();
238 sink.fail_next.store(true, Ordering::SeqCst);
239 let mut p = CacheSeqPusher::new(sink, 100, 0);
240 p.push(&(0..10), bb(&"A".repeat(10))).unwrap();
241 assert!(p.flush().is_err());
242 }
243
244 #[test]
245 fn set_listener_forwards_to_inner() {
246 let sink = RecordingSink::new();
248 let mut p = CacheSeqPusher::new(sink.clone(), 100, 0);
249 p.set_listener(Box::new(|_| {}));
250 assert!(sink.listener_set.load(Ordering::SeqCst));
251 }
252
253 #[test]
254 fn evict_breaks_on_gap_below_target() {
255 let sink = RecordingSink::new();
259 let mut p = CacheSeqPusher::new(sink.clone(), 20, 10);
260 p.push(&(0..10), bb(&"A".repeat(10))).unwrap();
261 p.push(&(20..30), bb(&"B".repeat(10))).unwrap();
262 let pushes = sink.pushes.lock().unwrap();
264 assert_eq!(pushes.len(), 1);
265 assert_eq!(pushes[0].0, 0..10);
266 drop(pushes);
267 }
268
269 #[test]
270 fn evict_until_partial_write_rebuffers_tail_at_correct_offset() {
271 let sink = PartialSink {
274 pushes: Arc::new(Mutex::new(Vec::new())),
275 partial: Arc::new(AtomicBool::new(false)),
276 };
277 let mut p = CacheSeqPusher::new(sink.clone(), 10, 0);
278 let res = p.push(&(0..10), bb(&"A".repeat(10)));
280 assert!(res.is_err());
281 p.flush().unwrap();
283 let pushes = sink.pushes.lock().unwrap();
284 assert_eq!(pushes.len(), 2);
285 assert_eq!(pushes[0].0, 0..10); assert_eq!(pushes[1].0, 2..10); assert_eq!(&pushes[1].1[..], b"AAAAAAAA");
288 }
289
290 #[test]
291 fn evict_continues_across_gaps_while_above_target() {
292 let sink = RecordingSink::new();
298 let mut p = CacheSeqPusher::new(sink.clone(), 100, 30);
299 p.push(&(0..40), bb(&"A".repeat(40))).unwrap();
300 p.push(&(200..240), bb(&"B".repeat(40))).unwrap();
301 p.push(&(400..440), bb(&"C".repeat(40))).unwrap();
302 let pushes = sink.pushes.lock().unwrap();
303 assert_eq!(pushes.len(), 3);
304 assert_eq!(pushes[0].0, 0..40);
305 assert_eq!(pushes[1].0, 200..240);
306 assert_eq!(pushes[2].0, 400..440);
307 }
308
309 #[test]
310 fn failed_push_eviction_retains_callers_chunk_for_retry() {
311 let sink = RecordingSink::new();
314 sink.fail_next.store(true, Ordering::SeqCst);
315 let mut p = CacheSeqPusher::new(sink.clone(), 10, 0);
316 let res = p.push(&(0..10), bb(&"A".repeat(10)));
317 assert!(res.is_err());
318 assert_eq!(
319 res.unwrap_err().1.len(),
320 0,
321 "buffered data is held internally, not returned to the caller"
322 );
323 p.flush().unwrap();
324 let pushes = sink.pushes.lock().unwrap();
325 assert_eq!(pushes.len(), 1);
326 assert_eq!(pushes[0].0, 0..10);
327 }
328}