1use async_trait::async_trait;
4use std::collections::HashMap;
5use std::sync::{Arc, RwLock};
6
7use crate::types::{to_hex, Hash};
8
9pub fn slice_blob_range(
14 data: &[u8],
15 start: u64,
16 end_inclusive: u64,
17) -> Result<Vec<u8>, StoreError> {
18 if data.is_empty() || end_inclusive < start {
19 return Ok(Vec::new());
20 }
21
22 let len = data.len() as u64;
23 if start >= len {
24 return Ok(Vec::new());
25 }
26
27 let actual_end = end_inclusive.min(len - 1);
28 let start = usize::try_from(start)
29 .map_err(|_| StoreError::Other("blob range start is too large".to_string()))?;
30 let end_exclusive = usize::try_from(actual_end.saturating_add(1))
31 .map_err(|_| StoreError::Other("blob range end is too large".to_string()))?;
32
33 Ok(data[start..end_exclusive].to_vec())
34}
35
36#[derive(Debug, Clone, Default)]
38pub struct StoreStats {
39 pub count: u64,
41 pub bytes: u64,
43 pub pinned_count: u64,
45 pub pinned_bytes: u64,
47}
48
49#[derive(Debug, Clone, Default, PartialEq, Eq)]
51pub struct PutManyReport {
52 pub total: usize,
54 pub inserted: usize,
56 pub inserted_bytes: u64,
58 pub inserted_hashes: Vec<Hash>,
60}
61
62#[async_trait]
64pub trait Store: Send + Sync {
65 async fn put(&self, hash: Hash, data: Vec<u8>) -> Result<bool, StoreError>;
68
69 async fn put_many(&self, items: Vec<(Hash, Vec<u8>)>) -> Result<usize, StoreError> {
72 let mut inserted = 0usize;
73 for (hash, data) in items {
74 if self.put(hash, data).await? {
75 inserted += 1;
76 }
77 }
78 Ok(inserted)
79 }
80
81 async fn put_many_optimistic(&self, items: Vec<(Hash, Vec<u8>)>) -> Result<usize, StoreError> {
88 self.put_many(items).await
89 }
90
91 async fn flush_pending(&self) -> Result<usize, StoreError> {
97 Ok(0)
98 }
99
100 async fn flush_pending_if_needed(&self) -> Result<usize, StoreError> {
104 self.flush_pending().await
105 }
106
107 async fn get(&self, hash: &Hash) -> Result<Option<Vec<u8>>, StoreError>;
110
111 async fn get_range(
117 &self,
118 hash: &Hash,
119 start: u64,
120 end_inclusive: u64,
121 ) -> Result<Option<Vec<u8>>, StoreError> {
122 let Some(data) = self.get(hash).await? else {
123 return Ok(None);
124 };
125 Ok(Some(slice_blob_range(&data, start, end_inclusive)?))
126 }
127
128 async fn blob_size(&self, hash: &Hash) -> Result<Option<u64>, StoreError> {
133 Ok(self.get(hash).await?.map(|data| data.len() as u64))
134 }
135
136 async fn has(&self, hash: &Hash) -> Result<bool, StoreError>;
138
139 async fn delete(&self, hash: &Hash) -> Result<bool, StoreError>;
142
143 async fn delete_many(&self, hashes: Vec<Hash>) -> Result<usize, StoreError> {
148 let mut deleted = 0usize;
149 for hash in hashes {
150 if self.delete(&hash).await? {
151 deleted += 1;
152 }
153 }
154 Ok(deleted)
155 }
156
157 fn set_max_bytes(&self, _max: u64) {}
163
164 fn max_bytes(&self) -> Option<u64> {
166 None
167 }
168
169 async fn stats(&self) -> StoreStats {
171 StoreStats::default()
172 }
173
174 async fn evict_if_needed(&self) -> Result<u64, StoreError> {
177 Ok(0)
178 }
179
180 async fn pin(&self, _hash: &Hash) -> Result<(), StoreError> {
186 Ok(())
187 }
188
189 async fn unpin(&self, _hash: &Hash) -> Result<(), StoreError> {
191 Ok(())
192 }
193
194 fn pin_count(&self, _hash: &Hash) -> u32 {
196 0
197 }
198
199 fn is_pinned(&self, hash: &Hash) -> bool {
201 self.pin_count(hash) > 0
202 }
203}
204
205#[derive(Debug, thiserror::Error)]
207pub enum StoreError {
208 #[error("IO error: {0}")]
209 Io(#[from] std::io::Error),
210 #[error("Store error: {0}")]
211 Other(String),
212}
213
214#[derive(Debug, Default)]
215struct BufferedStoreInner {
216 pending: HashMap<Hash, Vec<u8>>,
217 order: Vec<Hash>,
218 pending_bytes: usize,
219}
220
221#[derive(Debug, Clone, Copy)]
222struct BufferedStoreOptions {
223 check_base_on_put: bool,
224 flush_threshold_bytes: usize,
225}
226
227impl Default for BufferedStoreOptions {
228 fn default() -> Self {
229 Self {
230 check_base_on_put: true,
231 flush_threshold_bytes: 0,
232 }
233 }
234}
235
236#[derive(Debug, Clone)]
238pub struct BufferedStore<S: Store> {
239 base: Arc<S>,
240 inner: Arc<RwLock<BufferedStoreInner>>,
241 options: BufferedStoreOptions,
242}
243
244impl<S: Store> BufferedStore<S> {
245 pub fn new(base: Arc<S>) -> Self {
246 Self::with_options(base, BufferedStoreOptions::default())
247 }
248
249 pub fn new_optimistic(base: Arc<S>) -> Self {
250 Self::with_options(
251 base,
252 BufferedStoreOptions {
253 check_base_on_put: false,
254 flush_threshold_bytes: 0,
255 },
256 )
257 }
258
259 fn with_options(base: Arc<S>, options: BufferedStoreOptions) -> Self {
260 Self {
261 base,
262 inner: Arc::new(RwLock::new(BufferedStoreInner::default())),
263 options,
264 }
265 }
266
267 pub fn with_flush_threshold(mut self, bytes: usize) -> Self {
272 self.options.flush_threshold_bytes = bytes;
273 self
274 }
275
276 pub async fn flush(&self) -> Result<usize, StoreError> {
277 let items = {
278 let mut inner = self.inner.write().unwrap();
279 if inner.order.is_empty() {
280 return Ok(0);
281 }
282
283 let order = std::mem::take(&mut inner.order);
284 inner.pending_bytes = 0;
285 let mut items = Vec::with_capacity(order.len());
286 for hash in order {
287 if let Some(data) = inner.pending.remove(&hash) {
288 items.push((hash, data));
289 }
290 }
291 items
292 };
293
294 if self.options.check_base_on_put {
295 self.base.put_many(items).await
296 } else {
297 self.base.put_many_optimistic(items).await
298 }
299 }
300}
301
302#[async_trait]
303impl<S: Store> Store for BufferedStore<S> {
304 async fn put(&self, hash: Hash, data: Vec<u8>) -> Result<bool, StoreError> {
305 {
306 let inner = self.inner.read().unwrap();
307 if inner.pending.contains_key(&hash) {
308 return Ok(false);
309 }
310 }
311
312 if self.options.check_base_on_put && self.base.has(&hash).await? {
313 return Ok(false);
314 }
315
316 let mut inner = self.inner.write().unwrap();
317 if inner.pending.contains_key(&hash) {
318 return Ok(false);
319 }
320 inner.order.push(hash);
321 inner.pending_bytes += data.len();
322 inner.pending.insert(hash, data);
323 Ok(true)
324 }
325
326 async fn put_many(&self, items: Vec<(Hash, Vec<u8>)>) -> Result<usize, StoreError> {
327 let mut inserted = 0usize;
328 for (hash, data) in items {
329 if self.put(hash, data).await? {
330 inserted += 1;
331 }
332 }
333 Ok(inserted)
334 }
335
336 async fn flush_pending(&self) -> Result<usize, StoreError> {
337 BufferedStore::flush(self).await
338 }
339
340 async fn flush_pending_if_needed(&self) -> Result<usize, StoreError> {
341 if self.inner.read().unwrap().pending_bytes < self.options.flush_threshold_bytes {
342 return Ok(0);
343 }
344 self.flush().await
345 }
346
347 async fn get(&self, hash: &Hash) -> Result<Option<Vec<u8>>, StoreError> {
348 if let Some(data) = self.inner.read().unwrap().pending.get(hash).cloned() {
349 return Ok(Some(data));
350 }
351 self.base.get(hash).await
352 }
353
354 async fn get_range(
355 &self,
356 hash: &Hash,
357 start: u64,
358 end_inclusive: u64,
359 ) -> Result<Option<Vec<u8>>, StoreError> {
360 let pending = {
361 let inner = self.inner.read().unwrap();
362 inner
363 .pending
364 .get(hash)
365 .map(|data| slice_blob_range(data, start, end_inclusive))
366 .transpose()?
367 };
368 if pending.is_some() {
369 return Ok(pending);
370 }
371 self.base.get_range(hash, start, end_inclusive).await
372 }
373
374 async fn blob_size(&self, hash: &Hash) -> Result<Option<u64>, StoreError> {
375 if let Some(size) = self
376 .inner
377 .read()
378 .unwrap()
379 .pending
380 .get(hash)
381 .map(|data| data.len() as u64)
382 {
383 return Ok(Some(size));
384 }
385 self.base.blob_size(hash).await
386 }
387
388 async fn has(&self, hash: &Hash) -> Result<bool, StoreError> {
389 if self.inner.read().unwrap().pending.contains_key(hash) {
390 return Ok(true);
391 }
392 self.base.has(hash).await
393 }
394
395 async fn delete(&self, hash: &Hash) -> Result<bool, StoreError> {
396 let removed = {
397 let mut inner = self.inner.write().unwrap();
398 let removed = inner.pending.remove(hash);
399 if let Some(bytes) = &removed {
400 inner.pending_bytes -= bytes.len();
401 inner.order.retain(|queued| queued != hash);
402 }
403 removed.is_some()
404 };
405
406 if removed {
407 return Ok(true);
408 }
409
410 self.base.delete(hash).await
411 }
412
413 async fn stats(&self) -> StoreStats {
414 let mut stats = self.base.stats().await;
415 let pending_bytes = self
416 .inner
417 .read()
418 .unwrap()
419 .pending
420 .values()
421 .map(|data| data.len() as u64)
422 .sum::<u64>();
423 stats.count += self.inner.read().unwrap().pending.len() as u64;
424 stats.bytes += pending_bytes;
425 stats
426 }
427
428 async fn evict_if_needed(&self) -> Result<u64, StoreError> {
429 self.base.evict_if_needed().await
430 }
431
432 async fn pin(&self, hash: &Hash) -> Result<(), StoreError> {
433 self.base.pin(hash).await
434 }
435
436 async fn unpin(&self, hash: &Hash) -> Result<(), StoreError> {
437 self.base.unpin(hash).await
438 }
439
440 fn pin_count(&self, hash: &Hash) -> u32 {
441 self.base.pin_count(hash)
442 }
443}
444
445#[derive(Debug, Clone)]
447struct MemoryEntry {
448 data: Vec<u8>,
449 order: u64,
451}
452
453#[derive(Debug, Default)]
455struct MemoryStoreInner {
456 data: HashMap<String, MemoryEntry>,
457 pins: HashMap<String, u32>,
458 next_order: u64,
459 max_bytes: Option<u64>,
460}
461
462#[derive(Debug, Clone, Default)]
464pub struct MemoryStore {
465 inner: Arc<RwLock<MemoryStoreInner>>,
466}
467
468impl MemoryStore {
469 pub fn new() -> Self {
470 Self {
471 inner: Arc::new(RwLock::new(MemoryStoreInner::default())),
472 }
473 }
474
475 pub fn with_max_bytes(max_bytes: u64) -> Self {
477 Self {
478 inner: Arc::new(RwLock::new(MemoryStoreInner {
479 max_bytes: if max_bytes > 0 { Some(max_bytes) } else { None },
480 ..Default::default()
481 })),
482 }
483 }
484
485 pub fn size(&self) -> usize {
487 self.inner.read().unwrap().data.len()
488 }
489
490 pub fn total_bytes(&self) -> usize {
492 self.inner
493 .read()
494 .unwrap()
495 .data
496 .values()
497 .map(|e| e.data.len())
498 .sum()
499 }
500
501 pub fn clear(&self) {
503 self.inner.write().unwrap().data.clear();
504 }
505
506 pub fn keys(&self) -> Vec<Hash> {
508 self.inner
509 .read()
510 .unwrap()
511 .data
512 .keys()
513 .filter_map(|hex| {
514 let bytes = hex::decode(hex).ok()?;
515 if bytes.len() != 32 {
516 return None;
517 }
518 let mut hash = [0u8; 32];
519 hash.copy_from_slice(&bytes);
520 Some(hash)
521 })
522 .collect()
523 }
524
525 fn evict_to_target(&self, target_bytes: u64) -> u64 {
527 let mut inner = self.inner.write().unwrap();
528
529 let current_bytes: u64 = inner.data.values().map(|e| e.data.len() as u64).sum();
530 if current_bytes <= target_bytes {
531 return 0;
532 }
533
534 let mut unpinned: Vec<(String, u64, u64)> = inner
536 .data
537 .iter()
538 .filter(|(key, _)| inner.pins.get(*key).copied().unwrap_or(0) == 0)
539 .map(|(key, entry)| (key.clone(), entry.order, entry.data.len() as u64))
540 .collect();
541
542 unpinned.sort_by_key(|(_, order, _)| *order);
543
544 let mut freed = 0u64;
545 let to_free = current_bytes - target_bytes;
546
547 for (key, _, size) in unpinned {
548 if freed >= to_free {
549 break;
550 }
551 inner.data.remove(&key);
552 freed += size;
553 }
554
555 freed
556 }
557}
558
559#[async_trait]
560impl Store for MemoryStore {
561 async fn put(&self, hash: Hash, data: Vec<u8>) -> Result<bool, StoreError> {
562 let key = to_hex(&hash);
563 let mut inner = self.inner.write().unwrap();
564 if inner.data.contains_key(&key) {
565 return Ok(false);
566 }
567 let order = inner.next_order;
568 inner.next_order += 1;
569 inner.data.insert(key, MemoryEntry { data, order });
570 Ok(true)
571 }
572
573 async fn put_many(&self, items: Vec<(Hash, Vec<u8>)>) -> Result<usize, StoreError> {
574 let mut inserted = 0usize;
575 let mut inner = self.inner.write().unwrap();
576 for (hash, data) in items {
577 let key = to_hex(&hash);
578 if inner.data.contains_key(&key) {
579 continue;
580 }
581 let order = inner.next_order;
582 inner.next_order += 1;
583 inner.data.insert(key, MemoryEntry { data, order });
584 inserted += 1;
585 }
586 Ok(inserted)
587 }
588
589 async fn get(&self, hash: &Hash) -> Result<Option<Vec<u8>>, StoreError> {
590 let key = to_hex(hash);
591 let inner = self.inner.read().unwrap();
592 Ok(inner.data.get(&key).map(|e| e.data.clone()))
593 }
594
595 async fn get_range(
596 &self,
597 hash: &Hash,
598 start: u64,
599 end_inclusive: u64,
600 ) -> Result<Option<Vec<u8>>, StoreError> {
601 let key = to_hex(hash);
602 let inner = self.inner.read().unwrap();
603 inner
604 .data
605 .get(&key)
606 .map(|entry| slice_blob_range(&entry.data, start, end_inclusive))
607 .transpose()
608 }
609
610 async fn blob_size(&self, hash: &Hash) -> Result<Option<u64>, StoreError> {
611 let key = to_hex(hash);
612 let inner = self.inner.read().unwrap();
613 Ok(inner.data.get(&key).map(|entry| entry.data.len() as u64))
614 }
615
616 async fn has(&self, hash: &Hash) -> Result<bool, StoreError> {
617 let key = to_hex(hash);
618 Ok(self.inner.read().unwrap().data.contains_key(&key))
619 }
620
621 async fn delete(&self, hash: &Hash) -> Result<bool, StoreError> {
622 let key = to_hex(hash);
623 let mut inner = self.inner.write().unwrap();
624 inner.pins.remove(&key);
626 Ok(inner.data.remove(&key).is_some())
627 }
628
629 fn set_max_bytes(&self, max: u64) {
630 self.inner.write().unwrap().max_bytes = if max > 0 { Some(max) } else { None };
631 }
632
633 fn max_bytes(&self) -> Option<u64> {
634 self.inner.read().unwrap().max_bytes
635 }
636
637 async fn stats(&self) -> StoreStats {
638 let inner = self.inner.read().unwrap();
639 let mut count = 0u64;
640 let mut bytes = 0u64;
641 let mut pinned_count = 0u64;
642 let mut pinned_bytes = 0u64;
643
644 for (key, entry) in &inner.data {
645 count += 1;
646 bytes += entry.data.len() as u64;
647 if inner.pins.get(key).copied().unwrap_or(0) > 0 {
648 pinned_count += 1;
649 pinned_bytes += entry.data.len() as u64;
650 }
651 }
652
653 StoreStats {
654 count,
655 bytes,
656 pinned_count,
657 pinned_bytes,
658 }
659 }
660
661 async fn evict_if_needed(&self) -> Result<u64, StoreError> {
662 let max = match self.inner.read().unwrap().max_bytes {
663 Some(m) => m,
664 None => return Ok(0), };
666
667 let current: u64 = self
668 .inner
669 .read()
670 .unwrap()
671 .data
672 .values()
673 .map(|e| e.data.len() as u64)
674 .sum();
675
676 if current <= max {
677 return Ok(0);
678 }
679
680 let target = max * 9 / 10;
682 Ok(self.evict_to_target(target))
683 }
684
685 async fn pin(&self, hash: &Hash) -> Result<(), StoreError> {
686 let key = to_hex(hash);
687 let mut inner = self.inner.write().unwrap();
688 *inner.pins.entry(key).or_insert(0) += 1;
689 Ok(())
690 }
691
692 async fn unpin(&self, hash: &Hash) -> Result<(), StoreError> {
693 let key = to_hex(hash);
694 let mut inner = self.inner.write().unwrap();
695 if let Some(count) = inner.pins.get_mut(&key) {
696 if *count > 0 {
697 *count -= 1;
698 }
699 if *count == 0 {
700 inner.pins.remove(&key);
701 }
702 }
703 Ok(())
704 }
705
706 fn pin_count(&self, hash: &Hash) -> u32 {
707 let key = to_hex(hash);
708 self.inner
709 .read()
710 .unwrap()
711 .pins
712 .get(&key)
713 .copied()
714 .unwrap_or(0)
715 }
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721 use crate::hash::sha256;
722 use std::sync::atomic::{AtomicUsize, Ordering};
723
724 #[derive(Default)]
725 struct OptimisticBatchStore {
726 inner: MemoryStore,
727 regular_batches: AtomicUsize,
728 optimistic_batches: AtomicUsize,
729 }
730
731 #[async_trait]
732 impl Store for OptimisticBatchStore {
733 async fn put(&self, hash: Hash, data: Vec<u8>) -> Result<bool, StoreError> {
734 self.inner.put(hash, data).await
735 }
736
737 async fn put_many(&self, items: Vec<(Hash, Vec<u8>)>) -> Result<usize, StoreError> {
738 self.regular_batches.fetch_add(1, Ordering::Relaxed);
739 self.inner.put_many(items).await
740 }
741
742 async fn put_many_optimistic(
743 &self,
744 items: Vec<(Hash, Vec<u8>)>,
745 ) -> Result<usize, StoreError> {
746 self.optimistic_batches.fetch_add(1, Ordering::Relaxed);
747 self.inner.put_many(items).await
748 }
749
750 async fn get(&self, hash: &Hash) -> Result<Option<Vec<u8>>, StoreError> {
751 self.inner.get(hash).await
752 }
753
754 async fn has(&self, hash: &Hash) -> Result<bool, StoreError> {
755 self.inner.has(hash).await
756 }
757
758 async fn delete(&self, hash: &Hash) -> Result<bool, StoreError> {
759 self.inner.delete(hash).await
760 }
761 }
762
763 #[tokio::test]
764 async fn test_put_returns_true_for_new() {
765 let store = MemoryStore::new();
766 let data = vec![1u8, 2, 3];
767 let hash = sha256(&data);
768
769 let result = store.put(hash, data).await.unwrap();
770 assert!(result);
771 }
772
773 #[tokio::test]
774 async fn test_put_returns_false_for_duplicate() {
775 let store = MemoryStore::new();
776 let data = vec![1u8, 2, 3];
777 let hash = sha256(&data);
778
779 store.put(hash, data.clone()).await.unwrap();
780 let result = store.put(hash, data).await.unwrap();
781 assert!(!result);
782 }
783
784 #[tokio::test]
785 async fn test_put_many_counts_only_new_items() {
786 let store = MemoryStore::new();
787 let data1 = vec![1u8, 2, 3];
788 let data2 = vec![4u8, 5, 6];
789 let hash1 = sha256(&data1);
790 let hash2 = sha256(&data2);
791
792 store.put(hash1, data1.clone()).await.unwrap();
793 let inserted = store
794 .put_many(vec![(hash1, data1), (hash2, data2.clone())])
795 .await
796 .unwrap();
797
798 assert_eq!(inserted, 1);
799 assert_eq!(store.get(&hash2).await.unwrap(), Some(data2));
800 }
801
802 #[tokio::test]
803 async fn test_buffered_store_flushes_pending_writes() {
804 let base = std::sync::Arc::new(MemoryStore::new());
805 let buffered = BufferedStore::new(std::sync::Arc::clone(&base));
806 let data = vec![9u8, 8, 7];
807 let hash = sha256(&data);
808
809 assert!(buffered.put(hash, data.clone()).await.unwrap());
810 assert_eq!(buffered.get(&hash).await.unwrap(), Some(data.clone()));
811 assert_eq!(base.get(&hash).await.unwrap(), None);
812
813 let flushed = buffered.flush().await.unwrap();
814
815 assert_eq!(flushed, 1);
816 assert_eq!(base.get(&hash).await.unwrap(), Some(data));
817 }
818
819 #[tokio::test]
820 async fn test_optimistic_buffered_store_avoids_base_probe_but_preserves_contents() {
821 let base = std::sync::Arc::new(MemoryStore::new());
822 let buffered = BufferedStore::new_optimistic(std::sync::Arc::clone(&base));
823 let data = vec![4u8, 5, 6];
824 let hash = sha256(&data);
825
826 base.put(hash, data.clone()).await.unwrap();
827
828 assert!(buffered.put(hash, data.clone()).await.unwrap());
829 assert_eq!(buffered.get(&hash).await.unwrap(), Some(data.clone()));
830
831 let flushed = buffered.flush().await.unwrap();
832
833 assert_eq!(flushed, 0);
834 assert_eq!(base.get(&hash).await.unwrap(), Some(data));
835 }
836
837 #[tokio::test]
838 async fn test_optimistic_buffered_store_uses_idempotent_batch_flush() {
839 let base = std::sync::Arc::new(OptimisticBatchStore::default());
840 let buffered = BufferedStore::new_optimistic(std::sync::Arc::clone(&base));
841 let data = vec![7u8; 1024];
842 let hash = sha256(&data);
843
844 base.inner.put(hash, data.clone()).await.unwrap();
845 assert!(buffered.put(hash, data).await.unwrap());
846 assert_eq!(buffered.flush().await.unwrap(), 0);
847
848 assert_eq!(base.regular_batches.load(Ordering::Relaxed), 0);
849 assert_eq!(base.optimistic_batches.load(Ordering::Relaxed), 1);
850 }
851
852 #[tokio::test]
853 async fn threshold_coalesces_hints_but_explicit_flush_never_defers_writes() {
854 let base = Arc::new(OptimisticBatchStore::default());
855 let buffered = BufferedStore::new_optimistic(base.clone()).with_flush_threshold(8);
856 let first = vec![1; 4];
857 let second = vec![2; 4];
858 let h1 = sha256(&first);
859 let h2 = sha256(&second);
860 buffered.put(h1, first.clone()).await.unwrap();
861 assert!(!buffered.put(h1, first.clone()).await.unwrap());
862 assert_eq!(buffered.flush_pending_if_needed().await.unwrap(), 0);
863 assert_eq!(buffered.get(&h1).await.unwrap(), Some(first));
864 assert!(!base.has(&h1).await.unwrap());
865 buffered.put(h2, second.clone()).await.unwrap();
866 buffered.delete(&h1).await.unwrap();
867 assert_eq!(buffered.flush_pending_if_needed().await.unwrap(), 0);
868 assert_eq!(buffered.inner.read().unwrap().pending_bytes, 4);
869 buffered.put(h1, vec![1; 4]).await.unwrap();
870 assert_eq!(buffered.flush_pending_if_needed().await.unwrap(), 2);
871 assert_eq!(buffered.inner.read().unwrap().pending_bytes, 0);
872 assert_eq!(base.optimistic_batches.load(Ordering::Relaxed), 1);
873 assert_eq!(base.get(&h2).await.unwrap(), Some(second));
874
875 let tiny = vec![3];
876 let h3 = sha256(&tiny);
877 buffered.put(h3, tiny.clone()).await.unwrap();
878 assert_eq!(buffered.flush_pending().await.unwrap(), 1);
879 assert_eq!(base.get(&h3).await.unwrap(), Some(tiny));
880 assert_eq!(base.regular_batches.load(Ordering::Relaxed), 0);
881 }
882
883 #[tokio::test]
884 async fn threshold_does_not_leave_large_operations_pending_after_a_hint() {
885 let base = Arc::new(MemoryStore::new());
886 let buffered = BufferedStore::new(base.clone()).with_flush_threshold(8);
887 let bytes = vec![9; 16];
888 let hash = sha256(&bytes);
889 buffered.put(hash, bytes.clone()).await.unwrap();
890 assert_eq!(buffered.flush_pending_if_needed().await.unwrap(), 1);
891 assert_eq!(base.get(&hash).await.unwrap(), Some(bytes));
892 assert_eq!(buffered.inner.read().unwrap().pending_bytes, 0);
893
894 let ordinary = BufferedStore::new(base.clone());
895 let hash = sha256(&[5]);
896 ordinary.put(hash, vec![5]).await.unwrap();
897 assert_eq!(ordinary.flush_pending_if_needed().await.unwrap(), 1);
898 assert!(
899 base.has(&hash).await.unwrap(),
900 "default hints remain unconditional"
901 );
902 }
903
904 #[tokio::test]
905 async fn test_get_returns_data() {
906 let store = MemoryStore::new();
907 let data = vec![1u8, 2, 3];
908 let hash = sha256(&data);
909
910 store.put(hash, data.clone()).await.unwrap();
911 let result = store.get(&hash).await.unwrap();
912
913 assert_eq!(result, Some(data));
914 }
915
916 #[tokio::test]
917 async fn test_get_returns_none_for_missing() {
918 let store = MemoryStore::new();
919 let hash = [0u8; 32];
920
921 let result = store.get(&hash).await.unwrap();
922 assert!(result.is_none());
923 }
924
925 #[tokio::test]
926 async fn test_has_returns_true() {
927 let store = MemoryStore::new();
928 let data = vec![1u8, 2, 3];
929 let hash = sha256(&data);
930
931 store.put(hash, data).await.unwrap();
932 assert!(store.has(&hash).await.unwrap());
933 }
934
935 #[tokio::test]
936 async fn test_has_returns_false() {
937 let store = MemoryStore::new();
938 let hash = [0u8; 32];
939
940 assert!(!store.has(&hash).await.unwrap());
941 }
942
943 #[tokio::test]
944 async fn test_delete_returns_true() {
945 let store = MemoryStore::new();
946 let data = vec![1u8, 2, 3];
947 let hash = sha256(&data);
948
949 store.put(hash, data).await.unwrap();
950 let result = store.delete(&hash).await.unwrap();
951
952 assert!(result);
953 assert!(!store.has(&hash).await.unwrap());
954 }
955
956 #[tokio::test]
957 async fn test_delete_returns_false() {
958 let store = MemoryStore::new();
959 let hash = [0u8; 32];
960
961 let result = store.delete(&hash).await.unwrap();
962 assert!(!result);
963 }
964
965 #[tokio::test]
966 async fn test_size() {
967 let store = MemoryStore::new();
968 assert_eq!(store.size(), 0);
969
970 let data1 = vec![1u8];
971 let data2 = vec![2u8];
972 let hash1 = sha256(&data1);
973 let hash2 = sha256(&data2);
974
975 store.put(hash1, data1).await.unwrap();
976 store.put(hash2, data2).await.unwrap();
977
978 assert_eq!(store.size(), 2);
979 }
980
981 #[tokio::test]
982 async fn test_total_bytes() {
983 let store = MemoryStore::new();
984 assert_eq!(store.total_bytes(), 0);
985
986 let data1 = vec![1u8, 2, 3];
987 let data2 = vec![4u8, 5];
988 let hash1 = sha256(&data1);
989 let hash2 = sha256(&data2);
990
991 store.put(hash1, data1).await.unwrap();
992 store.put(hash2, data2).await.unwrap();
993
994 assert_eq!(store.total_bytes(), 5);
995 }
996
997 #[tokio::test]
998 async fn test_clear() {
999 let store = MemoryStore::new();
1000 let data = vec![1u8, 2, 3];
1001 let hash = sha256(&data);
1002
1003 store.put(hash, data).await.unwrap();
1004 store.clear();
1005
1006 assert_eq!(store.size(), 0);
1007 assert!(!store.has(&hash).await.unwrap());
1008 }
1009
1010 #[tokio::test]
1011 async fn test_keys() {
1012 let store = MemoryStore::new();
1013 assert!(store.keys().is_empty());
1014
1015 let data1 = vec![1u8];
1016 let data2 = vec![2u8];
1017 let hash1 = sha256(&data1);
1018 let hash2 = sha256(&data2);
1019
1020 store.put(hash1, data1).await.unwrap();
1021 store.put(hash2, data2).await.unwrap();
1022
1023 let keys = store.keys();
1024 assert_eq!(keys.len(), 2);
1025
1026 let mut hex_keys: Vec<_> = keys.iter().map(to_hex).collect();
1027 hex_keys.sort();
1028 let mut expected: Vec<_> = vec![to_hex(&hash1), to_hex(&hash2)];
1029 expected.sort();
1030 assert_eq!(hex_keys, expected);
1031 }
1032
1033 #[tokio::test]
1034 async fn test_pin_and_unpin() {
1035 let store = MemoryStore::new();
1036 let data = vec![1u8, 2, 3];
1037 let hash = sha256(&data);
1038
1039 store.put(hash, data).await.unwrap();
1040
1041 assert!(!store.is_pinned(&hash));
1043 assert_eq!(store.pin_count(&hash), 0);
1044
1045 store.pin(&hash).await.unwrap();
1047 assert!(store.is_pinned(&hash));
1048 assert_eq!(store.pin_count(&hash), 1);
1049
1050 store.unpin(&hash).await.unwrap();
1052 assert!(!store.is_pinned(&hash));
1053 assert_eq!(store.pin_count(&hash), 0);
1054 }
1055
1056 #[tokio::test]
1057 async fn test_pin_count_ref_counting() {
1058 let store = MemoryStore::new();
1059 let data = vec![1u8, 2, 3];
1060 let hash = sha256(&data);
1061
1062 store.put(hash, data).await.unwrap();
1063
1064 store.pin(&hash).await.unwrap();
1066 store.pin(&hash).await.unwrap();
1067 store.pin(&hash).await.unwrap();
1068 assert_eq!(store.pin_count(&hash), 3);
1069
1070 store.unpin(&hash).await.unwrap();
1072 assert_eq!(store.pin_count(&hash), 2);
1073 assert!(store.is_pinned(&hash));
1074
1075 store.unpin(&hash).await.unwrap();
1077 store.unpin(&hash).await.unwrap();
1078 assert_eq!(store.pin_count(&hash), 0);
1079 assert!(!store.is_pinned(&hash));
1080
1081 store.unpin(&hash).await.unwrap();
1083 assert_eq!(store.pin_count(&hash), 0);
1084 }
1085
1086 #[tokio::test]
1087 async fn test_stats() {
1088 let store = MemoryStore::new();
1089
1090 let data1 = vec![1u8, 2, 3]; let data2 = vec![4u8, 5]; let hash1 = sha256(&data1);
1093 let hash2 = sha256(&data2);
1094
1095 store.put(hash1, data1).await.unwrap();
1096 store.put(hash2, data2).await.unwrap();
1097
1098 store.pin(&hash1).await.unwrap();
1100
1101 let stats = store.stats().await;
1102 assert_eq!(stats.count, 2);
1103 assert_eq!(stats.bytes, 5);
1104 assert_eq!(stats.pinned_count, 1);
1105 assert_eq!(stats.pinned_bytes, 3);
1106 }
1107
1108 #[tokio::test]
1109 async fn test_max_bytes() {
1110 let store = MemoryStore::new();
1111 assert!(store.max_bytes().is_none());
1112
1113 store.set_max_bytes(1000);
1114 assert_eq!(store.max_bytes(), Some(1000));
1115
1116 store.set_max_bytes(0);
1118 assert!(store.max_bytes().is_none());
1119 }
1120
1121 #[tokio::test]
1122 async fn test_with_max_bytes() {
1123 let store = MemoryStore::with_max_bytes(500);
1124 assert_eq!(store.max_bytes(), Some(500));
1125
1126 let store_unlimited = MemoryStore::with_max_bytes(0);
1127 assert!(store_unlimited.max_bytes().is_none());
1128 }
1129
1130 #[tokio::test]
1131 async fn test_eviction_respects_pins() {
1132 let store = MemoryStore::with_max_bytes(10);
1134
1135 let data1 = vec![1u8, 1, 1]; let data2 = vec![2u8, 2, 2];
1138 let data3 = vec![3u8, 3, 3]; let hash1 = sha256(&data1);
1140 let hash2 = sha256(&data2);
1141 let hash3 = sha256(&data3);
1142
1143 store.put(hash1, data1).await.unwrap();
1144 store.put(hash2, data2).await.unwrap();
1145 store.put(hash3, data3).await.unwrap();
1146
1147 store.pin(&hash1).await.unwrap();
1149
1150 let data4 = vec![4u8, 4, 4];
1152 let hash4 = sha256(&data4);
1153 store.put(hash4, data4).await.unwrap();
1154
1155 let freed = store.evict_if_needed().await.unwrap();
1157 assert!(freed > 0);
1158
1159 assert!(store.has(&hash1).await.unwrap());
1161 assert!(!store.has(&hash2).await.unwrap());
1163 assert!(store.has(&hash3).await.unwrap());
1165 assert!(store.has(&hash4).await.unwrap());
1166 }
1167
1168 #[tokio::test]
1169 async fn test_eviction_lru_order() {
1170 let store = MemoryStore::with_max_bytes(15);
1172
1173 let data1 = vec![1u8; 5]; let data2 = vec![2u8; 5];
1176 let data3 = vec![3u8; 5];
1177 let data4 = vec![4u8; 5]; let hash1 = sha256(&data1);
1179 let hash2 = sha256(&data2);
1180 let hash3 = sha256(&data3);
1181 let hash4 = sha256(&data4);
1182
1183 store.put(hash1, data1).await.unwrap();
1184 store.put(hash2, data2).await.unwrap();
1185 store.put(hash3, data3).await.unwrap();
1186 store.put(hash4, data4).await.unwrap();
1187
1188 assert_eq!(store.total_bytes(), 20);
1190
1191 let freed = store.evict_if_needed().await.unwrap();
1193 assert!(freed >= 5); assert!(!store.has(&hash1).await.unwrap());
1197 assert!(store.has(&hash4).await.unwrap());
1199 }
1200
1201 #[tokio::test]
1202 async fn test_no_eviction_when_under_limit() {
1203 let store = MemoryStore::with_max_bytes(100);
1204
1205 let data = vec![1u8, 2, 3];
1206 let hash = sha256(&data);
1207 store.put(hash, data).await.unwrap();
1208
1209 let freed = store.evict_if_needed().await.unwrap();
1210 assert_eq!(freed, 0);
1211 assert!(store.has(&hash).await.unwrap());
1212 }
1213
1214 #[tokio::test]
1215 async fn test_no_eviction_without_limit() {
1216 let store = MemoryStore::new();
1217
1218 for i in 0..100u8 {
1220 let data = vec![i; 100];
1221 let hash = sha256(&data);
1222 store.put(hash, data).await.unwrap();
1223 }
1224
1225 let freed = store.evict_if_needed().await.unwrap();
1226 assert_eq!(freed, 0);
1227 assert_eq!(store.size(), 100);
1228 }
1229
1230 #[tokio::test]
1231 async fn test_delete_removes_pin() {
1232 let store = MemoryStore::new();
1233 let data = vec![1u8, 2, 3];
1234 let hash = sha256(&data);
1235
1236 store.put(hash, data).await.unwrap();
1237 store.pin(&hash).await.unwrap();
1238 assert!(store.is_pinned(&hash));
1239
1240 store.delete(&hash).await.unwrap();
1241 assert_eq!(store.pin_count(&hash), 0);
1243 }
1244}