1use std::collections::HashMap;
9use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
10use std::sync::Arc;
11use std::sync::Mutex;
12
13use async_trait::async_trait;
14use bytes::Bytes;
15
16use super::{
17 BoxPartSink, CloudAccessOutcome, CloudAccessState, CloudFileReadError, CloudHome,
18 CloudHomeError, CloudObjectVersion, CloudVersionedObject, ConditionalWriteOutcome,
19 ExactSlotStorage, PartSink,
20};
21use coven_protocol::objects::ObjectSlot;
22
23#[derive(Clone)]
24struct AppendPause {
25 call: usize,
26 reached: Arc<tokio::sync::Notify>,
27 release: Arc<tokio::sync::Notify>,
28}
29
30struct ExactStreamReadGuard {
31 inflight: Arc<AtomicUsize>,
32}
33
34struct InflightGuard {
35 inflight: Arc<AtomicUsize>,
36}
37
38#[derive(Clone)]
39struct ProbePause {
40 reached: Arc<tokio::sync::Notify>,
41 release: Arc<tokio::sync::Notify>,
42}
43
44#[derive(Clone)]
45struct MemoryObject {
46 bytes: Vec<u8>,
47 version: u64,
48}
49
50struct MemoryObjects {
51 values: HashMap<String, MemoryObject>,
52 next_version: u64,
53}
54
55impl MemoryObjects {
56 fn new() -> Self {
57 Self {
58 values: HashMap::new(),
59 next_version: 0,
60 }
61 }
62
63 fn insert(&mut self, key: String, bytes: Vec<u8>) -> Option<MemoryObject> {
64 self.next_version = self
65 .next_version
66 .checked_add(1)
67 .expect("in-memory cloud object version overflow");
68 self.values.insert(
69 key,
70 MemoryObject {
71 bytes,
72 version: self.next_version,
73 },
74 )
75 }
76
77 fn bytes(&self, key: &str) -> Option<Vec<u8>> {
78 self.values.get(key).map(|object| object.bytes.clone())
79 }
80}
81
82impl Drop for ExactStreamReadGuard {
83 fn drop(&mut self) {
84 self.inflight.fetch_sub(1, Ordering::SeqCst);
85 }
86}
87
88impl Drop for InflightGuard {
89 fn drop(&mut self) {
90 self.inflight.fetch_sub(1, Ordering::SeqCst);
91 }
92}
93
94#[derive(Clone)]
106pub struct InMemoryCloudHome {
107 provider_binding: coven_protocol::objects::ResolvedProviderBinding,
108 writes: Arc<Mutex<MemoryObjects>>,
109 exact_slot_allocations: Arc<AtomicUsize>,
110 exact_slot_allocation_delay_millis: Arc<AtomicU64>,
111 exact_slot_allocation_inflight: Arc<AtomicUsize>,
112 exact_slot_allocation_max_inflight: Arc<AtomicUsize>,
113 deletes: Arc<Mutex<Vec<String>>>,
114 fail_writes: Arc<AtomicBool>,
115 fail_next_range_reads: Arc<AtomicUsize>,
116 fail_next_exact_stream_reads: Arc<AtomicUsize>,
117 sort_listings: Arc<AtomicBool>,
118 exact_create_count: Arc<AtomicUsize>,
119 exact_creates: Arc<Mutex<Vec<ObjectSlot>>>,
120 fail_exact_create_before: Arc<AtomicUsize>,
121 fail_exact_create_after: Arc<AtomicUsize>,
122 lose_next_conditional_replace_response: Arc<AtomicBool>,
123 exact_create_pause: Arc<Mutex<Option<AppendPause>>>,
124 probe_pause: Arc<Mutex<Option<ProbePause>>>,
125 probe_failure: Arc<Mutex<Option<coven_protocol::objects::StorageBackendFailure>>>,
126 exact_full_read_count: Arc<AtomicUsize>,
127 exact_full_read_delay_millis: Arc<AtomicU64>,
128 exact_list_count: Arc<AtomicUsize>,
129 exact_listed_prefixes: Arc<Mutex<Vec<String>>>,
130 exact_full_read_inflight: Arc<AtomicUsize>,
131 exact_full_read_max_inflight: Arc<AtomicUsize>,
132 exact_stream_read_count: Arc<AtomicUsize>,
133 exact_reads: Arc<Mutex<Vec<ObjectSlot>>>,
134 exact_stream_read_inflight: Arc<AtomicUsize>,
135 exact_stream_read_max_inflight: Arc<AtomicUsize>,
136 exact_stream_read_barrier: Arc<Mutex<Option<Arc<tokio::sync::Barrier>>>>,
137 exact_stream_read_chunk_bytes: Arc<AtomicUsize>,
138 exact_stream_read_chunk_delay_millis: Arc<AtomicU64>,
139 exact_delete_count: Arc<AtomicUsize>,
140 exact_range_reads: Arc<Mutex<Vec<(u64, u64)>>>,
146 fail_exact_delete_on: Arc<AtomicUsize>,
147 fail_exact_delete_of: Arc<Mutex<Option<TargetedDeleteFailure>>>,
148}
149
150struct TargetedDeleteFailure {
153 keys: std::collections::HashSet<String>,
154 countdown: usize,
155 failure: TargetedDeleteFailureKind,
156}
157
158enum TargetedDeleteFailureKind {
161 Transport,
162 Permanent,
163}
164
165impl InMemoryCloudHome {
166 pub fn new() -> Self {
167 Self {
168 provider_binding: coven_protocol::objects::ResolvedProviderBinding {
169 store: coven_protocol::objects::StoreProviderBinding::S3 {
170 endpoint: coven_protocol::objects::S3EndpointBinding::Custom {
171 origin: "https://in-memory.invalid".to_string(),
172 },
173 region: "test".to_string(),
174 bucket: "in-memory".to_string(),
175 key_prefix: None,
176 },
177 device: coven_protocol::objects::ProviderDeviceBinding {
178 principal: coven_protocol::objects::ProviderPrincipalId::CustomS3Credential {
179 access_key_id_hash: coven_protocol::store_commit::ObjectHash::digest(
180 b"coven.s3-access-key-id.v1\0in-memory",
181 ),
182 },
183 },
184 },
185 writes: Arc::new(Mutex::new(MemoryObjects::new())),
186 exact_slot_allocations: Arc::new(AtomicUsize::new(0)),
187 exact_slot_allocation_delay_millis: Arc::new(AtomicU64::new(0)),
188 exact_slot_allocation_inflight: Arc::new(AtomicUsize::new(0)),
189 exact_slot_allocation_max_inflight: Arc::new(AtomicUsize::new(0)),
190 deletes: Arc::new(Mutex::new(Vec::new())),
191 fail_writes: Arc::new(AtomicBool::new(false)),
192 fail_next_range_reads: Arc::new(AtomicUsize::new(0)),
193 fail_next_exact_stream_reads: Arc::new(AtomicUsize::new(0)),
194 sort_listings: Arc::new(AtomicBool::new(false)),
195 exact_create_count: Arc::new(AtomicUsize::new(0)),
196 exact_creates: Arc::new(Mutex::new(Vec::new())),
197 fail_exact_create_before: Arc::new(AtomicUsize::new(0)),
198 fail_exact_create_after: Arc::new(AtomicUsize::new(0)),
199 lose_next_conditional_replace_response: Arc::new(AtomicBool::new(false)),
200 exact_create_pause: Arc::new(Mutex::new(None)),
201 probe_pause: Arc::new(Mutex::new(None)),
202 probe_failure: Arc::new(Mutex::new(None)),
203 exact_full_read_count: Arc::new(AtomicUsize::new(0)),
204 exact_list_count: Arc::new(AtomicUsize::new(0)),
205 exact_listed_prefixes: Arc::new(Mutex::new(Vec::new())),
206 exact_full_read_delay_millis: Arc::new(AtomicU64::new(0)),
207 exact_full_read_inflight: Arc::new(AtomicUsize::new(0)),
208 exact_full_read_max_inflight: Arc::new(AtomicUsize::new(0)),
209 exact_stream_read_count: Arc::new(AtomicUsize::new(0)),
210 exact_reads: Arc::new(Mutex::new(Vec::new())),
211 exact_stream_read_inflight: Arc::new(AtomicUsize::new(0)),
212 exact_stream_read_max_inflight: Arc::new(AtomicUsize::new(0)),
213 exact_stream_read_barrier: Arc::new(Mutex::new(None)),
214 exact_stream_read_chunk_bytes: Arc::new(AtomicUsize::new(0)),
215 exact_stream_read_chunk_delay_millis: Arc::new(AtomicU64::new(0)),
216 exact_delete_count: Arc::new(AtomicUsize::new(0)),
217 exact_range_reads: Arc::new(Mutex::new(Vec::new())),
218 fail_exact_delete_on: Arc::new(AtomicUsize::new(0)),
219 fail_exact_delete_of: Arc::new(Mutex::new(None)),
220 }
221 }
222
223 pub fn with_provider_binding(
224 mut self,
225 binding: coven_protocol::objects::ResolvedProviderBinding,
226 ) -> Self {
227 binding
228 .validate()
229 .expect("in-memory provider binding must be valid");
230 self.provider_binding = binding;
231 self
232 }
233
234 pub fn sort_listings(&self) {
240 self.sort_listings.store(true, Ordering::SeqCst);
241 }
242
243 pub fn arm_write_failures(&self) {
248 self.fail_writes.store(true, Ordering::SeqCst);
249 }
250
251 pub fn clear_write_failures(&self) {
254 self.fail_writes.store(false, Ordering::SeqCst);
255 }
256
257 pub fn fail_next_range_reads(&self, n: usize) {
262 self.fail_next_range_reads.store(n, Ordering::SeqCst);
263 }
264
265 pub fn fail_next_exact_stream_reads(&self, n: usize) {
269 self.fail_next_exact_stream_reads.store(n, Ordering::SeqCst);
270 }
271
272 pub fn stream_exact_reads_in_chunks(&self, chunk_bytes: usize, delay: std::time::Duration) {
276 assert!(chunk_bytes > 0, "stream chunk size must be nonzero");
277 self.exact_stream_read_chunk_bytes
278 .store(chunk_bytes, Ordering::SeqCst);
279 self.exact_stream_read_chunk_delay_millis.store(
280 u64::try_from(delay.as_millis()).expect("test stream delay fits u64 milliseconds"),
281 Ordering::SeqCst,
282 );
283 }
284
285 pub fn fail_exact_create_before_call(&self, call: usize) {
287 assert!(call > 0, "create call numbers are 1-based");
288 self.exact_create_count.store(0, Ordering::SeqCst);
289 self.fail_exact_create_before.store(call, Ordering::SeqCst);
290 }
291
292 pub fn fail_exact_create_after_call(&self, call: usize) {
294 assert!(call > 0, "create call numbers are 1-based");
295 self.exact_create_count.store(0, Ordering::SeqCst);
296 self.fail_exact_create_after.store(call, Ordering::SeqCst);
297 }
298
299 pub fn lose_next_conditional_replace_response(&self) {
302 self.lose_next_conditional_replace_response
303 .store(true, Ordering::SeqCst);
304 }
305
306 pub fn pause_after_exact_create_call(
308 &self,
309 call: usize,
310 ) -> (Arc<tokio::sync::Notify>, Arc<tokio::sync::Notify>) {
311 assert!(call > 0, "create call numbers are 1-based");
312 self.exact_create_count.store(0, Ordering::SeqCst);
313 let reached = Arc::new(tokio::sync::Notify::new());
314 let release = Arc::new(tokio::sync::Notify::new());
315 *self.exact_create_pause.lock().unwrap() = Some(AppendPause {
316 call,
317 reached: reached.clone(),
318 release: release.clone(),
319 });
320 (reached, release)
321 }
322
323 pub fn pause_next_probe(&self) -> (Arc<tokio::sync::Notify>, Arc<tokio::sync::Notify>) {
325 let reached = Arc::new(tokio::sync::Notify::new());
326 let release = Arc::new(tokio::sync::Notify::new());
327 *self.probe_pause.lock().unwrap() = Some(ProbePause {
328 reached: reached.clone(),
329 release: release.clone(),
330 });
331 (reached, release)
332 }
333
334 pub fn fail_next_probe_with(&self, failure: coven_protocol::objects::StorageBackendFailure) {
335 *self.probe_failure.lock().unwrap() = Some(failure);
336 }
337
338 pub fn exact_create_count(&self) -> usize {
339 self.exact_create_count.load(Ordering::SeqCst)
340 }
341
342 pub fn delay_exact_slot_allocations(&self, delay: std::time::Duration) {
343 self.exact_slot_allocation_delay_millis.store(
344 u64::try_from(delay.as_millis()).expect("test allocation delay fits u64 milliseconds"),
345 Ordering::SeqCst,
346 );
347 self.exact_slot_allocation_inflight
348 .store(0, Ordering::SeqCst);
349 self.exact_slot_allocation_max_inflight
350 .store(0, Ordering::SeqCst);
351 }
352
353 pub fn exact_slot_allocation_max_inflight(&self) -> usize {
354 self.exact_slot_allocation_max_inflight
355 .load(Ordering::SeqCst)
356 }
357
358 pub fn exact_creates(&self) -> Vec<ObjectSlot> {
359 self.exact_creates.lock().unwrap().clone()
360 }
361
362 pub fn clear_exact_creates(&self) {
363 self.exact_creates.lock().unwrap().clear();
364 }
365
366 pub fn exact_full_read_count(&self) -> usize {
367 self.exact_full_read_count.load(Ordering::SeqCst)
368 }
369
370 pub fn exact_list_count(&self) -> usize {
371 self.exact_list_count.load(Ordering::SeqCst)
372 }
373
374 pub fn exact_listed_prefixes(&self) -> Vec<String> {
375 self.exact_listed_prefixes.lock().unwrap().clone()
376 }
377
378 pub fn clear_exact_listings(&self) {
379 self.exact_list_count.store(0, Ordering::SeqCst);
380 self.exact_listed_prefixes.lock().unwrap().clear();
381 }
382
383 pub fn delay_exact_full_reads(&self, delay: std::time::Duration) {
385 self.exact_full_read_delay_millis.store(
386 u64::try_from(delay.as_millis()).expect("test read delay fits u64 milliseconds"),
387 Ordering::SeqCst,
388 );
389 self.exact_full_read_inflight.store(0, Ordering::SeqCst);
390 self.exact_full_read_max_inflight.store(0, Ordering::SeqCst);
391 }
392
393 pub fn exact_full_read_max_inflight(&self) -> usize {
394 self.exact_full_read_max_inflight.load(Ordering::SeqCst)
395 }
396
397 pub fn exact_range_reads(&self) -> Vec<(u64, u64)> {
399 self.exact_range_reads.lock().unwrap().clone()
400 }
401
402 pub fn exact_range_read_bytes(&self) -> u64 {
404 self.exact_range_reads
405 .lock()
406 .unwrap()
407 .iter()
408 .map(|(start, end)| end - start)
409 .sum()
410 }
411
412 pub fn clear_exact_range_reads(&self) {
413 self.exact_range_reads.lock().unwrap().clear();
414 }
415
416 pub fn exact_stream_read_count(&self) -> usize {
417 self.exact_stream_read_count.load(Ordering::SeqCst)
418 }
419
420 pub fn exact_reads(&self) -> Vec<ObjectSlot> {
421 self.exact_reads.lock().unwrap().clone()
422 }
423
424 pub fn clear_exact_reads(&self) {
425 self.exact_reads.lock().unwrap().clear();
426 }
427
428 pub fn arm_exact_stream_read_concurrency_probe(&self, width: usize) {
429 assert!(width > 0, "exact stream read probe width must be positive");
430 self.exact_stream_read_inflight.store(0, Ordering::SeqCst);
431 self.exact_stream_read_max_inflight
432 .store(0, Ordering::SeqCst);
433 *self.exact_stream_read_barrier.lock().unwrap() =
434 Some(Arc::new(tokio::sync::Barrier::new(width)));
435 }
436
437 pub fn exact_stream_read_max_inflight(&self) -> usize {
438 self.exact_stream_read_max_inflight.load(Ordering::SeqCst)
439 }
440
441 pub fn exact_delete_count(&self) -> usize {
442 self.exact_delete_count.load(Ordering::SeqCst)
443 }
444
445 pub fn fail_exact_delete_on_call(&self, call: usize) {
446 assert!(call > 0, "exact-delete call numbers are 1-based");
447 self.exact_delete_count.store(0, Ordering::SeqCst);
448 self.fail_exact_delete_on.store(call, Ordering::SeqCst);
449 }
450
451 pub fn fail_nth_exact_delete_of(&self, slots: &[&ObjectSlot], nth: usize) {
458 self.arm_targeted_delete_failure(slots, nth, TargetedDeleteFailureKind::Transport);
459 }
460
461 pub fn fail_nth_exact_delete_of_permanently(&self, slots: &[&ObjectSlot], nth: usize) {
467 self.arm_targeted_delete_failure(slots, nth, TargetedDeleteFailureKind::Permanent);
468 }
469
470 fn arm_targeted_delete_failure(
471 &self,
472 slots: &[&ObjectSlot],
473 nth: usize,
474 failure: TargetedDeleteFailureKind,
475 ) {
476 assert!(nth > 0, "targeted delete ordinals are 1-based");
477 let keys = slots
478 .iter()
479 .map(|slot| Self::exact_storage_key(slot).expect("test exact slot is valid"))
480 .collect();
481 *self.fail_exact_delete_of.lock().unwrap() = Some(TargetedDeleteFailure {
482 keys,
483 countdown: nth,
484 failure,
485 });
486 }
487
488 pub fn remove(&self, key: &str) {
492 self.writes.lock().unwrap().values.remove(key);
493 }
494
495 pub fn keys(&self) -> Vec<String> {
498 self.writes.lock().unwrap().values.keys().cloned().collect()
499 }
500
501 pub fn get(&self, key: &str) -> Option<Vec<u8>> {
505 self.writes.lock().unwrap().bytes(key)
506 }
507
508 pub fn len(&self) -> usize {
510 self.writes.lock().unwrap().values.len()
511 }
512
513 pub fn is_empty(&self) -> bool {
515 self.writes.lock().unwrap().values.is_empty()
516 }
517
518 pub fn deletes_seen(&self) -> Vec<String> {
520 self.deletes.lock().unwrap().clone()
521 }
522
523 pub fn insert_exact_object(&self, logical_key: &str, bytes: Vec<u8>) -> ObjectSlot {
525 let slot = ObjectSlot::logical(logical_key.to_string())
526 .map_err(CloudHomeError::from)
527 .expect("test logical key is non-empty");
528 self.writes
529 .lock()
530 .unwrap()
531 .insert(logical_key.to_string(), bytes);
532 slot
533 }
534
535 pub fn stored_exact_bytes(&self, slot: &ObjectSlot) -> Option<Vec<u8>> {
537 let key = Self::exact_storage_key(slot).expect("test exact slot is valid");
538 self.writes.lock().unwrap().bytes(&key)
539 }
540
541 pub fn contains_exact_object(&self, object: &coven_protocol::objects::ExactObjectRef) -> bool {
543 let key = Self::exact_storage_key(object.slot()).expect("test exact slot is valid");
544 self.writes.lock().unwrap().values.contains_key(&key)
545 }
546
547 pub fn restore_exact_object(&self, slot: &ObjectSlot, bytes: Vec<u8>) {
550 let key = Self::exact_storage_key(slot).expect("test exact slot is valid");
551 self.writes.lock().unwrap().insert(key, bytes);
552 }
553
554 pub fn remove_exact_object(&self, slot: &ObjectSlot) {
556 let key = Self::exact_storage_key(slot).expect("test exact slot is valid");
557 self.writes.lock().unwrap().values.remove(&key);
558 }
559
560 pub fn stored_exact_object(&self, slot: &ObjectSlot) -> Vec<u8> {
562 self.writes
563 .lock()
564 .unwrap()
565 .bytes(&Self::exact_storage_key(slot).expect("test exact slot is valid"))
566 .expect("exact slot exists")
567 }
568
569 pub fn replace_exact_object(&self, slot: &ObjectSlot, bytes: Vec<u8>) {
571 let previous = self.writes.lock().unwrap().insert(
572 Self::exact_storage_key(slot).expect("test exact slot is valid"),
573 bytes,
574 );
575 assert!(previous.is_some(), "exact slot exists");
576 }
577
578 async fn put_object(&self, key: &str, data: Vec<u8>) -> Result<(), CloudHomeError> {
579 if self.fail_writes.load(Ordering::SeqCst) {
580 return Err(CloudHomeError::Transport(
581 "InMemoryCloudHome: armed write failure".into(),
582 ));
583 }
584 self.writes.lock().unwrap().insert(key.to_string(), data);
585 Ok(())
586 }
587
588 async fn open_multipart<'a>(
589 &'a self,
590 key: &str,
591 _total_len: u64,
592 ) -> Result<BoxPartSink<'a>, CloudHomeError> {
593 if self.fail_writes.load(Ordering::SeqCst) {
596 return Err(CloudHomeError::Transport(
597 "InMemoryCloudHome: armed write failure".into(),
598 ));
599 }
600 Ok(Box::new(InMemoryPartSink {
601 writes: self.writes.clone(),
602 key: key.to_string(),
603 buf: Vec::new(),
604 }))
605 }
606
607 fn multipart_threshold(&self) -> u64 {
608 super::PROGRESS_CHUNK_SIZE as u64
611 }
612 fn validate_exact_slot(slot: &ObjectSlot) -> Result<(), CloudHomeError> {
613 slot.validate()?;
614 Ok(())
615 }
616
617 fn exact_storage_key(slot: &ObjectSlot) -> Result<String, CloudHomeError> {
618 Self::validate_exact_slot(slot)?;
619 Ok(match slot.physical() {
620 coven_protocol::objects::PhysicalObjectLocator::LogicalKey => {
621 slot.logical_key().to_string()
622 }
623 coven_protocol::objects::PhysicalObjectLocator::Opaque(provider_id) => {
624 format!("{}#exact#{provider_id}", slot.logical_key())
625 }
626 })
627 }
628
629 fn exact_slot_from_storage_key(key: &str) -> Result<ObjectSlot, CloudHomeError> {
633 match key.split_once("#exact#") {
634 Some((logical_key, provider_id)) => {
635 ObjectSlot::opaque(logical_key.to_string(), provider_id.to_string())
636 }
637 None => ObjectSlot::logical(key.to_string()),
638 }
639 .map_err(CloudHomeError::from)
640 }
641
642 async fn create_at_slot(
643 &self,
644 upload: &super::ExactUpload<'_>,
645 control: &super::UploadControl,
646 ) -> Result<super::ExactCreateOutcome, CloudHomeError> {
647 if self.fail_writes.load(Ordering::SeqCst) {
648 return Err(CloudHomeError::Transport(
649 "InMemoryCloudHome: armed write failure".into(),
650 ));
651 }
652 let slot = upload.object().slot();
653 self.exact_creates.lock().unwrap().push(slot.clone());
654 let key = Self::exact_storage_key(slot)?;
655 let call = self.exact_create_count.fetch_add(1, Ordering::SeqCst) + 1;
656 if self.fail_exact_create_before.load(Ordering::SeqCst) == call {
657 self.fail_exact_create_before.store(0, Ordering::SeqCst);
658 return Err(CloudHomeError::Transport(format!(
659 "InMemoryCloudHome: forced failure before exact create call {call}"
660 )));
661 }
662 let bytes = upload.body().await?.collect().await?;
663 control.report(bytes.len() as u64);
664 {
665 let mut writes = self.writes.lock().unwrap();
666 if let Some(existing) = writes.values.get(&key) {
667 return if upload.object().verify(&existing.bytes).is_ok() {
668 Ok(super::ExactCreateOutcome::AlreadyPresent)
669 } else {
670 Err(CloudHomeError::SlotCollision(key))
671 };
672 }
673 writes.insert(key.clone(), bytes);
674 }
675 let pause = self
676 .exact_create_pause
677 .lock()
678 .unwrap()
679 .clone()
680 .filter(|pause| pause.call == call);
681 if let Some(pause) = pause {
682 pause.reached.notify_one();
683 pause.release.notified().await;
684 self.exact_create_pause.lock().unwrap().take();
685 }
686 if self.fail_exact_create_after.load(Ordering::SeqCst) == call {
687 self.fail_exact_create_after.store(0, Ordering::SeqCst);
688 let stored_matches = self
689 .writes
690 .lock()
691 .unwrap()
692 .values
693 .get(&key)
694 .is_some_and(|stored| upload.object().verify(&stored.bytes).is_ok());
695 if !stored_matches {
696 return Err(CloudHomeError::Transport(format!(
697 "InMemoryCloudHome: forced failure after exact create call {call}"
698 )));
699 }
700 }
701 let stored_matches = self
702 .writes
703 .lock()
704 .unwrap()
705 .values
706 .get(&key)
707 .is_some_and(|stored| upload.object().verify(&stored.bytes).is_ok());
708 if !stored_matches {
709 return Err(CloudHomeError::SlotCollision(key));
710 }
711 Ok(super::ExactCreateOutcome::Created)
712 }
713
714 async fn read_exact(&self, slot: &ObjectSlot) -> Result<Vec<u8>, CloudHomeError> {
715 self.exact_full_read_count.fetch_add(1, Ordering::SeqCst);
716 let inflight = self.exact_full_read_inflight.fetch_add(1, Ordering::SeqCst) + 1;
717 self.exact_full_read_max_inflight
718 .fetch_max(inflight, Ordering::SeqCst);
719 let _guard = InflightGuard {
720 inflight: self.exact_full_read_inflight.clone(),
721 };
722 let delay = self.exact_full_read_delay_millis.load(Ordering::SeqCst);
723 if delay > 0 {
724 tokio::time::sleep(std::time::Duration::from_millis(delay)).await;
725 }
726 let key = Self::exact_storage_key(slot)?;
727 self.exact_reads.lock().unwrap().push(slot.clone());
728 self.writes
729 .lock()
730 .unwrap()
731 .bytes(&key)
732 .ok_or_else(|| CloudHomeError::NotFound(slot.logical_key().to_string()))
733 }
734
735 async fn read_exact_to_file(
736 &self,
737 slot: &ObjectSlot,
738 destination: &std::path::Path,
739 progress: super::DownloadProgress,
740 ) -> Result<(), CloudFileReadError> {
741 if self
742 .fail_next_exact_stream_reads
743 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| n.checked_sub(1))
744 .is_ok()
745 {
746 return Err(CloudHomeError::Transport(
747 "InMemoryCloudHome: armed exact stream-read failure".into(),
748 )
749 .into());
750 }
751 self.exact_stream_read_count.fetch_add(1, Ordering::SeqCst);
752 self.exact_reads.lock().unwrap().push(slot.clone());
753 let inflight = self
754 .exact_stream_read_inflight
755 .fetch_add(1, Ordering::SeqCst)
756 + 1;
757 self.exact_stream_read_max_inflight
758 .fetch_max(inflight, Ordering::SeqCst);
759 let _guard = ExactStreamReadGuard {
760 inflight: self.exact_stream_read_inflight.clone(),
761 };
762 let barrier = self.exact_stream_read_barrier.lock().unwrap().clone();
763 if let Some(barrier) = barrier {
764 barrier.wait().await;
765 }
766 let key = Self::exact_storage_key(slot)?;
767 let bytes = self
768 .writes
769 .lock()
770 .unwrap()
771 .bytes(&key)
772 .ok_or_else(|| CloudHomeError::NotFound(slot.logical_key().to_string()))?;
773 let chunk_bytes = self.exact_stream_read_chunk_bytes.load(Ordering::SeqCst);
774 let chunk_delay = std::time::Duration::from_millis(
775 self.exact_stream_read_chunk_delay_millis
776 .load(Ordering::SeqCst),
777 );
778 let chunks = if chunk_bytes == 0 {
779 vec![bytes::Bytes::from(bytes)]
780 } else {
781 bytes
782 .chunks(chunk_bytes)
783 .map(bytes::Bytes::copy_from_slice)
784 .collect::<Vec<_>>()
785 };
786 let stream = futures_util::StreamExt::then(
787 futures_util::stream::iter(chunks),
788 move |chunk| async move {
789 if !chunk_delay.is_zero() {
790 tokio::time::sleep(chunk_delay).await;
791 }
792 Ok(chunk)
793 },
794 );
795 super::write_cloud_object_stream(destination, Box::pin(stream), progress).await?;
796 Ok(())
797 }
798
799 async fn delete_exact(&self, slot: &ObjectSlot) -> Result<(), CloudHomeError> {
800 let key = Self::exact_storage_key(slot)?;
801 let call = self.exact_delete_count.fetch_add(1, Ordering::SeqCst) + 1;
802 if self.fail_exact_delete_on.load(Ordering::SeqCst) == call {
803 self.fail_exact_delete_on.store(0, Ordering::SeqCst);
804 return Err(CloudHomeError::Transport(format!(
805 "InMemoryCloudHome: forced exact delete failure on call {call}"
806 )));
807 }
808 {
809 let mut targeted = self.fail_exact_delete_of.lock().unwrap();
810 if let Some(failure) = targeted.as_mut() {
811 if failure.keys.contains(&key) {
812 failure.countdown -= 1;
813 if failure.countdown == 0 {
814 let error = match failure.failure {
815 TargetedDeleteFailureKind::Transport => CloudHomeError::Transport(
816 format!("InMemoryCloudHome: forced exact delete failure of {key}"),
817 ),
818 TargetedDeleteFailureKind::Permanent => CloudHomeError::Configuration(
819 format!("InMemoryCloudHome: refused to delete {key}"),
820 ),
821 };
822 *targeted = None;
823 return Err(error);
824 }
825 }
826 }
827 }
828 self.writes.lock().unwrap().values.remove(&key);
829 self.deletes.lock().unwrap().push(key);
830 Ok(())
831 }
832 async fn read(&self, key: &str) -> Result<Vec<u8>, CloudHomeError> {
833 self.writes
834 .lock()
835 .unwrap()
836 .bytes(key)
837 .ok_or_else(|| CloudHomeError::NotFound(key.to_string()))
838 }
839
840 async fn read_range(&self, key: &str, start: u64, end: u64) -> Result<Vec<u8>, CloudHomeError> {
841 if self
845 .fail_next_range_reads
846 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| n.checked_sub(1))
847 .is_ok()
848 {
849 return Err(CloudHomeError::Transport(
850 "InMemoryCloudHome: armed range-read failure".into(),
851 ));
852 }
853 let data = self.read(key).await?;
854 let s = start as usize;
855 let e = (end as usize).min(data.len());
856 if s > data.len() {
857 return Err(CloudHomeError::NotFound(format!("range past end of {key}")));
858 }
859 Ok(data[s..e].to_vec())
860 }
861
862 async fn list(&self, prefix: &str) -> Result<Vec<String>, CloudHomeError> {
863 let mut keys: Vec<String> = self
864 .writes
865 .lock()
866 .unwrap()
867 .values
868 .keys()
869 .filter(|k| k.starts_with(prefix))
870 .cloned()
871 .collect();
872 if self.sort_listings.load(Ordering::SeqCst) {
873 keys.sort();
874 }
875 Ok(keys)
876 }
877
878 async fn delete(&self, key: &str) -> Result<(), CloudHomeError> {
879 self.writes.lock().unwrap().values.remove(key);
880 self.deletes.lock().unwrap().push(key.to_string());
881 Ok(())
882 }
883
884 async fn exists(&self, key: &str) -> Result<bool, CloudHomeError> {
885 Ok(self.writes.lock().unwrap().values.contains_key(key))
886 }
887
888 async fn set_access(
889 &self,
890 desired: super::CloudAccessState,
891 ) -> Result<super::CloudAccessOutcome, CloudHomeError> {
892 Ok(match desired {
893 super::CloudAccessState::Present { .. } => {
894 super::CloudAccessOutcome::Present(super::CloudHomeJoinInfo::S3 {
895 bucket: "in-memory".to_string(),
896 region: "test".to_string(),
897 endpoint: Some("https://in-memory.invalid".to_string()),
898 access_key: "in-memory".to_string(),
899 secret_key: "in-memory".to_string(),
900 key_prefix: None,
901 })
902 }
903 super::CloudAccessState::Absent { .. } => {
904 super::CloudAccessOutcome::Absent(super::RevokeOutcome::Unsupported)
905 }
906 })
907 }
908}
909
910impl Default for InMemoryCloudHome {
911 fn default() -> Self {
912 Self::new()
913 }
914}
915
916struct InMemoryPartSink {
920 writes: Arc<Mutex<MemoryObjects>>,
921 key: String,
922 buf: Vec<u8>,
923}
924
925#[async_trait]
926impl PartSink for InMemoryPartSink {
927 fn part_size(&self) -> usize {
928 super::PROGRESS_CHUNK_SIZE
929 }
930
931 async fn send_part(
932 &mut self,
933 part: Bytes,
934 _offset: u64,
935 _is_last: bool,
936 _control: &super::UploadControl,
937 ) -> Result<(), CloudHomeError> {
938 self.buf.extend_from_slice(&part);
939 Ok(())
940 }
941
942 async fn abort(&mut self) -> Result<(), CloudHomeError> {
943 Ok(())
944 }
945
946 async fn finish(self: Box<Self>) -> Result<(), CloudHomeError> {
947 self.writes.lock().unwrap().insert(self.key, self.buf);
948 Ok(())
949 }
950}
951
952#[async_trait]
953impl CloudHome for InMemoryCloudHome {
954 async fn probe(&self) -> Result<(), CloudHomeError> {
955 let pause = self.probe_pause.lock().unwrap().take();
956 if let Some(pause) = pause {
957 pause.reached.notify_one();
958 pause.release.notified().await;
959 }
960 if let Some(kind) = self.probe_failure.lock().unwrap().take() {
961 return Err(CloudHomeError::backend(
962 kind,
963 "probe in-memory cloud home",
964 std::io::Error::other("injected provider probe failure"),
965 ));
966 }
967 Ok(())
968 }
969
970 async fn put_object(&self, key: &str, data: Vec<u8>) -> Result<(), CloudHomeError> {
971 InMemoryCloudHome::put_object(self, key, data).await
972 }
973
974 async fn open_multipart<'a>(
975 &'a self,
976 key: &str,
977 total_len: u64,
978 ) -> Result<BoxPartSink<'a>, CloudHomeError> {
979 InMemoryCloudHome::open_multipart(self, key, total_len).await
980 }
981
982 fn multipart_threshold(&self) -> u64 {
983 InMemoryCloudHome::multipart_threshold(self)
984 }
985
986 async fn read(&self, key: &str) -> Result<Vec<u8>, CloudHomeError> {
987 InMemoryCloudHome::read(self, key).await
988 }
989
990 async fn read_range(&self, key: &str, start: u64, end: u64) -> Result<Vec<u8>, CloudHomeError> {
991 InMemoryCloudHome::read_range(self, key, start, end).await
992 }
993
994 async fn list(&self, prefix: &str) -> Result<Vec<String>, CloudHomeError> {
995 InMemoryCloudHome::list(self, prefix).await
996 }
997
998 async fn delete(&self, key: &str) -> Result<(), CloudHomeError> {
999 InMemoryCloudHome::delete(self, key).await
1000 }
1001
1002 async fn exists(&self, key: &str) -> Result<bool, CloudHomeError> {
1003 InMemoryCloudHome::exists(self, key).await
1004 }
1005
1006 async fn set_access(
1007 &self,
1008 desired: CloudAccessState,
1009 ) -> Result<CloudAccessOutcome, CloudHomeError> {
1010 InMemoryCloudHome::set_access(self, desired).await
1011 }
1012}
1013
1014#[async_trait]
1015impl ExactSlotStorage for InMemoryCloudHome {
1016 async fn provider_binding(
1017 &self,
1018 ) -> Result<coven_protocol::objects::ResolvedProviderBinding, CloudHomeError> {
1019 Ok(self.provider_binding.clone())
1020 }
1021
1022 async fn list_slots(&self, prefix: &str) -> Result<Vec<ObjectSlot>, CloudHomeError> {
1023 self.exact_list_count.fetch_add(1, Ordering::SeqCst);
1024 self.exact_listed_prefixes
1025 .lock()
1026 .unwrap()
1027 .push(prefix.to_string());
1028 let mut keys: Vec<String> = self
1029 .writes
1030 .lock()
1031 .unwrap()
1032 .values
1033 .keys()
1034 .filter(|key| key.starts_with(prefix))
1035 .cloned()
1036 .collect();
1037 keys.sort();
1038 keys.iter()
1039 .map(|key| Self::exact_slot_from_storage_key(key))
1040 .collect()
1041 }
1042
1043 async fn allocate_slot(&self, logical_key: &str) -> Result<ObjectSlot, CloudHomeError> {
1044 let inflight = self
1045 .exact_slot_allocation_inflight
1046 .fetch_add(1, Ordering::SeqCst)
1047 + 1;
1048 self.exact_slot_allocation_max_inflight
1049 .fetch_max(inflight, Ordering::SeqCst);
1050 let _guard = InflightGuard {
1051 inflight: self.exact_slot_allocation_inflight.clone(),
1052 };
1053 let delay = self
1054 .exact_slot_allocation_delay_millis
1055 .load(Ordering::SeqCst);
1056 if delay > 0 {
1057 tokio::time::sleep(std::time::Duration::from_millis(delay)).await;
1058 }
1059 match &self.provider_binding.store {
1060 coven_protocol::objects::StoreProviderBinding::GoogleDrive { .. } => {
1061 let allocation = self.exact_slot_allocations.fetch_add(1, Ordering::SeqCst) + 1;
1062 ObjectSlot::opaque(logical_key.to_string(), format!("in-memory-{allocation}"))
1063 .map_err(CloudHomeError::from)
1064 }
1065 coven_protocol::objects::StoreProviderBinding::S3 { .. }
1066 | coven_protocol::objects::StoreProviderBinding::Dropbox { .. }
1067 | coven_protocol::objects::StoreProviderBinding::OneDrive { .. }
1068 | coven_protocol::objects::StoreProviderBinding::CloudKit { .. } => {
1069 ObjectSlot::logical(logical_key.to_string()).map_err(CloudHomeError::from)
1070 }
1071 }
1072 }
1073
1074 async fn create_at(
1075 &self,
1076 upload: &super::ExactUpload<'_>,
1077 control: &super::UploadControl,
1078 ) -> Result<super::ExactCreateOutcome, CloudHomeError> {
1079 InMemoryCloudHome::create_at_slot(self, upload, control).await
1080 }
1081
1082 async fn read_at(&self, slot: &ObjectSlot) -> Result<Vec<u8>, CloudHomeError> {
1083 InMemoryCloudHome::read_exact(self, slot).await
1084 }
1085
1086 async fn read_versioned_at(
1087 &self,
1088 slot: &ObjectSlot,
1089 ) -> Result<CloudVersionedObject, CloudHomeError> {
1090 let key = Self::exact_storage_key(slot)?;
1091 let writes = self.writes.lock().unwrap();
1092 let object = writes
1093 .values
1094 .get(&key)
1095 .ok_or_else(|| CloudHomeError::NotFound(slot.logical_key().to_string()))?;
1096 Ok(CloudVersionedObject {
1097 bytes: object.bytes.clone(),
1098 version: CloudObjectVersion::from_provider(object.version.to_string())?,
1099 })
1100 }
1101
1102 async fn replace_at_if_version(
1103 &self,
1104 slot: &ObjectSlot,
1105 expected: &CloudObjectVersion,
1106 bytes: Vec<u8>,
1107 ) -> Result<ConditionalWriteOutcome, CloudHomeError> {
1108 if self.fail_writes.load(Ordering::SeqCst) {
1109 return Err(CloudHomeError::Transport(
1110 "InMemoryCloudHome: armed write failure".into(),
1111 ));
1112 }
1113 let key = Self::exact_storage_key(slot)?;
1114 let mut writes = self.writes.lock().unwrap();
1115 let Some(current) = writes.values.get(&key) else {
1116 return Err(CloudHomeError::NotFound(slot.logical_key().to_string()));
1117 };
1118 if current.version.to_string() != expected.as_provider() {
1119 return Ok(ConditionalWriteOutcome::VersionChanged);
1120 }
1121 writes.insert(key, bytes);
1122 let version = writes
1123 .values
1124 .get(&Self::exact_storage_key(slot)?)
1125 .expect("conditional replacement inserted the record")
1126 .version;
1127 if self
1128 .lose_next_conditional_replace_response
1129 .swap(false, Ordering::SeqCst)
1130 {
1131 return Err(CloudHomeError::Transport(
1132 "InMemoryCloudHome: conditional replacement response lost".to_string(),
1133 ));
1134 }
1135 Ok(ConditionalWriteOutcome::Replaced(
1136 CloudObjectVersion::from_provider(version.to_string())?,
1137 ))
1138 }
1139
1140 async fn read_range_at(
1141 &self,
1142 slot: &ObjectSlot,
1143 start: u64,
1144 end: u64,
1145 ) -> Result<Vec<u8>, CloudHomeError> {
1146 let key = Self::exact_storage_key(slot)?;
1150 let bytes = self
1151 .writes
1152 .lock()
1153 .unwrap()
1154 .bytes(&key)
1155 .ok_or_else(|| CloudHomeError::NotFound(slot.logical_key().to_string()))?;
1156 let window = bytes
1160 .get(start as usize..end as usize)
1161 .ok_or_else(|| {
1162 CloudHomeError::NotFound(format!(
1163 "range {start}..{end} past the {} bytes of {}",
1164 bytes.len(),
1165 slot.logical_key()
1166 ))
1167 })?
1168 .to_vec();
1169 self.exact_range_reads.lock().unwrap().push((start, end));
1170 Ok(window)
1171 }
1172
1173 async fn read_at_to_file(
1174 &self,
1175 slot: &ObjectSlot,
1176 destination: &std::path::Path,
1177 progress: super::DownloadProgress,
1178 ) -> Result<(), CloudFileReadError> {
1179 InMemoryCloudHome::read_exact_to_file(self, slot, destination, progress).await
1180 }
1181
1182 async fn delete_at(&self, slot: &ObjectSlot) -> Result<(), CloudHomeError> {
1183 InMemoryCloudHome::delete_exact(self, slot).await
1184 }
1185}
1186
1187#[cfg(test)]
1188#[path = "test_utils_tests.rs"]
1189mod tests;
1190
1191#[cfg(any(test, feature = "test-utils"))]
1195pub fn test_cloud_home() -> std::sync::Arc<InMemoryCloudHome> {
1196 test_cloud_home_with_binding(coven_protocol::objects::ResolvedProviderBinding {
1197 store: coven_protocol::objects::StoreProviderBinding::GoogleDrive {
1198 corpus: coven_protocol::objects::GoogleDriveCorpus::SharedDrive {
1199 drive_id: "test-drive".to_string(),
1200 folder_id: "test-folder".to_string(),
1201 },
1202 },
1203 device: coven_protocol::objects::ProviderDeviceBinding {
1204 principal: coven_protocol::objects::ProviderPrincipalId::GoogleDrive {
1205 permission_id: "test-permission".to_string(),
1206 },
1207 },
1208 })
1209}
1210
1211#[cfg(any(test, feature = "test-utils"))]
1212pub fn test_cloud_home_with_binding(
1213 binding: coven_protocol::objects::ResolvedProviderBinding,
1214) -> std::sync::Arc<InMemoryCloudHome> {
1215 std::sync::Arc::new(InMemoryCloudHome::new().with_provider_binding(binding))
1216}