1use crate::{ProgressEntry, ProgressListener, Pusher};
4use bytes::Bytes;
5use std::collections::BTreeMap;
6
7#[derive(Debug)]
14pub struct CacheDirectPusher<P> {
15 inner: P,
16 cache: BTreeMap<u64, Bytes>,
17 cache_size: usize,
18 high_watermark: usize,
19 low_watermark: usize,
20}
21
22impl<P: Pusher> CacheDirectPusher<P> {
23 pub const fn new(inner: P, high_watermark: usize, low_watermark: usize) -> Self {
36 Self {
37 inner,
38 cache: BTreeMap::new(),
39 cache_size: 0,
40 high_watermark,
41 low_watermark,
42 }
43 }
44
45 fn evict_until(&mut self, target_size: usize) -> Result<(), P::Error> {
46 if self.cache_size <= target_size {
47 return Ok(());
48 }
49
50 let mut runs: Vec<(u64, usize)> = Vec::with_capacity(self.cache.len());
51 let mut curr_start = None;
52 let mut curr_len = 0;
53 let mut expected_next = 0;
54
55 for (&start, bytes) in &self.cache {
56 let len = bytes.len();
57 if let Some(c_start) = curr_start {
58 if start == expected_next {
59 curr_len += len;
60 expected_next += len as u64;
61 } else {
62 runs.push((c_start, curr_len));
63 curr_start = Some(start);
64 curr_len = len;
65 expected_next = start + len as u64;
66 }
67 } else {
68 curr_start = Some(start);
69 curr_len = len;
70 expected_next = start + len as u64;
71 }
72 }
73 if let Some(c_start) = curr_start {
74 runs.push((c_start, curr_len));
75 }
76 runs.sort_unstable_by_key(|&(_, len)| std::cmp::Reverse(len));
77
78 for (mut start, mut total_len) in runs {
79 while total_len > 0 {
80 let chunk = self.cache.remove(&start).unwrap();
81 let len = chunk.len();
82 self.cache_size -= len;
83 total_len -= len;
84 let range = start..start + len as u64;
85 if let Err((e, ret_bytes)) = self.inner.push(&range, chunk) {
86 if !ret_bytes.is_empty() {
87 self.cache_size += ret_bytes.len();
88 let written = len.saturating_sub(ret_bytes.len());
89 if let Some(old) = self.cache.insert(start + written as u64, ret_bytes) {
90 self.cache_size -= old.len();
91 }
92 }
93 return Err(e);
94 }
95 start += len as u64;
96 }
97 if self.cache_size <= target_size {
98 break;
99 }
100 }
101 Ok(())
102 }
103}
104
105impl<P: Pusher> Pusher for CacheDirectPusher<P> {
106 type Error = P::Error;
107
108 fn set_listener(&mut self, cb: ProgressListener) {
109 self.inner.set_listener(cb);
110 }
111
112 fn push(&mut self, range: &ProgressEntry, bytes: Bytes) -> Result<(), (Self::Error, Bytes)> {
113 if bytes.is_empty() {
114 return Ok(());
115 }
116
117 self.cache_size += bytes.len();
118 if let Some(old_bytes) = self.cache.insert(range.start, bytes) {
119 self.cache_size -= old_bytes.len();
120 }
121
122 if self.cache_size >= self.high_watermark
123 && let Err(e) = self.evict_until(self.low_watermark)
124 {
125 return Err((e, Bytes::new()));
126 }
127
128 Ok(())
129 }
130
131 fn flush(&mut self) -> Result<(), Self::Error> {
132 self.evict_until(0)?;
133 self.inner.flush()
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 #![allow(clippy::unwrap_used)]
140 use super::*;
141 use std::sync::atomic::{AtomicBool, Ordering};
142 use std::sync::{Arc, Mutex};
143
144 #[derive(Clone)]
147 struct RecordingSink {
148 pushes: Arc<Mutex<Vec<(ProgressEntry, Bytes)>>>,
149 fail_next: Arc<AtomicBool>,
150 listener_set: Arc<AtomicBool>,
151 }
152 impl RecordingSink {
153 fn new() -> Self {
154 Self {
155 pushes: Arc::new(Mutex::new(Vec::new())),
156 fail_next: Arc::new(AtomicBool::new(false)),
157 listener_set: Arc::new(AtomicBool::new(false)),
158 }
159 }
160 }
161 impl Pusher for RecordingSink {
162 type Error = std::io::Error;
163 fn set_listener(&mut self, _: ProgressListener) {
164 self.listener_set.store(true, Ordering::SeqCst);
165 }
166 fn push(
167 &mut self,
168 range: &ProgressEntry,
169 bytes: Bytes,
170 ) -> Result<(), (Self::Error, Bytes)> {
171 if self.fail_next.fetch_and(false, Ordering::SeqCst) {
172 return Err((std::io::Error::other("boom"), bytes));
173 }
174 self.pushes.lock().unwrap().push((range.clone(), bytes));
175 Ok(())
176 }
177 fn flush(&mut self) -> Result<(), Self::Error> {
178 Ok(())
179 }
180 }
181
182 #[derive(Clone)]
186 struct PartialSinkDirect {
187 pushes: Arc<Mutex<Vec<(ProgressEntry, Bytes)>>>,
188 partial: Arc<AtomicBool>,
189 }
190 impl Pusher for PartialSinkDirect {
191 type Error = std::io::Error;
192 fn push(
193 &mut self,
194 range: &ProgressEntry,
195 bytes: Bytes,
196 ) -> Result<(), (Self::Error, Bytes)> {
197 self.pushes
198 .lock()
199 .unwrap()
200 .push((range.clone(), bytes.clone()));
201 if !self.partial.swap(true, Ordering::SeqCst) {
202 let rem = bytes.slice(2..);
203 return Err((std::io::Error::other("partial"), rem));
204 }
205 Ok(())
206 }
207 }
208
209 fn bb(s: &str) -> Bytes {
210 Bytes::copy_from_slice(s.as_bytes())
211 }
212
213 #[test]
214 fn empty_push_is_noop() {
215 let sink = RecordingSink::new();
217 let mut p = CacheDirectPusher::new(sink.clone(), 100, 0);
218 p.push(&(0..0), Bytes::new()).unwrap();
219 assert!(sink.pushes.lock().unwrap().is_empty());
220 }
221
222 #[test]
223 fn equal_watermarks_skip_only_the_exact_hit() {
224 let sink = RecordingSink::new();
228 let mut p = CacheDirectPusher::new(sink.clone(), 10, 10);
229 p.push(&(0..10), bb(&"A".repeat(10))).unwrap();
230 assert!(sink.pushes.lock().unwrap().is_empty());
231
232 p.push(&(10..11), bb("B")).unwrap();
233 let ranges: Vec<_> = sink
234 .pushes
235 .lock()
236 .unwrap()
237 .iter()
238 .map(|(r, _)| r.clone())
239 .collect();
240 assert_eq!(ranges, vec![0..10, 10..11]);
241 }
242
243 #[test]
244 fn below_watermark_buffers_until_flush() {
245 let sink = RecordingSink::new();
247 let mut p = CacheDirectPusher::new(sink.clone(), 100, 10);
248 p.push(&(0..10), bb(&"A".repeat(10))).unwrap();
249 p.push(&(10..20), bb(&"B".repeat(10))).unwrap();
250 assert!(sink.pushes.lock().unwrap().is_empty());
251 p.flush().unwrap();
252 let pushes = sink.pushes.lock().unwrap();
253 assert_eq!(pushes.len(), 2);
254 assert_eq!(pushes[0].0, 0..10);
255 assert_eq!(pushes[1].0, 10..20);
256 drop(pushes);
257 }
258
259 #[test]
260 fn evicts_longest_run_first() {
261 let sink = RecordingSink::new();
264 let mut p = CacheDirectPusher::new(sink.clone(), 70, 0);
265 p.push(&(100..120), bb(&"B".repeat(20))).unwrap();
267 p.push(&(120..140), bb(&"B".repeat(20))).unwrap();
268 p.push(&(200..210), bb(&"C".repeat(10))).unwrap();
270 p.push(&(0..10), bb(&"A".repeat(10))).unwrap();
272 p.push(&(10..20), bb(&"A".repeat(10))).unwrap();
273 p.push(&(20..30), bb(&"A".repeat(10))).unwrap();
275 p.flush().unwrap();
276
277 let pushes = sink.pushes.lock().unwrap();
278 assert_eq!(pushes[0].0, 100..120);
280 assert_eq!(pushes.len(), 6);
281 drop(pushes);
282 }
283
284 #[test]
285 fn duplicate_start_overwrites_old_bytes() {
286 let sink = RecordingSink::new();
289 let mut p = CacheDirectPusher::new(sink.clone(), 100, 0);
290 p.push(&(0..10), bb(&"A".repeat(10))).unwrap();
291 p.push(&(0..10), bb(&"B".repeat(10))).unwrap();
292 p.flush().unwrap();
293 let pushes = sink.pushes.lock().unwrap();
294 assert_eq!(pushes.len(), 1);
295 assert_eq!(&pushes[0].1[..], b"BBBBBBBBBB");
296 drop(pushes);
297 }
298
299 #[test]
300 fn inner_push_failure_rebuffers_partial() {
301 let sink = RecordingSink::new();
304 sink.fail_next.store(true, Ordering::SeqCst);
305 let mut p = CacheDirectPusher::new(sink, 10, 0);
306 let res = p.push(&(0..10), bb(&"A".repeat(10)));
307 assert!(res.is_err());
308 }
309
310 #[test]
311 fn evict_stops_early_at_low_watermark() {
312 let sink = RecordingSink::new();
316 let mut p = CacheDirectPusher::new(sink.clone(), 100, 30);
317 p.push(&(0..60), bb(&"A".repeat(60))).unwrap();
318 p.push(&(100..120), bb(&"B".repeat(20))).unwrap();
319 p.push(&(200..220), bb(&"C".repeat(20))).unwrap();
320
321 let pushes = sink.pushes.lock().unwrap();
322 assert_eq!(pushes.len(), 2);
325 assert_eq!(pushes[0].0, 0..60);
326 assert_eq!(pushes[1].0, 100..120);
327 drop(pushes);
328 }
329
330 #[test]
331 fn set_listener_forwards_to_inner() {
332 let sink = RecordingSink::new();
334 let mut p = CacheDirectPusher::new(sink.clone(), 100, 0);
335 p.set_listener(Box::new(|_| {}));
336 assert!(sink.listener_set.load(Ordering::SeqCst));
337 }
338
339 #[test]
340 fn direct_evict_partial_write_rebuffers_chunk_tail_at_correct_offset() {
341 let sink = PartialSinkDirect {
345 pushes: Arc::new(Mutex::new(Vec::new())),
346 partial: Arc::new(AtomicBool::new(false)),
347 };
348 let mut p = CacheDirectPusher::new(sink.clone(), 30, 0);
349 p.push(&(0..10), bb(&"A".repeat(10))).unwrap();
350 p.push(&(10..20), bb(&"B".repeat(10))).unwrap();
351 let res = p.push(&(20..30), bb(&"C".repeat(10)));
352 assert!(res.is_err());
353 p.flush().unwrap();
354 let pushes = sink.pushes.lock().unwrap();
355 assert_eq!(pushes.len(), 4);
357 assert_eq!(pushes[0].0, 0..10);
358 assert_eq!(pushes[1].0, 2..10);
359 assert_eq!(&pushes[1].1[..], b"AAAAAAAA");
360 assert_eq!(pushes[2].0, 10..20);
361 assert_eq!(pushes[3].0, 20..30);
362 }
363}