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