1use crate::async_storage::{AsyncAuthorityStore, AsyncObjectStore, GenerationForkSource};
8use crate::cancellation::CancellationToken;
9use crate::foundation::{
10 AuthorityId, DurableCommit, Epoch, Head, OperationId, ProposedCommit, Sequence,
11};
12use crate::performance::{WorkBudget, WorkCounters};
13use crate::storage::{
14 AppendOutcome, AuthorityFailure, AuthorityResult, AuthorityStoreError, CreateAuthorityOutcome,
15 ObjectFailure, ObjectId, ObjectRead, ObjectReadRequest, ObjectReadRetention, ObjectReceipt,
16 ObjectResult, ObjectStoreError, ReplayLimit, object_digest,
17};
18use bytes::Bytes;
19use std::collections::BTreeMap;
20use std::sync::{Arc, Mutex, MutexGuard};
21use thiserror::Error;
22
23type ReferenceAuthority = crate::distributed::StreamAuthorityStore<acyclic_stream::MemoryStream>;
24type ReferenceObjects = crate::distributed::ProviderObjectStore<acyclic_objects::MemoryObjects>;
25
26#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
28pub enum SimulationOperation {
29 AuthorityCreate,
31 AuthorityHead,
33 AuthorityAppend,
35 AuthorityReplay,
37 AuthorityFence,
39 AuthorityFindOperation,
41 ObjectPut,
43 ObjectRead,
45 ObjectReadMany,
47 ObjectContains,
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53pub enum SimulationFault {
54 RejectBefore,
56 PartitionBefore,
58 DelayObjectVisibility,
60 CorruptObjectRead,
62 PartialObjectRead,
64 StaleAuthorityHead(Head),
66 DuplicateAuthorityReplay,
68 AmbiguousAuthorityAppend,
70 FenceBeforeAuthorityAppend,
72}
73
74impl SimulationFault {
75 const fn compatible(self, operation: SimulationOperation) -> bool {
76 match self {
77 Self::RejectBefore | Self::PartitionBefore => true,
78 Self::DelayObjectVisibility => matches!(operation, SimulationOperation::ObjectPut),
79 Self::CorruptObjectRead | Self::PartialObjectRead => matches!(
80 operation,
81 SimulationOperation::ObjectRead | SimulationOperation::ObjectReadMany
82 ),
83 Self::StaleAuthorityHead(_) => {
84 matches!(operation, SimulationOperation::AuthorityHead)
85 }
86 Self::DuplicateAuthorityReplay => {
87 matches!(operation, SimulationOperation::AuthorityReplay)
88 }
89 Self::AmbiguousAuthorityAppend | Self::FenceBeforeAuthorityAppend => {
90 matches!(operation, SimulationOperation::AuthorityAppend)
91 }
92 }
93 }
94}
95
96#[derive(Clone, Copy, Debug, Eq, PartialEq)]
98pub struct ScheduledSimulationFault {
99 pub operation: SimulationOperation,
101 pub occurrence: u64,
103 pub fault: SimulationFault,
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub struct SimulationOptions {
110 pub maximum_scheduled_faults: usize,
112 pub maximum_trace_entries: usize,
114 pub maximum_pending_objects: usize,
116 pub maximum_pending_object_bytes: u64,
118}
119
120impl Default for SimulationOptions {
121 fn default() -> Self {
122 Self {
123 maximum_scheduled_faults: 4_096,
124 maximum_trace_entries: 65_536,
125 maximum_pending_objects: 4_096,
126 maximum_pending_object_bytes: 256 * 1024 * 1024,
127 }
128 }
129}
130
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
133pub struct SimulationTrace {
134 pub operation: SimulationOperation,
136 pub occurrence: u64,
138 pub fault: Option<SimulationFault>,
140}
141
142#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
144pub enum SimulationError {
145 #[error("simulation bounds must be positive")]
147 ZeroBound,
148 #[error("simulation fault occurrence must be positive")]
150 ZeroOccurrence,
151 #[error("simulation fault is incompatible with its operation family")]
153 IncompatibleFault,
154 #[error("simulation operation occurrence already has a fault")]
156 DuplicateFault,
157 #[error("simulation operation occurrence has already completed")]
159 PastOccurrence,
160 #[error("simulation fault schedule is full")]
162 ScheduleFull,
163 #[error("simulation state is poisoned")]
165 Poisoned,
166}
167
168struct State {
169 options: SimulationOptions,
170 occurrences: BTreeMap<SimulationOperation, u64>,
171 faults: BTreeMap<(SimulationOperation, u64), SimulationFault>,
172 trace: Vec<SimulationTrace>,
173 pending_objects: BTreeMap<ObjectId, Bytes>,
174 pending_object_bytes: u64,
175}
176
177impl State {
178 fn intercept(
179 &mut self,
180 operation: SimulationOperation,
181 ) -> Result<(u64, Option<SimulationFault>), &'static str> {
182 if self.trace.len() == self.options.maximum_trace_entries {
183 return Err("simulation trace bound exhausted");
184 }
185 let occurrence = self
186 .occurrences
187 .get(&operation)
188 .copied()
189 .unwrap_or(0)
190 .checked_add(1)
191 .ok_or("simulation operation occurrence overflow")?;
192 self.occurrences.insert(operation, occurrence);
193 let fault = self.faults.remove(&(operation, occurrence));
194 self.trace
195 .try_reserve(1)
196 .map_err(|_| "simulation trace allocation failed")?;
197 self.trace.push(SimulationTrace {
198 operation,
199 occurrence,
200 fault,
201 });
202 Ok((occurrence, fault))
203 }
204}
205
206#[derive(Clone)]
208pub struct Simulation<
209 A = crate::distributed::StreamAuthorityStore<acyclic_stream::MemoryStream>,
210 O = crate::distributed::ProviderObjectStore<acyclic_objects::MemoryObjects>,
211> {
212 state: Arc<Mutex<State>>,
213 authority: Arc<A>,
214 objects: Arc<O>,
215}
216
217impl Simulation<ReferenceAuthority, ReferenceObjects> {
218 pub fn new(options: SimulationOptions) -> Result<Self, SimulationError> {
224 let (authority, objects) = reference_backends();
225 Self::wrap(authority, objects, options)
226 }
227}
228
229impl<A, O> Simulation<A, O> {
230 pub fn wrap(
239 authority: A,
240 objects: O,
241 options: SimulationOptions,
242 ) -> Result<Self, SimulationError> {
243 if options.maximum_scheduled_faults == 0
244 || options.maximum_trace_entries == 0
245 || options.maximum_pending_objects == 0
246 || options.maximum_pending_object_bytes == 0
247 {
248 return Err(SimulationError::ZeroBound);
249 }
250 Ok(Self {
251 state: Arc::new(Mutex::new(State {
252 options,
253 occurrences: BTreeMap::new(),
254 faults: BTreeMap::new(),
255 trace: Vec::new(),
256 pending_objects: BTreeMap::new(),
257 pending_object_bytes: 0,
258 })),
259 authority: Arc::new(authority),
260 objects: Arc::new(objects),
261 })
262 }
263
264 #[must_use]
266 pub fn stores(&self) -> (SimulatedAuthorityStore<A>, SimulatedObjectStore<O>) {
267 (
268 SimulatedAuthorityStore {
269 state: self.state.clone(),
270 inner: self.authority.clone(),
271 },
272 SimulatedObjectStore {
273 state: self.state.clone(),
274 inner: self.objects.clone(),
275 },
276 )
277 }
278
279 pub fn schedule(&self, scheduled: ScheduledSimulationFault) -> Result<(), SimulationError> {
286 if scheduled.occurrence == 0 {
287 return Err(SimulationError::ZeroOccurrence);
288 }
289 if !scheduled.fault.compatible(scheduled.operation) {
290 return Err(SimulationError::IncompatibleFault);
291 }
292 let mut state = self.lock_state()?;
293 schedule_locked(&mut state, scheduled)
294 }
295
296 pub fn schedule_next(
307 &self,
308 operation: SimulationOperation,
309 fault: SimulationFault,
310 ) -> Result<ScheduledSimulationFault, SimulationError> {
311 if !fault.compatible(operation) {
312 return Err(SimulationError::IncompatibleFault);
313 }
314 let mut state = self.lock_state()?;
315 let occurrence = state
316 .occurrences
317 .get(&operation)
318 .copied()
319 .unwrap_or(0)
320 .checked_add(1)
321 .ok_or(SimulationError::PastOccurrence)?;
322 let scheduled = ScheduledSimulationFault {
323 operation,
324 occurrence,
325 fault,
326 };
327 schedule_locked(&mut state, scheduled)?;
328 Ok(scheduled)
329 }
330
331 pub fn trace(&self) -> Result<Vec<SimulationTrace>, SimulationError> {
337 Ok(self.lock_state()?.trace.clone())
338 }
339
340 fn lock_state(&self) -> Result<MutexGuard<'_, State>, SimulationError> {
341 self.state.lock().map_err(|_| SimulationError::Poisoned)
342 }
343}
344
345fn schedule_locked(
346 state: &mut State,
347 scheduled: ScheduledSimulationFault,
348) -> Result<(), SimulationError> {
349 if state
350 .faults
351 .contains_key(&(scheduled.operation, scheduled.occurrence))
352 {
353 return Err(SimulationError::DuplicateFault);
354 }
355 if state
356 .occurrences
357 .get(&scheduled.operation)
358 .is_some_and(|completed| *completed >= scheduled.occurrence)
359 {
360 return Err(SimulationError::PastOccurrence);
361 }
362 if state.faults.len() == state.options.maximum_scheduled_faults {
363 return Err(SimulationError::ScheduleFull);
364 }
365 state
366 .faults
367 .insert((scheduled.operation, scheduled.occurrence), scheduled.fault);
368 Ok(())
369}
370
371impl<A, O: AsyncObjectStore> Simulation<A, O> {
372 pub async fn flush_object_visibility(
384 &self,
385 budget: WorkBudget,
386 cancellation: &CancellationToken,
387 ) -> ObjectResult<u64> {
388 let mut work = WorkCounters::default();
389 let mut published = 0_u64;
390 loop {
391 cancellation
392 .check()
393 .map_err(|_| ObjectFailure::new(ObjectStoreError::Cancelled, work))?;
394 let next = {
395 let state = self
396 .state
397 .lock()
398 .map_err(|_| ObjectFailure::new(ObjectStoreError::Corrupt, work))?;
399 state
400 .pending_objects
401 .first_key_value()
402 .map(|(object_id, bytes)| (*object_id, bytes.clone()))
403 };
404 let Some((object_id, bytes)) = next else {
405 return Ok(ObjectReceipt {
406 value: published,
407 work,
408 });
409 };
410 let remaining = work
411 .remaining(budget)
412 .map_err(|error| ObjectFailure::new(error.into(), work))?;
413 let receipt = self
414 .objects
415 .put(object_id, bytes, remaining, cancellation)
416 .await
417 .map_err(|failure| {
418 let nested = *failure.work;
419 match work.checked_add(nested) {
420 Ok(combined) => ObjectFailure::new(failure.error, combined),
421 Err(error) => ObjectFailure::new(error.into(), work),
422 }
423 })?;
424 work = work
425 .checked_add(receipt.work)
426 .map_err(|error| ObjectFailure::new(error.into(), work))?;
427 let mut state = self
428 .state
429 .lock()
430 .map_err(|_| ObjectFailure::new(ObjectStoreError::Corrupt, work))?;
431 if let Some(removed) = state.pending_objects.remove(&object_id) {
432 state.pending_object_bytes = state
433 .pending_object_bytes
434 .checked_sub(u64::try_from(removed.len()).unwrap_or(u64::MAX))
435 .ok_or_else(|| ObjectFailure::new(ObjectStoreError::Corrupt, work))?;
436 }
437 published = published.checked_add(1).ok_or_else(|| {
438 ObjectFailure::new(ObjectStoreError::Work(crate::WorkError::Overflow), work)
439 })?;
440 }
441 }
442}
443
444impl Default for Simulation {
445 fn default() -> Self {
446 let (authority, objects) = reference_backends();
447 Self {
448 state: Arc::new(Mutex::new(State {
449 options: SimulationOptions::default(),
450 occurrences: BTreeMap::new(),
451 faults: BTreeMap::new(),
452 trace: Vec::new(),
453 pending_objects: BTreeMap::new(),
454 pending_object_bytes: 0,
455 })),
456 authority: Arc::new(authority),
457 objects: Arc::new(objects),
458 }
459 }
460}
461
462#[derive(Clone)]
464pub struct SimulatedAuthorityStore<
465 A = crate::distributed::StreamAuthorityStore<acyclic_stream::MemoryStream>,
466> {
467 state: Arc<Mutex<State>>,
468 inner: Arc<A>,
469}
470
471#[derive(Clone)]
473pub struct SimulatedObjectStore<
474 O = crate::distributed::ProviderObjectStore<acyclic_objects::MemoryObjects>,
475> {
476 state: Arc<Mutex<State>>,
477 inner: Arc<O>,
478}
479
480fn reference_backends() -> (ReferenceAuthority, ReferenceObjects) {
481 let streams = Arc::new(acyclic_stream::MemoryStream::default());
482 let (objects, bucket) = acyclic_objects::MemoryObjects::with_default_bucket();
483 (
484 crate::distributed::StreamAuthorityStore::new(streams),
485 crate::distributed::ProviderObjectStore::new(Arc::new(objects), bucket),
486 )
487}
488
489fn authority_fault(error: impl Into<String>) -> AuthorityFailure {
490 AuthorityFailure::before_work(AuthorityStoreError::Rejected(error.into()))
491}
492
493fn object_fault(error: impl Into<String>) -> ObjectFailure {
494 ObjectFailure::before_work(ObjectStoreError::Rejected(error.into()))
495}
496
497fn intercept_authority(
498 state: &Mutex<State>,
499 operation: SimulationOperation,
500) -> Result<Option<SimulationFault>, AuthorityFailure> {
501 let mut state = state.lock().map_err(|_| {
502 AuthorityFailure::before_work(AuthorityStoreError::Corrupt(
503 "simulation state is poisoned".to_owned(),
504 ))
505 })?;
506 state
507 .intercept(operation)
508 .map(|(_, fault)| fault)
509 .map_err(authority_fault)
510}
511
512fn intercept_object(
513 state: &Mutex<State>,
514 operation: SimulationOperation,
515) -> Result<Option<SimulationFault>, ObjectFailure> {
516 let mut state = state
517 .lock()
518 .map_err(|_| ObjectFailure::before_work(ObjectStoreError::Corrupt))?;
519 state
520 .intercept(operation)
521 .map(|(_, fault)| fault)
522 .map_err(object_fault)
523}
524
525fn reject_authority(fault: Option<SimulationFault>) -> Result<(), AuthorityFailure> {
526 match fault {
527 Some(SimulationFault::RejectBefore) => Err(authority_fault("scheduled rejection")),
528 Some(SimulationFault::PartitionBefore) => Err(authority_fault("scheduled partition")),
529 _ => Ok(()),
530 }
531}
532
533fn reject_object(fault: Option<SimulationFault>) -> Result<(), ObjectFailure> {
534 match fault {
535 Some(SimulationFault::RejectBefore) => Err(object_fault("scheduled rejection")),
536 Some(SimulationFault::PartitionBefore) => Err(object_fault("scheduled partition")),
537 _ => Ok(()),
538 }
539}
540
541impl<A: AsyncAuthorityStore> AsyncAuthorityStore for SimulatedAuthorityStore<A> {
542 async fn fork_generation_authority(
543 &self,
544 source: GenerationForkSource,
545 destination_authority: AuthorityId,
546 operation_id: OperationId,
547 budget: WorkBudget,
548 cancellation: &CancellationToken,
549 ) -> AuthorityResult<CreateAuthorityOutcome> {
550 cancellation
551 .check()
552 .map_err(|_| AuthorityFailure::before_work(AuthorityStoreError::Cancelled))?;
553 let fault = intercept_authority(&self.state, SimulationOperation::AuthorityCreate)?;
554 reject_authority(fault)?;
555 self.inner
556 .fork_generation_authority(
557 source,
558 destination_authority,
559 operation_id,
560 budget,
561 cancellation,
562 )
563 .await
564 }
565
566 async fn create_authority(
567 &self,
568 authority_id: AuthorityId,
569 genesis_epoch: Epoch,
570 budget: WorkBudget,
571 cancellation: &CancellationToken,
572 ) -> AuthorityResult<CreateAuthorityOutcome> {
573 cancellation
574 .check()
575 .map_err(|_| AuthorityFailure::before_work(AuthorityStoreError::Cancelled))?;
576 let fault = intercept_authority(&self.state, SimulationOperation::AuthorityCreate)?;
577 reject_authority(fault)?;
578 self.inner
579 .create_authority(authority_id, genesis_epoch, budget, cancellation)
580 .await
581 }
582
583 async fn head(
584 &self,
585 authority_id: AuthorityId,
586 budget: WorkBudget,
587 cancellation: &CancellationToken,
588 ) -> AuthorityResult<Head> {
589 cancellation
590 .check()
591 .map_err(|_| AuthorityFailure::before_work(AuthorityStoreError::Cancelled))?;
592 let fault = intercept_authority(&self.state, SimulationOperation::AuthorityHead)?;
593 reject_authority(fault)?;
594 if let Some(SimulationFault::StaleAuthorityHead(head)) = fault {
595 let work = WorkCounters {
596 authority_records_read: 1,
597 ..WorkCounters::default()
598 };
599 work.verify(budget)
600 .map_err(|error| AuthorityFailure::before_work(error.into()))?;
601 return Ok(crate::AuthorityReceipt { value: head, work });
602 }
603 self.inner.head(authority_id, budget, cancellation).await
604 }
605
606 async fn compare_and_append(
607 &self,
608 authority_id: AuthorityId,
609 epoch: Epoch,
610 expected: Head,
611 commit: ProposedCommit,
612 budget: WorkBudget,
613 cancellation: &CancellationToken,
614 ) -> AuthorityResult<AppendOutcome> {
615 cancellation
616 .check()
617 .map_err(|_| AuthorityFailure::before_work(AuthorityStoreError::Cancelled))?;
618 let fault = intercept_authority(&self.state, SimulationOperation::AuthorityAppend)?;
619 reject_authority(fault)?;
620 if matches!(fault, Some(SimulationFault::FenceBeforeAuthorityAppend)) {
621 let fenced = self
622 .inner
623 .fence(authority_id, expected, budget, cancellation)
624 .await?;
625 let remaining = fenced
626 .work
627 .remaining(budget)
628 .map_err(|error| AuthorityFailure::new(error.into(), fenced.work))?;
629 let appended = self
630 .inner
631 .compare_and_append(
632 authority_id,
633 epoch,
634 expected,
635 commit,
636 remaining,
637 cancellation,
638 )
639 .await
640 .map_err(|failure| {
641 let nested = *failure.work;
642 match fenced.work.checked_add(nested) {
643 Ok(combined) => AuthorityFailure::new(failure.error, combined),
644 Err(error) => AuthorityFailure::new(error.into(), fenced.work),
645 }
646 })?;
647 let work = fenced
648 .work
649 .checked_add(appended.work)
650 .map_err(|error| AuthorityFailure::new(error.into(), fenced.work))?;
651 return Ok(crate::AuthorityReceipt {
652 value: appended.value,
653 work,
654 });
655 }
656 let receipt = self
657 .inner
658 .compare_and_append(authority_id, epoch, expected, commit, budget, cancellation)
659 .await?;
660 if matches!(fault, Some(SimulationFault::AmbiguousAuthorityAppend)) {
661 return Err(AuthorityFailure::new(
662 AuthorityStoreError::Indeterminate {
663 operation: "compare-and-append",
664 source: std::io::Error::other("scheduled acknowledgement loss"),
665 },
666 receipt.work,
667 ));
668 }
669 Ok(receipt)
670 }
671
672 async fn replay(
673 &self,
674 authority_id: AuthorityId,
675 after: Sequence,
676 limit: ReplayLimit,
677 budget: WorkBudget,
678 cancellation: &CancellationToken,
679 ) -> AuthorityResult<Vec<DurableCommit>> {
680 cancellation
681 .check()
682 .map_err(|_| AuthorityFailure::before_work(AuthorityStoreError::Cancelled))?;
683 let fault = intercept_authority(&self.state, SimulationOperation::AuthorityReplay)?;
684 reject_authority(fault)?;
685 let mut receipt = self
686 .inner
687 .replay(authority_id, after, limit, budget, cancellation)
688 .await?;
689 if matches!(fault, Some(SimulationFault::DuplicateAuthorityReplay))
690 && let Some(last) = receipt.value.last().cloned()
691 {
692 let copied = u64::try_from(last.payload.len()).unwrap_or(u64::MAX);
693 let extra = WorkCounters {
694 allocation_operations: 1,
695 bytes_copied: copied,
696 peak_allocation_bytes: copied,
697 items_returned: 1,
698 ..WorkCounters::default()
699 };
700 let work = receipt
701 .work
702 .checked_add(extra)
703 .map_err(|error| AuthorityFailure::new(error.into(), receipt.work))?;
704 work.verify(budget)
705 .map_err(|error| AuthorityFailure::new(error.into(), receipt.work))?;
706 receipt.value.push(last);
707 receipt.work = work;
708 }
709 Ok(receipt)
710 }
711
712 async fn fence(
713 &self,
714 authority_id: AuthorityId,
715 expected: Head,
716 budget: WorkBudget,
717 cancellation: &CancellationToken,
718 ) -> AuthorityResult<crate::storage::FenceOutcome> {
719 cancellation
720 .check()
721 .map_err(|_| AuthorityFailure::before_work(AuthorityStoreError::Cancelled))?;
722 let fault = intercept_authority(&self.state, SimulationOperation::AuthorityFence)?;
723 reject_authority(fault)?;
724 self.inner
725 .fence(authority_id, expected, budget, cancellation)
726 .await
727 }
728
729 async fn find_operation(
730 &self,
731 authority_id: AuthorityId,
732 operation_id: OperationId,
733 budget: WorkBudget,
734 cancellation: &CancellationToken,
735 ) -> AuthorityResult<Option<DurableCommit>> {
736 cancellation
737 .check()
738 .map_err(|_| AuthorityFailure::before_work(AuthorityStoreError::Cancelled))?;
739 let fault = intercept_authority(&self.state, SimulationOperation::AuthorityFindOperation)?;
740 reject_authority(fault)?;
741 self.inner
742 .find_operation(authority_id, operation_id, budget, cancellation)
743 .await
744 }
745}
746
747fn altered_read(
748 mut receipt: ObjectReceipt<ObjectRead>,
749 fault: Option<SimulationFault>,
750 budget: WorkBudget,
751) -> ObjectResult<ObjectRead> {
752 if !matches!(
753 fault,
754 Some(SimulationFault::CorruptObjectRead | SimulationFault::PartialObjectRead)
755 ) {
756 return Ok(receipt);
757 }
758 if receipt.value.bytes.is_empty() {
759 return Err(ObjectFailure::new(ObjectStoreError::Corrupt, receipt.work));
760 }
761 let original = u64::try_from(receipt.value.bytes.len()).unwrap_or(u64::MAX);
762 let retained = match fault {
763 Some(SimulationFault::PartialObjectRead) => original.saturating_sub(1),
764 _ => original,
765 };
766 let extra = WorkCounters {
767 allocation_operations: 1,
768 bytes_copied: retained,
769 peak_allocation_bytes: retained,
770 ..WorkCounters::default()
771 };
772 let work = receipt
773 .work
774 .checked_add(extra)
775 .map_err(|error| ObjectFailure::new(error.into(), receipt.work))?;
776 work.verify(budget)
777 .map_err(|error| ObjectFailure::new(error.into(), receipt.work))?;
778 let mut bytes = receipt.value.bytes.to_vec();
779 if matches!(fault, Some(SimulationFault::PartialObjectRead)) {
780 bytes.pop();
781 } else if let Some(first) = bytes.first_mut() {
782 *first ^= 0x80;
783 }
784 receipt.value = ObjectRead {
785 bytes: Bytes::from(bytes),
786 retention: ObjectReadRetention::Owned {
787 logical_bytes: retained,
788 },
789 };
790 receipt.work = work;
791 Ok(receipt)
792}
793
794impl<O: AsyncObjectStore> AsyncObjectStore for SimulatedObjectStore<O> {
795 async fn put(
796 &self,
797 object_id: ObjectId,
798 bytes: Bytes,
799 budget: WorkBudget,
800 cancellation: &CancellationToken,
801 ) -> ObjectResult<()> {
802 cancellation
803 .check()
804 .map_err(|_| ObjectFailure::before_work(ObjectStoreError::Cancelled))?;
805 let fault = intercept_object(&self.state, SimulationOperation::ObjectPut)?;
806 reject_object(fault)?;
807 if matches!(fault, Some(SimulationFault::DelayObjectVisibility)) {
808 let length = u64::try_from(bytes.len()).unwrap_or(u64::MAX);
809 let work = WorkCounters {
810 object_probes: 1,
811 backend_write_operations: 1,
812 object_bytes_written: length,
813 bytes_hashed: length.saturating_add(crate::OBJECT_DIGEST_ENVELOPE_BYTES),
814 ..WorkCounters::default()
815 };
816 work.verify(budget)
817 .map_err(|error| ObjectFailure::before_work(error.into()))?;
818 if object_digest(object_id.kind, &bytes) != object_id.digest {
819 return Err(ObjectFailure::new(ObjectStoreError::DigestMismatch, work));
820 }
821 let mut state = self
822 .state
823 .lock()
824 .map_err(|_| ObjectFailure::new(ObjectStoreError::Corrupt, work))?;
825 if let Some(existing) = state.pending_objects.get(&object_id) {
826 if existing != &bytes {
827 return Err(ObjectFailure::new(ObjectStoreError::Corrupt, work));
828 }
829 return Ok(ObjectReceipt { value: (), work });
830 }
831 let next_count = state.pending_objects.len().saturating_add(1);
832 let next_bytes = state
833 .pending_object_bytes
834 .checked_add(length)
835 .ok_or_else(|| {
836 ObjectFailure::new(ObjectStoreError::Work(crate::WorkError::Overflow), work)
837 })?;
838 if next_count > state.options.maximum_pending_objects
839 || next_bytes > state.options.maximum_pending_object_bytes
840 {
841 return Err(ObjectFailure::new(
842 ObjectStoreError::Rejected(
843 "simulation delayed-object bound exhausted".to_owned(),
844 ),
845 work,
846 ));
847 }
848 state.pending_objects.insert(object_id, bytes);
849 state.pending_object_bytes = next_bytes;
850 return Ok(ObjectReceipt { value: (), work });
851 }
852 self.inner.put(object_id, bytes, budget, cancellation).await
853 }
854
855 async fn put_many(
856 &self,
857 writes: &[crate::storage::ObjectWrite],
858 budget: WorkBudget,
859 cancellation: &CancellationToken,
860 ) -> ObjectResult<()> {
861 cancellation
862 .check()
863 .map_err(|_| ObjectFailure::before_work(ObjectStoreError::Cancelled))?;
864 self.inner.put_many(writes, budget, cancellation).await
865 }
866
867 async fn read(
868 &self,
869 object_id: ObjectId,
870 maximum_bytes: u64,
871 budget: WorkBudget,
872 cancellation: &CancellationToken,
873 ) -> ObjectResult<ObjectRead> {
874 cancellation
875 .check()
876 .map_err(|_| ObjectFailure::before_work(ObjectStoreError::Cancelled))?;
877 let fault = intercept_object(&self.state, SimulationOperation::ObjectRead)?;
878 reject_object(fault)?;
879 altered_read(
880 self.inner
881 .read(object_id, maximum_bytes, budget, cancellation)
882 .await?,
883 fault,
884 budget,
885 )
886 }
887
888 async fn read_many(
889 &self,
890 requests: &[ObjectReadRequest],
891 budget: WorkBudget,
892 cancellation: &CancellationToken,
893 ) -> ObjectResult<Vec<ObjectRead>> {
894 cancellation
895 .check()
896 .map_err(|_| ObjectFailure::before_work(ObjectStoreError::Cancelled))?;
897 let fault = intercept_object(&self.state, SimulationOperation::ObjectReadMany)?;
898 reject_object(fault)?;
899 let mut receipt = self.inner.read_many(requests, budget, cancellation).await?;
900 if matches!(
901 fault,
902 Some(SimulationFault::CorruptObjectRead | SimulationFault::PartialObjectRead)
903 ) && let Some(first) = receipt.value.first_mut()
904 {
905 let altered = altered_read(
906 ObjectReceipt {
907 value: first.clone(),
908 work: WorkCounters::default(),
909 },
910 fault,
911 receipt
912 .work
913 .remaining(budget)
914 .map_err(|error| ObjectFailure::new(error.into(), receipt.work))?,
915 )?;
916 receipt.work = receipt
917 .work
918 .checked_add(altered.work)
919 .map_err(|error| ObjectFailure::new(error.into(), receipt.work))?;
920 *first = altered.value;
921 }
922 Ok(receipt)
923 }
924
925 async fn contains(
926 &self,
927 object_id: ObjectId,
928 budget: WorkBudget,
929 cancellation: &CancellationToken,
930 ) -> ObjectResult<bool> {
931 cancellation
932 .check()
933 .map_err(|_| ObjectFailure::before_work(ObjectStoreError::Cancelled))?;
934 let fault = intercept_object(&self.state, SimulationOperation::ObjectContains)?;
935 reject_object(fault)?;
936 self.inner.contains(object_id, budget, cancellation).await
937 }
938}
939
940#[cfg(test)]
941#[path = "tests/simulation.rs"]
942mod tests;