1use std::cmp::Reverse;
18use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
19use std::fmt;
20use std::ops::Range;
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::{Arc, Mutex, PoisonError, RwLock};
23use std::time::{Duration, Instant};
24
25use async_stream::try_stream;
26use bytes::Bytes;
27use common_telemetry::{debug, error, info, warn};
28use common_wal::config::object_store::{AckMode, CorruptedSegmentAction, ObjectStoreWalConfig};
29use futures::future::{BoxFuture, OptionFuture};
30use futures::stream::{BoxStream, FuturesUnordered};
31use futures::{Stream, StreamExt, TryStreamExt};
32use object_store::{ErrorKind, ObjectStore};
33use snafu::{IntoError, OptionExt, ResultExt, ensure};
34use store_api::logstore::entry::{Entry, NaiveEntry};
35use store_api::logstore::provider::{ObjectStoreProvider, Provider};
36use store_api::logstore::{AppendBatchResponse, EntryId, LogStore, SendableEntryStream};
37use store_api::storage::RegionId;
38use tokio::sync::{OwnedSemaphorePermit, Semaphore, mpsc, oneshot, watch};
39use tokio::time::MissedTickBehavior;
40use tokio_util::task::AbortOnDropHandle;
41
42use crate::error::{
43 CorruptedWalObjectSnafu, Error, IncompleteWalEntrySnafu, InvalidProviderSnafu,
44 InvalidWalObjectSnafu, InvalidWalObjectStoreSnafu, MismatchedWalPrefixSnafu,
45 MismatchedWalRegionSnafu, MissingWalHistorySnafu, ObjectStoreWalFailingSnafu,
46 ObjectStoreWalSnafu, ObjectStoreWalStoppedSnafu, Result, StaleWalObjectSnafu,
47 UnconfirmedWalEpochStartSnafu, WalObjectSequenceExhaustedSnafu,
48 WalObjectSequenceUnsettledSnafu, WalSegmentFetchSnafu, WalStartWithdrawnSnafu,
49};
50use crate::metrics::{
51 METRIC_OBJECT_STORE_WAL_APPEND_ACK_SECONDS, METRIC_OBJECT_STORE_WAL_CREATE_CONFLICTS_TOTAL,
52 METRIC_OBJECT_STORE_WAL_CREATE_FAILURES_TOTAL, METRIC_OBJECT_STORE_WAL_CREATED_OBJECTS_TOTAL,
53 METRIC_OBJECT_STORE_WAL_DELETED_OBJECTS_TOTAL, METRIC_OBJECT_STORE_WAL_FAILED_DELETES_TOTAL,
54 METRIC_OBJECT_STORE_WAL_FAILING, METRIC_OBJECT_STORE_WAL_INDEXED_BYTES,
55 METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS,
56 METRIC_OBJECT_STORE_WAL_MISSING_HISTORY_ACCEPTED_TOTAL, METRIC_OBJECT_STORE_WAL_OBJECT_BYTES,
57 METRIC_OBJECT_STORE_WAL_OBJECT_ENTRIES, METRIC_OBJECT_STORE_WAL_POISONED_TOTAL,
58 METRIC_OBJECT_STORE_WAL_READ_SECONDS, METRIC_OBJECT_STORE_WAL_RECOVERED_OBJECTS_TOTAL,
59 METRIC_OBJECT_STORE_WAL_RECOVERY_SECONDS, METRIC_OBJECT_STORE_WAL_REJECTED_APPENDS_TOTAL,
60 METRIC_OBJECT_STORE_WAL_RELEASE_RECORDS_TOTAL, METRIC_OBJECT_STORE_WAL_SEAL_TO_DURABLE_SECONDS,
61 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL, METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS,
62 METRIC_OBJECT_STORE_WAL_STALLED_APPENDS_TOTAL,
63};
64use crate::object_store_wal::batch::{OBJECT_SEQ_LIMIT, OpenBatch, entry_id, sequence_floor};
65use crate::object_store_wal::catalog::ObjectCatalog;
66use crate::object_store_wal::format::{
67 ChainLink, EncodedObject, FixedTrailer, FooterEntry, HEADER_LEN, Header, MIN_OBJECT_LEN,
68 RELEASE_REGION_ID, Record, Release, TRAILER_LEN, decode_footer, decode_header, decode_releases,
69 decode_segment, decode_trailer, encode_object, encode_releases, footer_range, object_len,
70 verify_segment_ranges,
71};
72use crate::object_store_wal::io::{ListedObject, ObjectStoreIo, PutResult};
73
74const COMMAND_BUFFER: usize = 1024;
75const APPEND_BUFFER: usize = 16;
77const MIN_FLUSH_INTERVAL: Duration = Duration::from_millis(10);
78const MAX_IN_FLIGHT_CREATES: usize = 4;
79const MAX_IN_FLIGHT_DELETES: usize = 4;
80const CREATE_RETRY_DELAY: Duration = Duration::from_millis(100);
83const MAX_FAILING_RETRY_DELAY: Duration = Duration::from_secs(30);
91const FAILING_LOG_INTERVAL: Duration = Duration::from_secs(60);
93const MAX_SEALED_BATCHES: usize = 2 * MAX_IN_FLIGHT_CREATES;
99const RECOVERY_CONCURRENCY: usize = 64;
108const READ_PREFETCH_DEPTH: usize = 8;
114const READ_PREFETCH_BYTES: usize = 64 * 1024 * 1024;
124const RECOVERY_TAIL_WINDOW: usize = 64 * 1024;
129const MAX_KEPT_RELEASE_BYTES: u64 = 4 * 1024;
136
137pub struct ObjectStoreLogStore {
156 prefix: String,
157 storage: String,
159 ack_mode: AckMode,
160 on_corrupted_segment: CorruptedSegmentAction,
161 accept_missing_history: bool,
162 found_empty: bool,
164 history_end: u64,
167 started_regions: StartedRegions,
168 io: Arc<dyn WalObjectIo>,
169 catalog: Arc<RwLock<ObjectCatalog>>,
170 obsolete_entry_ids: ObsoleteEntryIds,
171 wal_holes: WalHoles,
173 deleting: Arc<DeletingObjects>,
175 read_prefetch_budget: Arc<Semaphore>,
177 terminal_error: TerminalError,
178 stopped: Arc<AtomicBool>,
179 command_tx: mpsc::Sender<Command>,
180 append_tx: mpsc::Sender<QueuedAppend>,
181 #[cfg(any(test, feature = "testing"))]
182 admitted_appends: watch::Receiver<usize>,
183 #[cfg(any(test, feature = "testing"))]
184 creates_held: watch::Sender<bool>,
185 #[cfg(any(test, feature = "testing"))]
186 creates_fail: Arc<AtomicBool>,
187 #[cfg(any(test, feature = "testing"))]
188 next_create_fails_after_write: Arc<AtomicBool>,
189 #[cfg(any(test, feature = "testing"))]
190 parked_creates: watch::Receiver<usize>,
191 #[cfg(any(test, feature = "testing"))]
192 durability_waits: watch::Receiver<usize>,
193}
194
195type ObsoleteEntryIds = Arc<Mutex<HashMap<RegionId, EntryId>>>;
196
197type StartedRegions = Arc<Mutex<HashSet<RegionId>>>;
202
203type WalHoles = Arc<Mutex<HashMap<RegionId, BTreeMap<u64, WalHole>>>>;
204
205#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct WalHole {
209 pub path: String,
211 pub object_seq: u64,
212 pub min_entry_id: EntryId,
214 pub max_entry_id: EntryId,
215}
216
217type TerminalError = Arc<Mutex<Option<Arc<Error>>>>;
218
219#[derive(Debug, Default)]
226struct DeletingObjects {
227 attempts: Mutex<BTreeMap<u64, watch::Sender<bool>>>,
229}
230
231impl DeletingObjects {
232 fn lock(&self) -> std::sync::MutexGuard<'_, BTreeMap<u64, watch::Sender<bool>>> {
233 self.attempts.lock().unwrap_or_else(PoisonError::into_inner)
234 }
235
236 fn start(&self, object_seq: u64) {
237 self.lock().insert(object_seq, watch::channel(false).0);
238 }
239
240 fn contains(&self, object_seq: u64) -> bool {
241 self.lock().contains_key(&object_seq)
242 }
243
244 fn finish(&self, object_seq: u64) {
247 if let Some(settled) = self.lock().remove(&object_seq) {
248 settled.send_replace(true);
249 }
250 }
251
252 fn abandon_all(&self) {
255 for (_, settled) in std::mem::take(&mut *self.lock()) {
256 settled.send_replace(true);
257 }
258 }
259
260 fn attempt(&self, object_seq: u64) -> Option<watch::Receiver<bool>> {
263 self.lock().get(&object_seq).map(watch::Sender::subscribe)
264 }
265
266 #[cfg(any(test, feature = "testing"))]
268 async fn wait_until_empty(&self) {
269 loop {
270 let in_flight = self
271 .lock()
272 .values()
273 .map(watch::Sender::subscribe)
274 .collect::<Vec<_>>();
275 if in_flight.is_empty() {
276 return;
277 }
278 for mut settled in in_flight {
279 let _ = settled.wait_for(|settled| *settled).await;
280 }
281 }
282 }
283}
284
285impl fmt::Debug for ObjectStoreLogStore {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 f.debug_struct("ObjectStoreLogStore")
288 .field("prefix", &self.prefix)
289 .field("ack_mode", &self.ack_mode)
290 .field("on_corrupted_segment", &self.on_corrupted_segment)
291 .finish_non_exhaustive()
292 }
293}
294
295impl ObjectStoreLogStore {
296 pub async fn try_new(
303 object_store: ObjectStore,
304 config: &ObjectStoreWalConfig,
305 node_id: u64,
306 generation: u64,
307 ) -> Result<Arc<Self>> {
308 let prefix = config.node_prefix(node_id, generation);
309 let io = ObjectStoreIo::new(object_store, &prefix)?;
310 io.probe_conditional_create().await?;
311 Self::open(Arc::new(io), config, prefix).await
312 }
313
314 async fn open(
315 io: Arc<dyn WalObjectIo>,
316 config: &ObjectStoreWalConfig,
317 prefix: String,
318 ) -> Result<Arc<Self>> {
319 ensure!(
320 config.flush_interval >= MIN_FLUSH_INTERVAL,
321 InvalidWalObjectStoreSnafu {
322 reason: format!(
323 "flush interval {:?} is shorter than {MIN_FLUSH_INTERVAL:?}",
324 config.flush_interval
325 ),
326 }
327 );
328
329 let max_batch_bytes = positive_bytes(config.max_batch_bytes.as_bytes(), "max batch bytes")?;
330 let max_unpersisted_bytes = positive_bytes(
331 config.max_unpersisted_bytes.as_bytes(),
332 "max unpersisted bytes",
333 )?;
334 ensure!(
335 config.max_unpersisted_age > Duration::ZERO,
336 InvalidWalObjectStoreSnafu {
337 reason: "max unpersisted age is zero",
338 }
339 );
340 let recovery_started_at = Instant::now();
341 let Recovered {
342 mut catalog,
343 next_object_seq,
344 durable_entry_ids,
345 tip,
346 max_epoch,
347 reclaim_boundary,
348 unindexed,
349 released_entry_ids,
350 recovered_objects,
351 } = recover(io.as_ref()).await?;
352 ensure!(
356 max_epoch <= next_object_seq,
357 CorruptedWalObjectSnafu {
358 reason: format!(
359 "an object carries epoch {max_epoch}, above the next sequence {next_object_seq}"
360 ),
361 }
362 );
363 let found_empty = next_object_seq == 0;
364 let history_end = durable_entry_ids
365 .values()
366 .map(|entry_id| sequence_floor(*entry_id))
367 .max()
368 .unwrap_or(0);
369 METRIC_OBJECT_STORE_WAL_RECOVERED_OBJECTS_TOTAL.inc_by(recovered_objects);
372 METRIC_OBJECT_STORE_WAL_RECOVERY_SECONDS
373 .observe(recovery_started_at.elapsed().as_secs_f64());
374 publish_indexed(&catalog);
375 let start_boundary = if durable_entry_ids.is_empty() {
380 next_object_seq
381 } else {
382 reclaim_boundary
383 };
384 let (start, moved_past) =
385 start_epoch(io.as_ref(), next_object_seq, tip, start_boundary).await?;
386 let epoch = start.epoch;
387 info!(
389 "Opened object store WAL under {prefix} at epoch {epoch}, start object {}",
390 start.object_seq
391 );
392 catalog
393 .insert_object(start.object_seq, Vec::new())
394 .with_context(|_| InvalidWalObjectSnafu {
395 path: io.object_path(start.object_seq),
396 })?;
397 METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.inc();
398 METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.add(object_len(&[]) as i64);
399 for (®ion_id, &entry_id) in &released_entry_ids {
401 catalog.obsolete(region_id, entry_id);
402 }
403 let writer_reclaim_boundary = catalog.reclaim_boundary(reclaim_boundary);
404 let catalog = Arc::new(RwLock::new(catalog));
405 let obsolete_entry_ids = Arc::new(Mutex::new(released_entry_ids));
406 let wal_holes = WalHoles::default();
407 let deleting = Arc::new(DeletingObjects::default());
408 let terminal_error = TerminalError::default();
409 let stopped = Arc::new(AtomicBool::new(false));
410 let (command_tx, command_rx) = mpsc::channel(COMMAND_BUFFER);
411 let (append_tx, append_rx) = mpsc::channel(APPEND_BUFFER);
412 #[cfg(any(test, feature = "testing"))]
413 let (admitted_appends_tx, admitted_appends_rx) = watch::channel(0);
414 #[cfg(any(test, feature = "testing"))]
415 let (creates_held_tx, creates_held_rx) = watch::channel(false);
416 #[cfg(any(test, feature = "testing"))]
417 let creates_fail = Arc::new(AtomicBool::new(false));
418 #[cfg(any(test, feature = "testing"))]
419 let next_create_fails_after_write = Arc::new(AtomicBool::new(false));
420 #[cfg(any(test, feature = "testing"))]
421 let (parked_creates_tx, parked_creates_rx) = watch::channel(0);
422 #[cfg(any(test, feature = "testing"))]
423 let (durability_waits_tx, durability_waits_rx) = watch::channel(0);
424
425 let started_regions = StartedRegions::default();
426 let actor = Actor {
427 io: io.clone(),
428 started_regions: started_regions.clone(),
429 withdrawal: Withdrawal::Kept,
430 catalog: catalog.clone(),
431 obsolete_entry_ids: obsolete_entry_ids.clone(),
432 wal_holes: wal_holes.clone(),
433 terminal_error: terminal_error.clone(),
434 stopped: stopped.clone(),
435 command_rx,
436 append_rx,
437 ack_mode: config.ack_mode,
438 max_unpersisted_bytes,
439 max_unpersisted_age: config.max_unpersisted_age,
440 open_batch: OpenBatch::new(max_batch_bytes),
441 issued_entry_ids: durable_entry_ids,
442 pending: Vec::new(),
443 sealed: VecDeque::new(),
444 creates: FuturesUnordered::new(),
445 stalled: None,
446 failing: None,
447 durable_waiters: Vec::new(),
448 stop: Vec::new(),
449 stop_error: None,
450 next_object_seq: start
451 .object_seq
452 .checked_add(1)
453 .filter(|next_object_seq| *next_object_seq < OBJECT_SEQ_LIMIT),
454 epoch,
455 last_indexed: start,
456 reclaim_boundary: writer_reclaim_boundary,
457 durable_reclaim_boundary: reclaim_boundary,
458 unindexed: unindexed.into_iter().chain(moved_past).collect(),
459 failed_releases: BTreeMap::new(),
460 creating: HashSet::new(),
461 deleting: deleting.clone(),
462 deletes: FuturesUnordered::new(),
463 failed_deletes: HashSet::new(),
464 flush_interval: config.flush_interval,
465 #[cfg(any(test, feature = "testing"))]
466 admitted_appends: admitted_appends_tx,
467 #[cfg(any(test, feature = "testing"))]
468 creates_held: creates_held_rx,
469 #[cfg(any(test, feature = "testing"))]
470 creates_fail: creates_fail.clone(),
471 #[cfg(any(test, feature = "testing"))]
472 next_create_fails_after_write: next_create_fails_after_write.clone(),
473 #[cfg(any(test, feature = "testing"))]
474 parked_creates: Arc::new(parked_creates_tx),
475 #[cfg(any(test, feature = "testing"))]
476 durability_waits: durability_waits_tx,
477 };
478 #[cfg(test)]
479 LIVE_ACTORS.fetch_add(1, Ordering::SeqCst);
480 common_runtime::spawn_global(actor.run());
481 let storage = match config.storage_provider.as_str() {
482 "" => format!("the default storage ({})", io.storage()),
483 name => format!("storage provider {name:?} ({})", io.storage()),
484 };
485 Ok(Arc::new(Self {
486 prefix,
487 storage,
488 ack_mode: config.ack_mode,
489 on_corrupted_segment: config.on_corrupted_segment,
490 accept_missing_history: config.accept_missing_history,
491 found_empty,
492 history_end,
493 started_regions,
494 io,
495 catalog,
496 obsolete_entry_ids,
497 wal_holes,
498 deleting,
499 read_prefetch_budget: Arc::new(Semaphore::new(READ_PREFETCH_BYTES)),
500 terminal_error,
501 stopped,
502 command_tx,
503 append_tx,
504 #[cfg(any(test, feature = "testing"))]
505 admitted_appends: admitted_appends_rx,
506 #[cfg(any(test, feature = "testing"))]
507 creates_held: creates_held_tx,
508 #[cfg(any(test, feature = "testing"))]
509 creates_fail,
510 #[cfg(any(test, feature = "testing"))]
511 next_create_fails_after_write,
512 #[cfg(any(test, feature = "testing"))]
513 parked_creates: parked_creates_rx,
514 #[cfg(any(test, feature = "testing"))]
515 durability_waits: durability_waits_rx,
516 }))
517 }
518
519 pub(crate) fn durable_entry_id(&self, provider: &Provider) -> Result<EntryId> {
524 self.check_terminal()?;
525 let region_id = self.region_of(provider)?;
526 Ok(self.durable_entry_id_of(region_id))
527 }
528
529 fn durable_entry_id_of(&self, region_id: RegionId) -> EntryId {
530 let catalog = self.catalog.read().unwrap_or_else(PoisonError::into_inner);
531 catalog.region_max_entry_id(region_id).unwrap_or(0)
532 }
533
534 pub fn wal_holes(&self, provider: &Provider) -> Result<Vec<WalHole>> {
539 let region_id = self.region_of(provider)?;
540 Ok(self
541 .wal_holes
542 .lock()
543 .unwrap_or_else(PoisonError::into_inner)
544 .get(®ion_id)
545 .map(|holes| holes.values().cloned().collect())
546 .unwrap_or_default())
547 }
548
549 fn region_of(&self, provider: &Provider) -> Result<RegionId> {
551 let provider =
552 provider
553 .as_object_store_provider()
554 .with_context(|| InvalidProviderSnafu {
555 expected: ObjectStoreProvider::type_name(),
556 actual: provider.type_name(),
557 })?;
558 ensure!(
559 provider.prefix == self.prefix,
560 MismatchedWalPrefixSnafu {
561 expected: self.prefix.clone(),
562 actual: provider.prefix.clone(),
563 }
564 );
565 Ok(provider.region_id)
566 }
567
568 fn check_region(&self, provider: &Provider, region_id: RegionId) -> Result<()> {
569 self.check_terminal()?;
570 let provider_region = self.region_of(provider)?;
571 ensure!(
572 provider_region == region_id,
573 MismatchedWalRegionSnafu {
574 region_id,
575 reason: format!("provider belongs to region {provider_region}"),
576 }
577 );
578 Ok(())
579 }
580
581 fn check_terminal(&self) -> Result<()> {
582 match terminal(&self.terminal_error) {
583 Some(error) => Err(shared(&error)),
584 None => Ok(()),
585 }
586 }
587
588 async fn release(&self, region_id: RegionId, full: bool) -> Result<()> {
599 let (response_tx, response_rx) = oneshot::channel();
600 self.command_tx
601 .send(Command::Release {
602 region_id,
603 full,
604 response: response_tx,
605 })
606 .await
607 .ok()
608 .context(ObjectStoreWalStoppedSnafu)?;
609 response_rx.await.ok().context(ObjectStoreWalStoppedSnafu)?
610 }
611
612 fn missing_history(&self, region_id: RegionId, entry_id: EntryId) -> Option<String> {
644 if self
645 .started_regions
646 .lock()
647 .unwrap_or_else(PoisonError::into_inner)
648 .contains(®ion_id)
649 {
650 return None;
651 }
652 if self
653 .catalog
654 .read()
655 .unwrap_or_else(PoisonError::into_inner)
656 .region_max_entry_id(region_id)
657 .is_some_and(|max_entry_id| max_entry_id >= entry_id)
658 {
659 return None;
660 }
661 if self.found_empty {
662 return Some("the prefix held no object when the WAL opened".to_string());
663 }
664 let sequence_floor = sequence_floor(entry_id);
665 (sequence_floor > self.history_end).then(|| {
666 let held = match self.history_end {
667 0 => "no entry".to_string(),
668 history_end => format!("entries only in objects below {history_end}"),
669 };
670 format!(
671 "its watermark, entry {entry_id}, names object {}, but the prefix held {held} when the WAL opened",
672 sequence_floor - 1
673 )
674 })
675 }
676
677 async fn withdraw_start(&self) {
690 let (response_tx, response_rx) = oneshot::channel();
691 if self
692 .command_tx
693 .send(Command::Withdraw {
694 response: response_tx,
695 })
696 .await
697 .is_ok()
698 {
699 let _ = response_rx.await;
700 }
701 }
702}
703
704#[cfg(any(test, feature = "testing"))]
706fn injected_create_failure(io: &dyn WalObjectIo, object_seq: u64) -> Result<PutResult> {
707 let error = object_store::Error::new(
708 object_store::ErrorKind::Unexpected,
709 "injected create failure",
710 )
711 .set_temporary();
712 Err(error).context(crate::error::WalObjectStoreSnafu {
713 operation: "write",
714 path: io.object_path(object_seq),
715 })
716}
717
718fn record_obsolete(
721 obsolete_entry_ids: &ObsoleteEntryIds,
722 catalog: &RwLock<ObjectCatalog>,
723 region_id: RegionId,
724 entry_id: EntryId,
725) {
726 obsolete_entry_ids
727 .lock()
728 .unwrap_or_else(PoisonError::into_inner)
729 .entry(region_id)
730 .and_modify(|current| *current = (*current).max(entry_id))
731 .or_insert(entry_id);
732 catalog
733 .write()
734 .unwrap_or_else(PoisonError::into_inner)
735 .obsolete(region_id, entry_id);
736}
737
738fn positive_bytes(bytes: u64, name: &str) -> Result<usize> {
739 usize::try_from(bytes)
740 .ok()
741 .filter(|bytes| *bytes > 0)
742 .with_context(|| InvalidWalObjectStoreSnafu {
743 reason: format!("{name} {bytes} is zero or too large"),
744 })
745}
746
747#[cfg(any(test, feature = "testing"))]
748impl ObjectStoreLogStore {
749 pub async fn wait_for_admitted_appends(&self, expected: usize) -> Result<()> {
752 self.admitted_appends
753 .clone()
754 .wait_for(|count| *count >= expected)
755 .await
756 .ok()
757 .map(|_| ())
758 .context(ObjectStoreWalStoppedSnafu)
759 }
760
761 pub async fn seal_open_batch(&self) -> Result<()> {
764 ensure!(
765 !self.stopped.load(Ordering::Acquire),
766 ObjectStoreWalStoppedSnafu
767 );
768 let (response_tx, response_rx) = oneshot::channel();
769 self.command_tx
770 .send(Command::Seal {
771 response: response_tx,
772 })
773 .await
774 .ok()
775 .context(ObjectStoreWalStoppedSnafu)?;
776 response_rx.await.ok().context(ObjectStoreWalStoppedSnafu)?
777 }
778
779 pub fn hold_creates(&self) {
784 self.creates_held.send_replace(true);
785 }
786
787 pub fn release_creates(&self) {
789 self.creates_held.send_replace(false);
790 }
791
792 pub fn fail_creates(&self) {
795 self.creates_fail.store(true, Ordering::Release);
796 }
797
798 pub fn allow_creates(&self) {
801 self.creates_fail.store(false, Ordering::Release);
802 }
803
804 pub fn fail_next_create_after_write(&self) {
808 self.next_create_fails_after_write
809 .store(true, Ordering::Release);
810 }
811
812 pub async fn wait_for_parked_creates(&self, expected: usize) -> Result<()> {
815 self.parked_creates
816 .clone()
817 .wait_for(|count| *count >= expected)
818 .await
819 .ok()
820 .map(|_| ())
821 .context(ObjectStoreWalStoppedSnafu)
822 }
823
824 pub async fn wait_for_durability_waits(&self, expected: usize) -> Result<()> {
828 self.durability_waits
829 .clone()
830 .wait_for(|count| *count >= expected)
831 .await
832 .ok()
833 .map(|_| ())
834 .context(ObjectStoreWalStoppedSnafu)
835 }
836
837 pub async fn crash(&self) {
841 self.stopped.store(true, Ordering::Release);
842 let (response_tx, response_rx) = oneshot::channel();
843 if self
844 .command_tx
845 .send(Command::Crash {
846 response: response_tx,
847 })
848 .await
849 .is_ok()
850 {
851 let _ = response_rx.await;
852 }
853 }
854
855 pub async fn wait_for_garbage_collection(&self) {
860 self.deleting.wait_until_empty().await;
861 }
862
863 pub fn begin_stop(&self) {
866 self.stopped.store(true, Ordering::Release);
867 }
868
869 pub fn segment_location(
874 &self,
875 provider: &Provider,
876 object_seq: u64,
877 ) -> Result<Option<(String, Range<u64>)>> {
878 let region_id = self.region_of(provider)?;
879 let catalog = self.catalog.read().unwrap_or_else(PoisonError::into_inner);
880 Ok(catalog
881 .objects_for_entry_range(region_id, 0, EntryId::MAX)?
882 .into_iter()
883 .find(|(seq, _)| *seq == object_seq)
884 .map(|(_, entry)| {
885 let start = entry.segment_offset;
886 (
887 self.io.object_path(object_seq),
888 start..start + entry.segment_len,
889 )
890 }))
891 }
892}
893
894#[async_trait::async_trait]
895impl LogStore for ObjectStoreLogStore {
896 type Error = Error;
897
898 async fn stop(&self) -> Result<()> {
902 self.stopped.store(true, Ordering::Release);
903 let (response_tx, response_rx) = oneshot::channel();
904 let sent = self
905 .command_tx
906 .send(Command::Stop {
907 response: response_tx,
908 })
909 .await;
910 if sent.is_ok() {
912 return response_rx.await.unwrap_or(Ok(()));
913 }
914 Ok(())
915 }
916
917 async fn append_batch(&self, entries: Vec<Entry>) -> Result<AppendBatchResponse> {
918 ensure!(
919 !self.stopped.load(Ordering::Acquire),
920 ObjectStoreWalStoppedSnafu
921 );
922 self.check_terminal()?;
923 if entries.is_empty() {
924 return Ok(AppendBatchResponse::default());
925 }
926 for entry in &entries {
927 let region_id = self.region_of(entry.provider())?;
928 ensure!(
929 region_id == entry.region_id(),
930 MismatchedWalRegionSnafu {
931 region_id: entry.region_id(),
932 reason: format!("provider belongs to region {region_id}"),
933 }
934 );
935 ensure!(entry.is_complete(), IncompleteWalEntrySnafu { region_id });
936 }
937
938 let requested_at = Instant::now();
939 let (response_tx, response_rx) = oneshot::channel();
940 self.append_tx
941 .send((entries, response_tx))
942 .await
943 .ok()
944 .context(ObjectStoreWalStoppedSnafu)?;
945 let response = response_rx.await.ok().context(ObjectStoreWalStoppedSnafu)?;
946 if self.ack_mode == AckMode::Durable && response.is_ok() {
948 METRIC_OBJECT_STORE_WAL_APPEND_ACK_SECONDS
949 .observe(requested_at.elapsed().as_secs_f64());
950 }
951 response
952 }
953
954 async fn read(
963 &self,
964 provider: &Provider,
965 entry_id: EntryId,
966 ) -> Result<SendableEntryStream<'static, Entry, Error>> {
967 self.check_terminal()?;
968 let region_id = self.region_of(provider)?;
969 let obsolete = self
970 .obsolete_entry_ids
971 .lock()
972 .unwrap_or_else(PoisonError::into_inner)
973 .get(®ion_id)
974 .copied();
975 let start_entry_id =
976 entry_id.max(obsolete.map_or(0, |obsolete| obsolete.saturating_add(1)));
977 let objects = {
978 let catalog = self.catalog.read().unwrap_or_else(PoisonError::into_inner);
979 match catalog.region_max_entry_id(region_id) {
980 Some(max_entry_id)
981 if start_entry_id <= max_entry_id && obsolete != Some(EntryId::MAX) =>
982 {
983 catalog
984 .objects_for_entry_range(region_id, start_entry_id, max_entry_id)?
985 .into_iter()
986 .map(|(object_seq, entry)| (object_seq, entry.clone()))
987 .collect::<Vec<_>>()
988 }
989 _ => Vec::new(),
990 }
991 };
992
993 let io = self.io.clone();
994 let catalog = self.catalog.clone();
995 let deleting = self.deleting.clone();
996 let provider = provider.clone();
997 let on_corrupted_segment = self.on_corrupted_segment;
998 let wal_holes = self.wal_holes.clone();
999 let prefetch_budget = self.read_prefetch_budget.clone();
1000 Ok(Box::pin(try_stream! {
1001 let started_at = Instant::now();
1002 let mut objects = objects.into_iter().peekable();
1003 let mut fetches = VecDeque::new();
1004 loop {
1005 while fetches.len() < READ_PREFETCH_DEPTH {
1008 let Some((_, entry)) = objects.peek() else {
1009 break;
1010 };
1011 let permit = if fetches.is_empty() {
1012 None
1013 } else {
1014 match try_reserve_prefetch_bytes(&prefetch_budget, entry) {
1015 Some(permit) => Some(permit),
1016 None => break,
1017 }
1018 };
1019 let Some((object_seq, footer_entry)) = objects.next() else {
1020 break;
1021 };
1022 let fetch =
1023 spawn_fetch_segment(io.clone(), object_seq, footer_entry.clone(), permit);
1024 fetches.push_back((object_seq, footer_entry, fetch));
1025 }
1026 let Some((object_seq, footer_entry, fetch)) = fetches.pop_front() else {
1027 break;
1028 };
1029 let (fetched, _permit) = fetch.await;
1031 let records = match fetched {
1032 Ok(records) => records,
1033 Err(error) if is_collected(&catalog, &deleting, object_seq).await => {
1034 debug!(
1035 "Skipped WAL object {} (sequence {}), collected after the read listed it: {error}",
1036 io.object_path(object_seq),
1037 object_seq
1038 );
1039 continue;
1040 }
1041 Err(error @ Error::InvalidWalObject { .. })
1042 if on_corrupted_segment == CorruptedSegmentAction::Skip =>
1043 {
1044 record_skipped_segment(&catalog, &wal_holes, io.as_ref(), object_seq, &footer_entry, &error);
1045 continue;
1046 }
1047 Err(error) => Err(error)?,
1048 };
1049 let entries = records
1050 .into_iter()
1051 .filter(|record| record.entry_id >= start_entry_id)
1052 .map(|record| {
1053 Entry::Naive(NaiveEntry {
1054 provider: provider.clone(),
1055 region_id,
1056 entry_id: record.entry_id,
1057 data: record.payload.into(),
1058 })
1059 })
1060 .collect::<Vec<_>>();
1061 if !entries.is_empty() {
1062 yield entries;
1063 }
1064 }
1065 METRIC_OBJECT_STORE_WAL_READ_SECONDS.observe(started_at.elapsed().as_secs_f64());
1066 }))
1067 }
1068
1069 async fn create_namespace(&self, ns: &Provider) -> Result<()> {
1070 self.check_terminal()?;
1071 self.region_of(ns).map(|_| ())
1072 }
1073
1074 async fn delete_namespace(&self, ns: &Provider) -> Result<()> {
1075 self.check_terminal()?;
1076 self.region_of(ns).map(|_| ())
1077 }
1078
1079 async fn list_namespaces(&self) -> Result<Vec<Provider>> {
1080 self.check_terminal()?;
1081 let catalog = self.catalog.read().unwrap_or_else(PoisonError::into_inner);
1082 let regions = catalog
1083 .objects_in_order()
1084 .flat_map(|(_, footer)| footer.iter().map(|entry| entry.region_id))
1085 .filter(|region_id| *region_id != RELEASE_REGION_ID)
1086 .collect::<BTreeSet<_>>();
1087 Ok(regions
1088 .into_iter()
1089 .map(|region_id| Provider::object_store_provider(region_id, self.prefix.clone()))
1090 .collect())
1091 }
1092
1093 async fn obsolete(
1121 &self,
1122 provider: &Provider,
1123 region_id: RegionId,
1124 entry_id: EntryId,
1125 ) -> Result<()> {
1126 self.check_region(provider, region_id)?;
1127 let watermark = match self.ack_mode {
1128 AckMode::Durable => entry_id,
1129 AckMode::Enqueued => entry_id.min(self.durable_entry_id_of(region_id)),
1130 };
1131 let (response_tx, response_rx) = oneshot::channel();
1132 let command = Command::Obsolete {
1133 region_id,
1134 entry_id,
1135 watermark,
1136 response: response_tx,
1137 };
1138 let answered = match self.command_tx.send(command).await {
1139 Ok(()) => response_rx.await.ok(),
1140 Err(_) => None,
1141 };
1142 match answered {
1143 Some(result) => result,
1144 None => {
1148 record_obsolete(
1149 &self.obsolete_entry_ids,
1150 &self.catalog,
1151 region_id,
1152 watermark,
1153 );
1154 Ok(())
1155 }
1156 }
1157 }
1158
1159 async fn obsolete_all(&self, provider: &Provider, region_id: RegionId) -> Result<()> {
1162 self.check_region(provider, region_id)?;
1163 self.release(region_id, true).await
1164 }
1165
1166 async fn release_region(&self, provider: &Provider, region_id: RegionId) -> Result<()> {
1170 self.check_region(provider, region_id)?;
1171 self.release(region_id, false).await
1172 }
1173
1174 fn entry(
1175 &self,
1176 data: Vec<u8>,
1177 entry_id: EntryId,
1178 region_id: RegionId,
1179 provider: &Provider,
1180 ) -> Result<Entry> {
1181 self.check_region(provider, region_id)?;
1182 Ok(Entry::Naive(NaiveEntry {
1183 provider: provider.clone(),
1184 region_id,
1185 entry_id,
1186 data,
1187 }))
1188 }
1189
1190 fn latest_entry_id(&self, provider: &Provider) -> Result<EntryId> {
1191 self.durable_entry_id(provider)
1192 }
1193
1194 async fn wait_durable(&self, provider: &Provider, entry_id: EntryId) -> Result<()> {
1198 self.check_terminal()?;
1199 let region_id = self.region_of(provider)?;
1200 if entry_id <= self.durable_entry_id_of(region_id) {
1201 return Ok(());
1202 }
1203 let (response_tx, response_rx) = oneshot::channel();
1204 self.command_tx
1205 .send(Command::WaitDurable {
1206 region_id,
1207 entry_id,
1208 response: response_tx,
1209 })
1210 .await
1211 .ok()
1212 .context(ObjectStoreWalStoppedSnafu)?;
1213 response_rx.await.ok().context(ObjectStoreWalStoppedSnafu)?
1214 }
1215
1216 async fn check_region_history(&self, provider: &Provider, entry_id: EntryId) -> Result<()> {
1221 let region_id = self.region_of(provider)?;
1222 let Some(reason) = self.missing_history(region_id, entry_id) else {
1223 return self.check_terminal();
1224 };
1225 if !self.accept_missing_history {
1226 if self.found_empty {
1227 self.withdraw_start().await;
1228 }
1229 return MissingWalHistorySnafu {
1230 region_id,
1231 prefix: self.prefix.clone(),
1232 storage: self.storage.clone(),
1233 reason,
1234 }
1235 .fail();
1236 }
1237 self.check_terminal()?;
1238 warn!(
1239 "Opening region {region_id} without its WAL history, as accept_missing_history allows: prefix {} on {} does not hold it, {reason}; the region's writes above entry {entry_id} that were not flushed are lost",
1240 self.prefix, self.storage
1241 );
1242 METRIC_OBJECT_STORE_WAL_MISSING_HISTORY_ACCEPTED_TOTAL.inc();
1243 self.start_region_history(provider)
1244 }
1245
1246 fn start_region_history(&self, provider: &Provider) -> Result<()> {
1250 let region_id = self.region_of(provider)?;
1251 let mut started_regions = self
1252 .started_regions
1253 .lock()
1254 .unwrap_or_else(PoisonError::into_inner);
1255 self.check_terminal()?;
1257 started_regions.insert(region_id);
1258 Ok(())
1259 }
1260}
1261
1262type AppendResponse = oneshot::Sender<Result<AppendBatchResponse>>;
1263
1264type QueuedAppend = (Vec<Entry>, AppendResponse);
1265
1266enum Command {
1267 WaitDurable {
1269 region_id: RegionId,
1270 entry_id: EntryId,
1271 response: oneshot::Sender<Result<()>>,
1272 },
1273 Obsolete {
1277 region_id: RegionId,
1278 entry_id: EntryId,
1279 watermark: EntryId,
1280 response: oneshot::Sender<Result<()>>,
1281 },
1282 Release {
1287 region_id: RegionId,
1288 full: bool,
1289 response: oneshot::Sender<Result<()>>,
1290 },
1291 Withdraw { response: oneshot::Sender<()> },
1294 Stop {
1295 response: oneshot::Sender<Result<()>>,
1296 },
1297 #[cfg(any(test, feature = "testing"))]
1298 Seal {
1299 response: oneshot::Sender<Result<()>>,
1300 },
1301 #[cfg(any(test, feature = "testing"))]
1302 Crash { response: oneshot::Sender<()> },
1303}
1304
1305struct PendingAppend {
1307 last_entry_ids: HashMap<RegionId, EntryId>,
1308 response: AppendResponse,
1309}
1310
1311struct DurableWaiter {
1313 region_id: RegionId,
1314 entry_id: EntryId,
1315 response: oneshot::Sender<Result<()>>,
1316}
1317
1318enum CreateState {
1320 Pending,
1324 InFlight,
1325 Created,
1326}
1327
1328struct SealedBatch {
1331 object_seq: u64,
1332 reclaim_boundary: u64,
1333 bytes: Bytes,
1334 footer: Vec<FooterEntry>,
1335 releases: Vec<Release>,
1336 first_admitted_at: Instant,
1337 sealed_at: Instant,
1338 attempts: u32,
1340 waiters: Vec<PendingAppend>,
1341 release_waiters: Vec<oneshot::Sender<Result<()>>>,
1344 #[cfg(any(test, feature = "testing"))]
1345 seal_waiters: Vec<oneshot::Sender<Result<()>>>,
1346 state: CreateState,
1347}
1348
1349impl SealedBatch {
1350 fn is_in_flight(&self) -> bool {
1351 matches!(self.state, CreateState::InFlight)
1352 }
1353
1354 fn fail(self, error: impl Fn() -> Error) {
1355 for waiter in self.waiters {
1356 let _ = waiter.response.send(Err(error()));
1357 }
1358 for waiter in self.release_waiters {
1359 let _ = waiter.send(Err(error()));
1360 }
1361 #[cfg(any(test, feature = "testing"))]
1362 for waiter in self.seal_waiters {
1363 let _ = waiter.send(Err(error()));
1364 }
1365 }
1366}
1367
1368type CreateOutcome = (u64, Result<PutResult>);
1369
1370enum Withdrawal {
1373 Kept,
1374 Deleting {
1377 object_seq: u64,
1378 waiters: Vec<oneshot::Sender<()>>,
1379 },
1380 Settled,
1381}
1382
1383struct Failing {
1397 error: Arc<Error>,
1399 since: Instant,
1400 delay: Duration,
1402 probe_at: Option<Instant>,
1404 failed_creates: u64,
1405 rejected_appends: u64,
1406 logged_at: Instant,
1407}
1408
1409impl Failing {
1410 fn schedule(&mut self) {
1413 self.probe_at = Some(Instant::now() + self.delay);
1414 self.delay = (self.delay * 2).min(MAX_FAILING_RETRY_DELAY);
1415 }
1416
1417 fn reject(&mut self) -> Error {
1420 self.rejected_appends += 1;
1421 METRIC_OBJECT_STORE_WAL_REJECTED_APPENDS_TOTAL.inc();
1422 ObjectStoreWalFailingSnafu.into_error(self.error.clone())
1423 }
1424}
1425
1426struct Actor {
1473 io: Arc<dyn WalObjectIo>,
1474 started_regions: StartedRegions,
1475 withdrawal: Withdrawal,
1476 catalog: Arc<RwLock<ObjectCatalog>>,
1477 obsolete_entry_ids: ObsoleteEntryIds,
1478 wal_holes: WalHoles,
1479 terminal_error: TerminalError,
1480 stopped: Arc<AtomicBool>,
1481 command_rx: mpsc::Receiver<Command>,
1482 append_rx: mpsc::Receiver<QueuedAppend>,
1483 ack_mode: AckMode,
1484 max_unpersisted_bytes: usize,
1485 max_unpersisted_age: Duration,
1486 open_batch: OpenBatch,
1487 issued_entry_ids: HashMap<RegionId, EntryId>,
1491 pending: Vec<PendingAppend>,
1493 sealed: VecDeque<SealedBatch>,
1495 creates: FuturesUnordered<BoxFuture<'static, CreateOutcome>>,
1497 stalled: Option<(QueuedAppend, Instant)>,
1501 failing: Option<Failing>,
1504 durable_waiters: Vec<DurableWaiter>,
1505 stop: Vec<oneshot::Sender<Result<()>>>,
1507 stop_error: Option<Arc<Error>>,
1510 next_object_seq: Option<u64>,
1514 epoch: u64,
1516 last_indexed: ChainLink,
1519 reclaim_boundary: u64,
1521 durable_reclaim_boundary: u64,
1523 unindexed: BTreeSet<u64>,
1526 failed_releases: BTreeMap<RegionId, EntryId>,
1531 creating: HashSet<u64>,
1533 deleting: Arc<DeletingObjects>,
1534 deletes: FuturesUnordered<BoxFuture<'static, (u64, Result<()>)>>,
1535 failed_deletes: HashSet<u64>,
1537 flush_interval: Duration,
1538 #[cfg(any(test, feature = "testing"))]
1539 admitted_appends: watch::Sender<usize>,
1540 #[cfg(any(test, feature = "testing"))]
1541 creates_held: watch::Receiver<bool>,
1542 #[cfg(any(test, feature = "testing"))]
1543 creates_fail: Arc<AtomicBool>,
1544 #[cfg(any(test, feature = "testing"))]
1545 next_create_fails_after_write: Arc<AtomicBool>,
1546 #[cfg(any(test, feature = "testing"))]
1547 parked_creates: Arc<watch::Sender<usize>>,
1548 #[cfg(any(test, feature = "testing"))]
1549 durability_waits: watch::Sender<usize>,
1550}
1551
1552impl Actor {
1553 async fn run(mut self) {
1554 let mut interval = tokio::time::interval(self.flush_interval);
1555 interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
1556 interval.tick().await;
1558
1559 loop {
1560 let probe_at = self.failing.as_ref().and_then(|failing| failing.probe_at);
1561 tokio::select! {
1562 _ = interval.tick() => {
1563 if !self.is_stopped() {
1566 self.flush_open_batch();
1567 }
1568 }
1569 Some((object_seq, result)) = self.creates.next(), if !self.creates.is_empty() => {
1570 self.on_create_completed(object_seq, result);
1571 }
1572 Some((object_seq, result)) = self.deletes.next(), if !self.deletes.is_empty() => {
1573 self.on_delete_completed(object_seq, result);
1574 }
1575 Some(()) = OptionFuture::from(probe_at.map(|at| tokio::time::sleep_until(at.into()))) => {
1576 self.probe();
1577 }
1578 Some((entries, response)) = self.append_rx.recv(), if self.stalled.is_none() => {
1579 self.handle_append(entries, response);
1580 }
1581 command = self.command_rx.recv() => match command {
1582 Some(Command::WaitDurable { region_id, entry_id, response }) => {
1583 self.handle_wait_durable(region_id, entry_id, response);
1584 }
1585 Some(Command::Obsolete { region_id, entry_id, watermark, response }) => {
1586 self.handle_obsolete(region_id, entry_id, watermark, response);
1587 }
1588 Some(Command::Release { region_id, full, response }) => {
1589 self.handle_release(region_id, full, response);
1590 }
1591 Some(Command::Withdraw { response }) => {
1592 self.handle_withdraw(response);
1593 }
1594 Some(Command::Stop { response }) => {
1595 self.handle_stop(response);
1596 }
1597 #[cfg(any(test, feature = "testing"))]
1598 Some(Command::Seal { response }) => {
1599 self.handle_seal(response);
1600 }
1601 #[cfg(any(test, feature = "testing"))]
1602 Some(Command::Crash { response }) => {
1603 self.handle_crash();
1604 let _ = response.send(());
1605 return;
1606 }
1607 None => return,
1610 },
1611 }
1612 if self.finish_stop() {
1613 return;
1614 }
1615 }
1616 }
1617
1618 fn handle_append(&mut self, entries: Vec<Entry>, response: AppendResponse) {
1619 if self.is_stopped() {
1622 let _ = response.send(Err(ObjectStoreWalStoppedSnafu.build()));
1623 return;
1624 }
1625 if let Some(error) = terminal(&self.terminal_error) {
1626 let _ = response.send(Err(shared(&error)));
1627 return;
1628 }
1629 if let Some(failing) = &mut self.failing {
1630 let _ = response.send(Err(failing.reject()));
1631 return;
1632 }
1633 if self.should_hold_back() {
1634 METRIC_OBJECT_STORE_WAL_STALLED_APPENDS_TOTAL.inc();
1635 self.stalled = Some(((entries, response), Instant::now()));
1636 self.ensure_create_in_flight();
1637 return;
1638 }
1639 self.admit(entries, response);
1640 }
1641
1642 fn admit(&mut self, entries: Vec<Entry>, response: AppendResponse) {
1646 if !self.open_batch.is_empty() && self.open_batch.would_exhaust_positions(&entries) {
1651 self.flush_open_batch();
1652 if let Some(error) = terminal(&self.terminal_error) {
1653 let _ = response.send(Err(shared(&error)));
1654 return;
1655 }
1656 }
1657 let Some(object_seq) = self.next_object_seq else {
1658 let error = self.poison(
1659 WalObjectSequenceExhaustedSnafu {
1660 last_object_seq: OBJECT_SEQ_LIMIT - 1,
1661 }
1662 .build(),
1663 );
1664 let _ = response.send(Err(shared(&error)));
1665 return;
1666 };
1667 let last_entry_ids = match self.open_batch.admit(object_seq, entries) {
1668 Ok(last_entry_ids) => last_entry_ids,
1669 Err(error) => {
1672 let _ = response.send(Err(error));
1673 return;
1674 }
1675 };
1676 for (region_id, entry_id) in &last_entry_ids {
1677 self.issued_entry_ids
1678 .entry(*region_id)
1679 .and_modify(|issued| *issued = (*issued).max(*entry_id))
1680 .or_insert(*entry_id);
1681 }
1682 match self.ack_mode {
1683 AckMode::Durable => self.pending.push(PendingAppend {
1684 last_entry_ids,
1685 response,
1686 }),
1687 AckMode::Enqueued => {
1688 let _ = response.send(Ok(AppendBatchResponse { last_entry_ids }));
1689 }
1690 }
1691 #[cfg(any(test, feature = "testing"))]
1692 self.admitted_appends.send_modify(|count| *count += 1);
1693 if self.open_batch.should_seal() {
1694 self.flush_open_batch();
1695 }
1696 }
1697
1698 fn should_hold_back(&self) -> bool {
1702 self.sealed.len() + 2 > MAX_SEALED_BATCHES
1703 || (self.ack_mode == AckMode::Enqueued && self.backlog_at_threshold())
1704 }
1705
1706 fn backlog_at_threshold(&self) -> bool {
1709 let bytes = self.open_batch.estimated_bytes()
1710 + self
1711 .sealed
1712 .iter()
1713 .map(|batch| batch.bytes.len())
1714 .sum::<usize>();
1715 if bytes >= self.max_unpersisted_bytes {
1716 return true;
1717 }
1718 let oldest = self
1719 .sealed
1720 .front()
1721 .map(|batch| batch.first_admitted_at)
1722 .or_else(|| self.open_batch.first_admitted_at());
1723 oldest.is_some_and(|admitted_at| admitted_at.elapsed() >= self.max_unpersisted_age)
1724 }
1725
1726 fn ensure_create_in_flight(&mut self) {
1729 if !self.sealed.iter().any(SealedBatch::is_in_flight) {
1730 self.flush_open_batch();
1731 }
1732 }
1733
1734 fn release_stalled(&mut self) {
1736 if self.stalled.is_none() {
1737 return;
1738 }
1739 if self.is_stopped() {
1740 if let Some((_, response)) = self.take_stalled() {
1741 let _ = response.send(Err(ObjectStoreWalStoppedSnafu.build()));
1742 }
1743 return;
1744 }
1745 if self.should_hold_back() {
1746 self.ensure_create_in_flight();
1748 return;
1749 }
1750 if let Some((entries, response)) = self.take_stalled() {
1751 self.admit(entries, response);
1752 }
1753 }
1754
1755 fn take_stalled(&mut self) -> Option<QueuedAppend> {
1758 let (append, stalled_at) = self.stalled.take()?;
1759 METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS.observe(stalled_at.elapsed().as_secs_f64());
1760 Some(append)
1761 }
1762
1763 fn flush_open_batch(&mut self) -> bool {
1766 self.seal(Vec::new())
1767 }
1768
1769 fn seal(&mut self, releases: Vec<Release>) -> bool {
1778 if self.open_batch.is_empty()
1779 && releases.is_empty()
1780 && (self.failed_releases.is_empty() || (self.failing.is_some() && self.stop.is_empty()))
1781 {
1782 return false;
1783 }
1784 if let Some(error) = terminal(&self.terminal_error) {
1785 self.fail_unacknowledged(|| shared(&error));
1786 return false;
1787 }
1788 let Some(object_seq) = self.next_object_seq else {
1789 self.poison(
1790 WalObjectSequenceExhaustedSnafu {
1791 last_object_seq: OBJECT_SEQ_LIMIT - 1,
1792 }
1793 .build(),
1794 );
1795 return false;
1796 };
1797
1798 let (entries, first_admitted_at) = self.open_batch.seal();
1799 let entry_count = entries.len();
1800 let header = Header {
1801 object_seq,
1802 epoch: self.epoch,
1803 prev: Some(
1804 self.sealed
1805 .back()
1806 .map_or(self.last_indexed, |batch| ChainLink {
1807 object_seq: batch.object_seq,
1808 epoch: self.epoch,
1809 }),
1810 ),
1811 reclaim_boundary: self.reclaim_boundary,
1812 };
1813 let mut records = std::mem::take(&mut self.failed_releases);
1814 for release in releases {
1815 let entry_id = records.entry(release.region_id).or_insert(release.entry_id);
1816 *entry_id = (*entry_id).max(release.entry_id);
1817 }
1818 let encoded = match encode_batch(header, entries, &records) {
1819 Ok(encoded) => encoded,
1820 Err(error) => {
1821 self.poison(error);
1822 return false;
1823 }
1824 };
1825 METRIC_OBJECT_STORE_WAL_OBJECT_BYTES.observe(encoded.bytes.len() as f64);
1826 METRIC_OBJECT_STORE_WAL_OBJECT_ENTRIES.observe(entry_count as f64);
1827 self.next_object_seq = object_seq
1828 .checked_add(1)
1829 .filter(|next_object_seq| *next_object_seq < OBJECT_SEQ_LIMIT);
1830 if self.next_object_seq.is_none() {
1831 set_terminal(
1834 &self.terminal_error,
1835 WalObjectSequenceExhaustedSnafu {
1836 last_object_seq: object_seq,
1837 }
1838 .build(),
1839 );
1840 }
1841 self.sealed.push_back(SealedBatch {
1842 object_seq,
1843 reclaim_boundary: self.reclaim_boundary,
1844 bytes: encoded.bytes,
1845 footer: encoded.footer,
1846 releases: records
1847 .into_iter()
1848 .map(|(region_id, entry_id)| Release {
1849 region_id,
1850 entry_id,
1851 })
1852 .collect(),
1853 first_admitted_at,
1854 sealed_at: Instant::now(),
1855 attempts: 0,
1856 waiters: std::mem::take(&mut self.pending),
1857 release_waiters: Vec::new(),
1858 #[cfg(any(test, feature = "testing"))]
1859 seal_waiters: Vec::new(),
1860 state: CreateState::Pending,
1861 });
1862 self.start_creates();
1863 true
1864 }
1865
1866 fn start_creates(&mut self) {
1875 if self.is_stopped() && self.ack_mode == AckMode::Durable && self.stop.is_empty() {
1876 return;
1877 }
1878 if !self.is_stopped()
1879 && let Some(failing) = &mut self.failing
1880 {
1881 if self.creates.is_empty() && failing.probe_at.is_none() {
1882 failing.schedule();
1883 }
1884 return;
1885 }
1886 let mut in_flight = self.creates.len();
1887 for index in 0..self.sealed.len() {
1888 if in_flight >= MAX_IN_FLIGHT_CREATES {
1889 break;
1890 }
1891 let batch = &self.sealed[index];
1892 if !matches!(batch.state, CreateState::Pending) {
1893 continue;
1894 }
1895 let delay = if batch.attempts == 0 {
1896 Duration::ZERO
1897 } else {
1898 CREATE_RETRY_DELAY
1899 };
1900 self.start_create(index, delay);
1901 in_flight += 1;
1902 }
1903 }
1904
1905 fn probe(&mut self) {
1908 let Some(failing) = &mut self.failing else {
1909 return;
1910 };
1911 failing.probe_at = None;
1912 if self.is_stopped() {
1913 return;
1914 }
1915 if let Some(index) = self
1916 .sealed
1917 .iter()
1918 .position(|batch| matches!(batch.state, CreateState::Pending))
1919 {
1920 self.start_create(index, Duration::ZERO);
1921 }
1922 }
1923
1924 fn start_create(&mut self, index: usize, delay: Duration) {
1926 let batch = &mut self.sealed[index];
1927 batch.state = CreateState::InFlight;
1928 batch.attempts += 1;
1929 let object_seq = batch.object_seq;
1930 let sealed_at = batch.sealed_at;
1931 let bytes = batch.bytes.clone();
1932 let io = self.io.clone();
1933 self.creating.insert(object_seq);
1934 let epoch = self.epoch;
1935 let ack_mode = self.ack_mode;
1936 #[cfg(any(test, feature = "testing"))]
1937 let mut creates_held = self.creates_held.clone();
1938 #[cfg(any(test, feature = "testing"))]
1939 let creates_fail = self.creates_fail.clone();
1940 #[cfg(any(test, feature = "testing"))]
1941 let next_create_fails_after_write = self.next_create_fails_after_write.clone();
1942 #[cfg(any(test, feature = "testing"))]
1943 let parked_creates = self.parked_creates.clone();
1944 self.creates.push(Box::pin(async move {
1945 if !delay.is_zero() {
1946 tokio::time::sleep(delay).await;
1947 }
1948 #[cfg(any(test, feature = "testing"))]
1951 if *creates_held.borrow() {
1952 parked_creates.send_modify(|count| *count += 1);
1953 }
1954 #[cfg(any(test, feature = "testing"))]
1955 if creates_held.wait_for(|held| !*held).await.is_err() {
1956 return (object_seq, Err(ObjectStoreWalStoppedSnafu.build()));
1957 }
1958 #[cfg(any(test, feature = "testing"))]
1959 if creates_fail.load(Ordering::Acquire) {
1960 let result = injected_create_failure(io.as_ref(), object_seq);
1961 record_create(&result, Some(sealed_at));
1962 return (object_seq, result);
1963 }
1964 let created = io.put_if_absent(object_seq, bytes).await;
1965 #[cfg(any(test, feature = "testing"))]
1966 let created = match created {
1967 Ok(_) if next_create_fails_after_write.swap(false, Ordering::AcqRel) => {
1968 injected_create_failure(io.as_ref(), object_seq)
1969 }
1970 created => created,
1971 };
1972 record_create(&created, Some(sealed_at));
1975 let result = match created {
1978 Err(error @ Error::WalObjectConflict { .. }) if ack_mode == AckMode::Durable => {
1979 stale_conflict(io.as_ref(), object_seq, epoch, error).await
1980 }
1981 result => result,
1982 };
1983 (object_seq, result)
1984 }));
1985 }
1986
1987 fn on_create_completed(&mut self, object_seq: u64, result: Result<PutResult>) {
1988 self.creating.remove(&object_seq);
1989 let Some(index) = self
1993 .sealed
1994 .iter()
1995 .position(|batch| batch.object_seq == object_seq)
1996 else {
1997 self.start_creates();
1998 self.start_deletes();
1999 return;
2000 };
2001 match result {
2002 Ok(_) => {
2003 self.sealed[index].state = CreateState::Created;
2004 self.leave_failing("a create succeeded");
2005 }
2006 Err(error @ Error::WalObjectStore { .. })
2012 if self.ack_mode == AckMode::Enqueued && !self.is_stopped() =>
2013 {
2014 self.sealed[index].state = CreateState::Pending;
2015 self.on_create_failed(error);
2016 }
2017 Err(error @ Error::WalObjectStore { .. }) => self.roll_back(Arc::new(error)),
2023 Err(error @ Error::StaleWalObject { .. }) if self.ack_mode == AckMode::Durable => {
2024 self.roll_back(Arc::new(error))
2025 }
2026 Err(error) => {
2027 self.poison(error);
2028 return;
2029 }
2030 }
2031 self.settle();
2032 }
2033
2034 fn on_create_failed(&mut self, error: Error) {
2038 let Some(failing) = &mut self.failing else {
2039 if requires_intervention(&error) {
2040 self.enter_failing(error);
2041 }
2042 return;
2043 };
2044 failing.failed_creates += 1;
2045 failing.error = Arc::new(error);
2046 if failing.logged_at.elapsed() >= FAILING_LOG_INTERVAL {
2047 failing.logged_at = Instant::now();
2048 warn!(
2049 failing.error;
2050 "Object store WAL still cannot upload its backlog of {} objects after {:?}: {} creates failed, {} appends rejected, next attempt in {:?}",
2051 self.sealed.len(),
2052 failing.since.elapsed(),
2053 failing.failed_creates,
2054 failing.rejected_appends,
2055 failing.delay
2056 );
2057 }
2058 }
2059
2060 fn enter_failing(&mut self, error: Error) {
2061 error!(
2062 error;
2063 "Object store WAL cannot upload its backlog of {} objects: the object store refused a create with an error that repeating it cannot settle. Appends are rejected until a create succeeds; the backlog is kept and attempted again with backoff",
2064 self.sealed.len()
2065 );
2066 METRIC_OBJECT_STORE_WAL_FAILING.inc();
2067 let stalled = self.take_stalled();
2069 let now = Instant::now();
2070 let failing = self.failing.insert(Failing {
2071 error: Arc::new(error),
2072 since: now,
2073 delay: CREATE_RETRY_DELAY,
2074 probe_at: None,
2075 failed_creates: 1,
2076 rejected_appends: 0,
2077 logged_at: now,
2078 });
2079 if let Some((_, response)) = stalled {
2080 let _ = response.send(Err(failing.reject()));
2081 }
2082 for batch in &mut self.sealed {
2085 for waiter in batch.release_waiters.drain(..) {
2086 let _ = waiter.send(Err(failing.reject()));
2087 }
2088 }
2089 }
2090
2091 fn leave_failing(&mut self, reason: &str) {
2093 let Some(failing) = self.failing.take() else {
2094 return;
2095 };
2096 METRIC_OBJECT_STORE_WAL_FAILING.dec();
2097 info!(
2098 "Object store WAL is no longer failing, {reason}, after {:?}: {} creates failed, {} appends rejected",
2099 failing.since.elapsed(),
2100 failing.failed_creates,
2101 failing.rejected_appends
2102 );
2103 }
2104
2105 fn settle(&mut self) {
2108 while let Some(front) = self.sealed.front() {
2109 if !matches!(front.state, CreateState::Created) || !self.index_front() {
2110 break;
2111 }
2112 }
2113 self.start_creates();
2114 self.release_stalled();
2115 }
2116
2117 fn index_front(&mut self) -> bool {
2120 let Some(front) = self.sealed.front() else {
2121 return false;
2122 };
2123 let indexed = self
2124 .catalog
2125 .write()
2126 .unwrap_or_else(PoisonError::into_inner)
2127 .insert_object(front.object_seq, front.footer.clone());
2128 if let Err(error) = indexed {
2129 self.poison(error);
2130 return false;
2131 }
2132 let Some(batch) = self.sealed.pop_front() else {
2133 return false;
2134 };
2135 METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.inc();
2136 METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.add(batch.bytes.len() as i64);
2137 METRIC_OBJECT_STORE_WAL_RELEASE_RECORDS_TOTAL.inc_by(batch.releases.len() as u64);
2138 self.last_indexed = ChainLink {
2139 object_seq: batch.object_seq,
2140 epoch: self.epoch,
2141 };
2142 self.update_reclaim_boundary();
2145 if batch.reclaim_boundary > self.durable_reclaim_boundary {
2146 self.durable_reclaim_boundary = batch.reclaim_boundary;
2147 self.collect();
2148 }
2149 for waiter in batch.waiters {
2150 let _ = waiter.response.send(Ok(AppendBatchResponse {
2151 last_entry_ids: waiter.last_entry_ids,
2152 }));
2153 }
2154 for waiter in batch.release_waiters {
2155 let _ = waiter.send(Ok(()));
2156 }
2157 #[cfg(any(test, feature = "testing"))]
2158 for waiter in batch.seal_waiters {
2159 let _ = waiter.send(Ok(()));
2160 }
2161 self.resolve_durable_waiters();
2162 true
2163 }
2164
2165 fn resolve_durable_waiters(&mut self) {
2166 let waiters = std::mem::take(&mut self.durable_waiters);
2167 for waiter in waiters {
2168 if self.is_durable_through(waiter.region_id, waiter.entry_id) {
2169 let _ = waiter.response.send(Ok(()));
2170 } else {
2171 self.durable_waiters.push(waiter);
2172 }
2173 }
2174 }
2175
2176 fn is_durable_through(&self, region_id: RegionId, entry_id: EntryId) -> bool {
2180 self.lowest_pending_entry_id(region_id)
2181 .is_none_or(|pending| pending > entry_id)
2182 }
2183
2184 fn lowest_pending_entry_id(&self, region_id: RegionId) -> Option<EntryId> {
2188 self.sealed
2189 .iter()
2190 .flat_map(|batch| batch.footer.iter())
2191 .find(|entry| entry.region_id == region_id)
2192 .map(|entry| entry.min_entry_id)
2193 .or_else(|| {
2194 self.next_object_seq
2195 .filter(|_| self.open_batch.holds_region(region_id))
2196 .map(|object_seq| entry_id(object_seq, 1))
2197 })
2198 }
2199
2200 fn roll_back(&mut self, error: Arc<Error>) {
2209 let stopped = self.is_stopped();
2210 let failure = || {
2211 if stopped {
2212 ObjectStoreWalStoppedSnafu.build()
2213 } else {
2214 shared(&error)
2215 }
2216 };
2217 for mut batch in std::mem::take(&mut self.sealed) {
2218 self.unindexed.insert(batch.object_seq);
2219 self.keep_releases(&mut batch);
2220 batch.fail(failure);
2221 }
2222 self.reset_open_batch();
2223 self.fail_waiters(failure);
2226 if self.ack_mode == AckMode::Enqueued
2230 || (!self.stop.is_empty() && !self.failed_releases.is_empty())
2231 {
2232 self.stop_error.get_or_insert(error);
2233 }
2234 self.leave_failing("its backlog was dropped");
2235 }
2236
2237 fn keep_releases(&mut self, batch: &mut SealedBatch) {
2240 for release in batch.releases.drain(..) {
2241 self.keep_release(release);
2242 }
2243 }
2244
2245 fn keep_release(&mut self, release: Release) {
2246 let entry_id = self
2247 .failed_releases
2248 .entry(release.region_id)
2249 .or_insert(release.entry_id);
2250 *entry_id = (*entry_id).max(release.entry_id);
2251 }
2252
2253 fn poison(&mut self, error: Error) -> Arc<Error> {
2261 let error = set_terminal(&self.terminal_error, error);
2262 let stopped = self.is_stopped();
2263 let releases_lost = !self.failed_releases.is_empty()
2264 || self.sealed.iter().any(|batch| !batch.releases.is_empty());
2265 let failure = || {
2266 if stopped {
2267 ObjectStoreWalStoppedSnafu.build()
2268 } else {
2269 shared(&error)
2270 }
2271 };
2272 for batch in self.sealed.drain(..) {
2273 batch.fail(failure);
2274 }
2275 self.reset_open_batch();
2276 self.fail_unacknowledged(failure);
2277 if self.ack_mode == AckMode::Enqueued || (!self.stop.is_empty() && releases_lost) {
2280 self.stop_error.get_or_insert(error.clone());
2281 }
2282 self.leave_failing("the store is poisoned");
2283 error
2284 }
2285
2286 fn reset_open_batch(&mut self) {
2289 self.open_batch.reset();
2290 }
2291
2292 fn fail_unacknowledged(&mut self, error: impl Fn() -> Error) {
2295 if let Some((_, response)) = self.take_stalled() {
2296 let _ = response.send(Err(error()));
2297 }
2298 self.fail_waiters(error);
2299 }
2300
2301 fn fail_waiters(&mut self, error: impl Fn() -> Error) {
2303 for pending in self.pending.drain(..) {
2304 let _ = pending.response.send(Err(error()));
2305 }
2306 for waiter in self.durable_waiters.drain(..) {
2307 let _ = waiter.response.send(Err(error()));
2308 }
2309 }
2310
2311 fn handle_wait_durable(
2312 &mut self,
2313 region_id: RegionId,
2314 entry_id: EntryId,
2315 response: oneshot::Sender<Result<()>>,
2316 ) {
2317 if let Some(error) = terminal(&self.terminal_error) {
2318 let _ = response.send(Err(shared(&error)));
2319 return;
2320 }
2321 let durable = {
2322 let catalog = self.catalog.read().unwrap_or_else(PoisonError::into_inner);
2323 catalog.region_max_entry_id(region_id).unwrap_or(0)
2324 };
2325 if entry_id <= durable {
2326 let _ = response.send(Ok(()));
2327 return;
2328 }
2329 if let Some(error) = &self.stop_error {
2332 let _ = response.send(Err(shared(error)));
2333 return;
2334 }
2335 if self.is_durable_through(region_id, entry_id) {
2336 let _ = response.send(Ok(()));
2337 return;
2338 }
2339 self.durable_waiters
2341 .retain(|waiter| !waiter.response.is_closed());
2342 self.durable_waiters.push(DurableWaiter {
2343 region_id,
2344 entry_id,
2345 response,
2346 });
2347 #[cfg(any(test, feature = "testing"))]
2348 self.durability_waits.send_modify(|count| *count += 1);
2349 }
2350
2351 fn handle_withdraw(&mut self, response: oneshot::Sender<()>) {
2363 match &mut self.withdrawal {
2364 Withdrawal::Kept => {}
2365 Withdrawal::Deleting { waiters, .. } => {
2366 waiters.push(response);
2367 return;
2368 }
2369 Withdrawal::Settled => {
2370 let _ = response.send(());
2371 return;
2372 }
2373 }
2374 let started_regions = self
2375 .started_regions
2376 .lock()
2377 .unwrap_or_else(PoisonError::into_inner);
2378 if !started_regions.is_empty()
2379 || terminal(&self.terminal_error).is_some()
2380 || self.failing.is_some()
2381 || !self.issued_entry_ids.is_empty()
2382 || !self.open_batch.is_empty()
2383 || !self.sealed.is_empty()
2384 || !self.creates.is_empty()
2385 || self.stalled.is_some()
2386 || !self.unindexed.is_empty()
2387 || !self.failed_releases.is_empty()
2388 {
2389 let _ = response.send(());
2390 return;
2391 }
2392 let start_object = {
2396 let catalog = self.catalog.read().unwrap_or_else(PoisonError::into_inner);
2397 let mut objects = catalog.objects_in_order();
2398 match (objects.next(), objects.next()) {
2399 (Some((object_seq, [])), None) => Some(object_seq),
2400 _ => None,
2401 }
2402 };
2403 let Some(object_seq) = start_object else {
2404 let _ = response.send(());
2405 return;
2406 };
2407 let path = self.io.object_path(object_seq);
2408 self.terminal_error
2409 .lock()
2410 .unwrap_or_else(PoisonError::into_inner)
2411 .get_or_insert_with(|| Arc::new(WalStartWithdrawnSnafu { path }.build()));
2412 drop(started_regions);
2413 self.deleting.start(object_seq);
2414 let io = self.io.clone();
2415 self.deletes.push(Box::pin(async move {
2416 (object_seq, io.delete(object_seq).await)
2417 }));
2418 self.withdrawal = Withdrawal::Deleting {
2419 object_seq,
2420 waiters: vec![response],
2421 };
2422 }
2423
2424 fn handle_obsolete(
2428 &mut self,
2429 region_id: RegionId,
2430 entry_id: EntryId,
2431 watermark: EntryId,
2432 response: oneshot::Sender<Result<()>>,
2433 ) {
2434 let result = self.raise_sequence_floor(region_id, entry_id).map(|_| {
2435 record_obsolete(
2436 &self.obsolete_entry_ids,
2437 &self.catalog,
2438 region_id,
2439 watermark,
2440 );
2441 });
2442 if result.is_ok() {
2443 self.update_reclaim_boundary();
2444 self.collect();
2445 }
2446 let _ = response.send(result);
2447 }
2448
2449 fn handle_release(
2461 &mut self,
2462 region_id: RegionId,
2463 full: bool,
2464 response: oneshot::Sender<Result<()>>,
2465 ) {
2466 if self.is_stopped() {
2467 let _ = response.send(Err(ObjectStoreWalStoppedSnafu.build()));
2468 return;
2469 }
2470 if let Some(error) = terminal(&self.terminal_error) {
2471 let _ = response.send(Err(shared(&error)));
2472 return;
2473 }
2474 let entry_id = if full {
2475 EntryId::MAX
2476 } else {
2477 self.issued_entry_ids.get(®ion_id).copied().unwrap_or(0)
2478 };
2479 record_obsolete(&self.obsolete_entry_ids, &self.catalog, region_id, entry_id);
2480 self.update_reclaim_boundary();
2481 self.collect();
2482 let release = Release {
2483 region_id,
2484 entry_id,
2485 };
2486 if let Some(failing) = &mut self.failing {
2490 let _ = response.send(Err(failing.reject()));
2491 self.keep_release(release);
2492 return;
2493 }
2494 if self.seal(vec![release])
2495 && let Some(batch) = self.sealed.back_mut()
2496 {
2497 batch.release_waiters.push(response);
2498 return;
2499 }
2500 if let Some(error) = terminal(&self.terminal_error) {
2502 let _ = response.send(Err(shared(&error)));
2503 }
2504 }
2505
2506 fn update_reclaim_boundary(&mut self) {
2509 self.reclaim_boundary = self
2510 .catalog
2511 .read()
2512 .unwrap_or_else(PoisonError::into_inner)
2513 .reclaim_boundary(self.reclaim_boundary);
2514 }
2515
2516 fn collect(&mut self) {
2519 self.failed_deletes.clear();
2520 self.start_deletes();
2521 }
2522
2523 fn start_deletes(&mut self) {
2528 if self.is_stopped() || terminal(&self.terminal_error).is_some() {
2529 return;
2530 }
2531 let free = MAX_IN_FLIGHT_DELETES.saturating_sub(self.deletes.len());
2532 let boundary = self.durable_reclaim_boundary;
2533 let candidates = {
2534 let catalog = self.catalog.read().unwrap_or_else(PoisonError::into_inner);
2535 catalog
2536 .objects_in_order()
2537 .map(|(object_seq, _)| object_seq)
2538 .take_while(|object_seq| *object_seq < boundary)
2539 .chain(self.unindexed.range(..boundary).copied())
2540 .filter(|object_seq| {
2541 !self.deleting.contains(*object_seq)
2542 && !self.failed_deletes.contains(object_seq)
2543 && !self.creating.contains(object_seq)
2544 })
2545 .take(free)
2546 .collect::<Vec<_>>()
2547 };
2548 for object_seq in candidates {
2549 self.deleting.start(object_seq);
2550 let io = self.io.clone();
2551 self.deletes.push(Box::pin(async move {
2552 (object_seq, io.delete(object_seq).await)
2553 }));
2554 }
2555 }
2556
2557 fn on_delete_completed(&mut self, object_seq: u64, result: Result<()>) {
2561 if let Withdrawal::Deleting {
2562 object_seq: withdrawn,
2563 waiters,
2564 } = &mut self.withdrawal
2565 && *withdrawn == object_seq
2566 {
2567 let path = self.io.object_path(object_seq);
2568 match result {
2569 Ok(()) => {
2570 let mut catalog = self.catalog.write().unwrap_or_else(PoisonError::into_inner);
2571 if let Some(footer) = catalog.remove_object(object_seq) {
2572 METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.dec();
2573 METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.sub(object_len(&footer) as i64);
2574 }
2575 info!(
2576 "Deleted the start object {path} of the object store WAL, whose prefix held no object, after refusing a region without its history"
2577 );
2578 }
2579 Err(error) => warn!(
2580 error;
2581 "Failed to delete the start object {path} of the object store WAL after refusing a region without its history; the next open passes regions that never flushed"
2582 ),
2583 }
2584 for waiter in waiters.drain(..) {
2585 let _ = waiter.send(());
2586 }
2587 self.withdrawal = Withdrawal::Settled;
2588 self.deleting.finish(object_seq);
2589 return;
2590 }
2591 match result {
2592 Ok(()) => {
2593 self.unindexed.remove(&object_seq);
2594 let mut catalog = self.catalog.write().unwrap_or_else(PoisonError::into_inner);
2595 let footer = catalog.remove_object(object_seq);
2596 if let Some(footer) = &footer {
2597 METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.dec();
2598 METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.sub(object_len(footer) as i64);
2599 }
2600 let mut wal_holes = self
2601 .wal_holes
2602 .lock()
2603 .unwrap_or_else(PoisonError::into_inner);
2604 for entry in footer.iter().flatten() {
2605 if let Some(holes) = wal_holes.get_mut(&entry.region_id) {
2606 holes.remove(&object_seq);
2607 }
2608 }
2609 METRIC_OBJECT_STORE_WAL_DELETED_OBJECTS_TOTAL.inc();
2610 }
2611 Err(error) => {
2612 warn!(
2613 error;
2614 "Failed to delete WAL object {} (sequence {}), it is deleted again at the next collection",
2615 self.io.object_path(object_seq),
2616 object_seq
2617 );
2618 METRIC_OBJECT_STORE_WAL_FAILED_DELETES_TOTAL.inc();
2619 self.failed_deletes.insert(object_seq);
2620 }
2621 }
2622 self.start_deletes();
2623 self.deleting.finish(object_seq);
2624 }
2625
2626 fn raise_sequence_floor(&mut self, region_id: RegionId, entry_id: EntryId) -> Result<()> {
2631 if self.is_stopped() {
2633 return Ok(());
2634 }
2635 if let Some(error) = terminal(&self.terminal_error) {
2636 return Err(shared(&error));
2637 }
2638 if self.ack_mode == AckMode::Enqueued
2644 && entry_id <= self.issued_entry_ids.get(®ion_id).copied().unwrap_or(0)
2645 {
2646 return Ok(());
2647 }
2648 let sequence_floor = sequence_floor(entry_id);
2649 let Some(next_object_seq) = self.next_object_seq else {
2650 return Ok(());
2651 };
2652 if next_object_seq >= sequence_floor {
2653 return Ok(());
2654 }
2655 ensure!(
2656 self.open_batch.is_empty(),
2657 WalObjectSequenceUnsettledSnafu {
2658 object_seq: next_object_seq,
2659 }
2660 );
2661 if sequence_floor < OBJECT_SEQ_LIMIT {
2662 self.next_object_seq = Some(sequence_floor);
2663 Ok(())
2664 } else {
2665 self.next_object_seq = None;
2666 let error = self.poison(
2667 WalObjectSequenceExhaustedSnafu {
2668 last_object_seq: OBJECT_SEQ_LIMIT - 1,
2669 }
2670 .build(),
2671 );
2672 Err(shared(&error))
2673 }
2674 }
2675
2676 fn handle_stop(&mut self, response: oneshot::Sender<Result<()>>) {
2683 self.stop.push(response);
2684 if let Some((_, response)) = self.take_stalled() {
2685 let _ = response.send(Err(ObjectStoreWalStoppedSnafu.build()));
2686 }
2687 match self.ack_mode {
2688 AckMode::Durable => {
2689 self.reset_open_batch();
2690 for pending in self.pending.drain(..) {
2691 let _ = pending
2692 .response
2693 .send(Err(ObjectStoreWalStoppedSnafu.build()));
2694 }
2695 if let Some(index) = self
2696 .sealed
2697 .iter()
2698 .position(|batch| matches!(batch.state, CreateState::Pending))
2699 {
2700 for mut batch in self.sealed.split_off(index) {
2701 self.keep_releases(&mut batch);
2702 batch.fail(|| ObjectStoreWalStoppedSnafu.build());
2703 }
2704 }
2705 self.seal(Vec::new());
2706 }
2707 AckMode::Enqueued => {
2708 self.flush_open_batch();
2709 self.start_creates();
2710 }
2711 }
2712 }
2713
2714 fn finish_stop(&mut self) -> bool {
2718 if self.stop.is_empty()
2719 || !self.sealed.is_empty()
2720 || !self.creates.is_empty()
2721 || !self.deletes.is_empty()
2722 {
2723 return false;
2724 }
2725 for waiter in self.durable_waiters.drain(..) {
2726 let _ = waiter
2727 .response
2728 .send(Err(ObjectStoreWalStoppedSnafu.build()));
2729 }
2730 let error = self.stop_error.take();
2731 for response in self.stop.drain(..) {
2732 let _ = response.send(error.as_ref().map_or(Ok(()), |error| Err(shared(error))));
2733 }
2734 true
2735 }
2736
2737 #[cfg(any(test, feature = "testing"))]
2738 fn handle_seal(&mut self, response: oneshot::Sender<Result<()>>) {
2739 if self.is_stopped() {
2740 let _ = response.send(Err(ObjectStoreWalStoppedSnafu.build()));
2741 return;
2742 }
2743 if self.flush_open_batch()
2744 && let Some(batch) = self.sealed.back_mut()
2745 {
2746 batch.seal_waiters.push(response);
2747 return;
2748 }
2749 let result = terminal(&self.terminal_error).map_or(Ok(()), |error| Err(shared(&error)));
2750 let _ = response.send(result);
2751 }
2752
2753 #[cfg(any(test, feature = "testing"))]
2756 fn handle_crash(&mut self) {
2757 let stopped = || ObjectStoreWalStoppedSnafu.build();
2758 for batch in self.sealed.drain(..) {
2759 batch.fail(stopped);
2760 }
2761 self.reset_open_batch();
2762 self.fail_unacknowledged(stopped);
2763 for response in self.stop.drain(..) {
2764 let _ = response.send(Err(stopped()));
2765 }
2766 }
2767
2768 fn is_stopped(&self) -> bool {
2769 self.stopped.load(Ordering::Acquire)
2770 }
2771}
2772
2773impl Drop for Actor {
2774 fn drop(&mut self) {
2778 self.deleting.abandon_all();
2779 self.take_stalled();
2780 self.leave_failing("its actor exited");
2781 #[cfg(test)]
2782 LIVE_ACTORS.fetch_sub(1, Ordering::SeqCst);
2783 }
2784}
2785
2786#[cfg(test)]
2789static LIVE_ACTORS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
2790
2791fn terminal(terminal_error: &TerminalError) -> Option<Arc<Error>> {
2792 terminal_error
2793 .lock()
2794 .unwrap_or_else(PoisonError::into_inner)
2795 .clone()
2796}
2797
2798fn set_terminal(terminal_error: &TerminalError, error: Error) -> Arc<Error> {
2801 let mut terminal_error = terminal_error
2802 .lock()
2803 .unwrap_or_else(PoisonError::into_inner);
2804 if terminal_error.is_none() {
2805 METRIC_OBJECT_STORE_WAL_POISONED_TOTAL.inc();
2806 }
2807 terminal_error
2808 .get_or_insert_with(|| Arc::new(error))
2809 .clone()
2810}
2811
2812fn record_create(result: &Result<PutResult>, sealed_at: Option<Instant>) {
2817 match result {
2818 Ok(_) => {
2819 if let Some(sealed_at) = sealed_at {
2820 METRIC_OBJECT_STORE_WAL_SEAL_TO_DURABLE_SECONDS
2821 .observe(sealed_at.elapsed().as_secs_f64());
2822 }
2823 METRIC_OBJECT_STORE_WAL_CREATED_OBJECTS_TOTAL.inc();
2824 }
2825 Err(Error::WalObjectStore { .. }) => METRIC_OBJECT_STORE_WAL_CREATE_FAILURES_TOTAL.inc(),
2826 Err(Error::WalObjectConflict { .. }) => {
2827 METRIC_OBJECT_STORE_WAL_CREATE_CONFLICTS_TOTAL.inc()
2828 }
2829 Err(_) => {}
2830 }
2831}
2832
2833fn publish_indexed(catalog: &ObjectCatalog) {
2835 let (objects, bytes) = catalog
2836 .objects_in_order()
2837 .fold((0, 0), |(objects, bytes), (_, footer)| {
2838 (objects + 1, bytes + object_len(footer))
2839 });
2840 METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.set(objects);
2841 METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.set(bytes as i64);
2842}
2843
2844fn requires_intervention(error: &Error) -> bool {
2862 match error {
2863 Error::WalObjectStore {
2864 operation, error, ..
2865 } => match error.kind() {
2866 ErrorKind::PermissionDenied | ErrorKind::ConfigInvalid => true,
2867 ErrorKind::NotFound => *operation == "write",
2868 _ => false,
2869 },
2870 _ => false,
2871 }
2872}
2873
2874fn shared(error: &Arc<Error>) -> Error {
2876 ObjectStoreWalSnafu.into_error(error.clone())
2877}
2878
2879async fn is_collected(
2885 catalog: &RwLock<ObjectCatalog>,
2886 deleting: &DeletingObjects,
2887 object_seq: u64,
2888) -> bool {
2889 if let Some(mut settled) = deleting.attempt(object_seq) {
2890 let _ = settled.wait_for(|settled| *settled).await;
2891 }
2892 !catalog
2893 .read()
2894 .unwrap_or_else(PoisonError::into_inner)
2895 .contains_object(object_seq)
2896}
2897
2898async fn fetch_segment(
2904 io: &dyn WalObjectIo,
2905 object_seq: u64,
2906 entry: &FooterEntry,
2907) -> Result<Vec<Record>> {
2908 let fetch = || io.get_range(object_seq, entry.segment_offset, entry.segment_len);
2909 if let Ok(records) = decode_segment(&fetch().await?, entry) {
2910 return Ok(records);
2911 }
2912 decode_segment(&fetch().await?, entry).with_context(|_| InvalidWalObjectSnafu {
2913 path: io.object_path(object_seq),
2914 })
2915}
2916
2917fn try_reserve_prefetch_bytes(
2920 budget: &Arc<Semaphore>,
2921 entry: &FooterEntry,
2922) -> Option<OwnedSemaphorePermit> {
2923 let records = u64::from(entry.entry_count) * std::mem::size_of::<Record>() as u64;
2924 let bytes = u32::try_from(entry.segment_len.checked_add(records)?).ok()?;
2925 budget.clone().try_acquire_many_owned(bytes).ok()
2926}
2927
2928fn spawn_fetch_segment(
2935 io: Arc<dyn WalObjectIo>,
2936 object_seq: u64,
2937 entry: FooterEntry,
2938 permit: Option<OwnedSemaphorePermit>,
2939) -> impl Future<Output = (Result<Vec<Record>>, Option<OwnedSemaphorePermit>)> + Send + 'static {
2940 let task_io = io.clone();
2941 let task = AbortOnDropHandle::new(common_runtime::spawn_global(async move {
2942 let fetched = fetch_segment(task_io.as_ref(), object_seq, &entry).await;
2943 (fetched, permit)
2944 }));
2945 async move {
2946 match task.await {
2947 Ok(fetched) => fetched,
2948 Err(error) => (
2949 Err(error).context(WalSegmentFetchSnafu {
2950 path: io.object_path(object_seq),
2951 }),
2952 None,
2953 ),
2954 }
2955 }
2956}
2957
2958fn record_skipped_segment(
2964 catalog: &RwLock<ObjectCatalog>,
2965 wal_holes: &WalHoles,
2966 io: &dyn WalObjectIo,
2967 object_seq: u64,
2968 entry: &FooterEntry,
2969 error: &Error,
2970) {
2971 let catalog = catalog.read().unwrap_or_else(PoisonError::into_inner);
2972 if !catalog.contains_object(object_seq) {
2973 debug!(
2974 "Skipped WAL object {} (sequence {}), collected after the read listed it: {error}",
2975 io.object_path(object_seq),
2976 object_seq
2977 );
2978 return;
2979 }
2980 let hole = WalHole {
2981 path: io.object_path(object_seq),
2982 object_seq,
2983 min_entry_id: entry.min_entry_id,
2984 max_entry_id: entry.max_entry_id,
2985 };
2986 warn!(
2987 error;
2988 "Skipped a corrupted WAL segment of region {}, object {} (sequence {}), entry ids {}..={}",
2989 entry.region_id,
2990 hole.path,
2991 object_seq,
2992 hole.min_entry_id,
2993 hole.max_entry_id
2994 );
2995 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.inc();
2996 wal_holes
2997 .lock()
2998 .unwrap_or_else(PoisonError::into_inner)
2999 .entry(entry.region_id)
3000 .or_default()
3001 .entry(object_seq)
3002 .or_insert(hole);
3003}
3004
3005fn encode_batch(
3009 header: Header,
3010 entries: Vec<Entry>,
3011 releases: &BTreeMap<RegionId, EntryId>,
3012) -> Result<EncodedObject> {
3013 let release_record = (!releases.is_empty()).then(|| Record {
3014 region_id: RELEASE_REGION_ID,
3015 entry_id: entry_id(header.object_seq, 1),
3016 payload: encode_releases(releases),
3017 });
3018 let records = entries
3019 .into_iter()
3020 .map(|entry| Record {
3021 region_id: entry.region_id(),
3022 entry_id: entry.entry_id(),
3023 payload: Bytes::from(entry.into_bytes()),
3024 })
3025 .chain(release_record)
3026 .collect::<Vec<_>>();
3027 encode_object(header, &records)
3028}
3029
3030#[derive(Debug)]
3032struct Recovered {
3033 catalog: ObjectCatalog,
3034 next_object_seq: u64,
3036 durable_entry_ids: HashMap<RegionId, EntryId>,
3037 tip: Option<ChainLink>,
3039 max_epoch: u64,
3041 reclaim_boundary: u64,
3043 unindexed: Vec<u64>,
3045 released_entry_ids: HashMap<RegionId, EntryId>,
3048 recovered_objects: u64,
3050}
3051
3052struct FetchedObjects {
3057 objects: Vec<FetchedObject>,
3058 vanished: Vec<(u64, Error)>,
3059}
3060
3061struct FetchedObject {
3064 object: ListedObject,
3065 header: Header,
3066 footer: Vec<FooterEntry>,
3067 releases: Option<Bytes>,
3068}
3069
3070async fn recover(io: &dyn WalObjectIo) -> Result<Recovered> {
3080 let mut fetched = fetch_footers(io, io.list_ahead(), RECOVERY_CONCURRENCY).await?;
3081 let releases = fetched
3082 .objects
3083 .iter_mut()
3084 .filter_map(|fetched| Some((fetched.object.object_seq, fetched.releases.take()?)))
3085 .collect();
3086 let mut recovered = finish_recovery(fetched)?;
3087 recovered.released_entry_ids = read_releases(io, &recovered.catalog, releases).await?;
3088 Ok(recovered)
3089}
3090
3091async fn read_releases(
3112 io: &dyn WalObjectIo,
3113 catalog: &ObjectCatalog,
3114 mut fetched: HashMap<u64, Bytes>,
3115) -> Result<HashMap<RegionId, EntryId>> {
3116 let segments = catalog
3117 .objects_in_order()
3118 .filter_map(|(object_seq, footer)| {
3119 footer
3120 .iter()
3121 .find(|entry| entry.region_id == RELEASE_REGION_ID)
3122 .map(|entry| (object_seq, entry.clone(), fetched.remove(&object_seq)))
3123 })
3124 .collect::<Vec<_>>();
3125 let releases = futures::stream::iter(segments)
3126 .map(|(object_seq, entry, fetched)| async move {
3127 let bytes = match fetched {
3128 Some(bytes) => bytes,
3129 None => {
3130 io.get_range(object_seq, entry.segment_offset, entry.segment_len)
3131 .await?
3132 }
3133 };
3134 decode_segment(&bytes, &entry)
3135 .and_then(|records| decode_releases(&records[0].payload))
3136 .with_context(|_| InvalidWalObjectSnafu {
3137 path: io.object_path(object_seq),
3138 })
3139 })
3140 .buffered(RECOVERY_CONCURRENCY)
3141 .try_collect::<Vec<_>>()
3142 .await?;
3143 let mut released_entry_ids = HashMap::new();
3144 for release in releases.into_iter().flatten() {
3145 let entry_id = released_entry_ids
3146 .entry(release.region_id)
3147 .or_insert(release.entry_id);
3148 *entry_id = (*entry_id).max(release.entry_id);
3149 }
3150 Ok(released_entry_ids)
3151}
3152
3153fn finish_recovery(fetched: FetchedObjects) -> Result<Recovered> {
3159 let FetchedObjects { objects, vanished } = fetched;
3160 let recovered_objects = objects.len() as u64;
3161 let headers = objects
3162 .iter()
3163 .map(|fetched| (fetched.object.object_seq, &fetched.header))
3164 .collect::<BTreeMap<_, _>>();
3165 let chain = select_chain(&headers);
3166 ensure!(
3171 headers.is_empty() || !chain.is_empty(),
3172 CorruptedWalObjectSnafu {
3173 reason: format!(
3174 "no object among {} present objects completes a chain",
3175 headers.len()
3176 ),
3177 }
3178 );
3179 let tip = chain.last().map(|object_seq| ChainLink {
3180 object_seq: *object_seq,
3181 epoch: headers[object_seq].epoch,
3182 });
3183 let max_epoch = headers
3184 .values()
3185 .map(|header| header.epoch)
3186 .max()
3187 .unwrap_or(0);
3188 let last_listed = headers
3189 .keys()
3190 .copied()
3191 .chain(vanished.iter().map(|(object_seq, _)| *object_seq))
3192 .max();
3193 let after_listed = match last_listed {
3194 None => 0,
3195 Some(last_object_seq) => last_object_seq
3196 .checked_add(1)
3197 .context(WalObjectSequenceExhaustedSnafu { last_object_seq })?,
3198 };
3199 let reclaim_boundary = tip.map_or(0, |tip| headers[&tip.object_seq].reclaim_boundary);
3200 if let Some((_, error)) = vanished
3201 .into_iter()
3202 .find(|(object_seq, _)| *object_seq >= reclaim_boundary)
3203 {
3204 return Err(error);
3205 }
3206 let chain = chain.into_iter().collect::<HashSet<_>>();
3207
3208 let mut catalog = ObjectCatalog::default();
3209 let mut unindexed = Vec::new();
3210 for FetchedObject { object, footer, .. } in objects {
3211 if chain.contains(&object.object_seq) {
3212 catalog
3213 .insert_object(object.object_seq, footer)
3214 .with_context(|_| InvalidWalObjectSnafu { path: object.path })?;
3215 } else {
3216 unindexed.push(object.object_seq);
3217 }
3218 }
3219 let next_object_seq = catalog.next_object_seq()?.max(after_listed);
3220 ensure!(
3221 next_object_seq < OBJECT_SEQ_LIMIT,
3222 WalObjectSequenceExhaustedSnafu {
3223 last_object_seq: next_object_seq - 1,
3224 }
3225 );
3226 let durable_entry_ids = durable_entry_ids(&catalog);
3227 Ok(Recovered {
3228 catalog,
3229 next_object_seq,
3230 durable_entry_ids,
3231 tip,
3232 max_epoch,
3233 reclaim_boundary,
3234 unindexed,
3235 released_entry_ids: HashMap::new(),
3236 recovered_objects,
3237 })
3238}
3239
3240fn select_chain(headers: &BTreeMap<u64, &Header>) -> Vec<u64> {
3256 let mut candidates = headers.iter().collect::<Vec<_>>();
3257 candidates.sort_unstable_by_key(|(object_seq, header)| Reverse((header.epoch, **object_seq)));
3258 candidates
3259 .into_iter()
3260 .find_map(|(object_seq, _)| chain_ending_at(headers, *object_seq))
3261 .unwrap_or_default()
3262}
3263
3264fn chain_ending_at(headers: &BTreeMap<u64, &Header>, tip: u64) -> Option<Vec<u64>> {
3267 let reclaim_boundary = headers[&tip].reclaim_boundary;
3268 let mut chain = vec![tip];
3269 let mut header = headers[&tip];
3270 while let Some(link) = header.prev {
3271 if link.object_seq >= header.object_seq {
3272 return None;
3273 }
3274 if link.object_seq < reclaim_boundary {
3275 break;
3276 }
3277 header = headers
3278 .get(&link.object_seq)
3279 .filter(|prev| prev.epoch == link.epoch)?;
3280 chain.push(link.object_seq);
3281 }
3282 chain.reverse();
3283 Some(chain)
3284}
3285
3286async fn start_epoch(
3303 io: &dyn WalObjectIo,
3304 mut object_seq: u64,
3305 tip: Option<ChainLink>,
3306 reclaim_boundary: u64,
3307) -> Result<(ChainLink, Vec<u64>)> {
3308 let mut moved_past = Vec::new();
3309 loop {
3310 ensure!(
3311 object_seq < OBJECT_SEQ_LIMIT,
3312 WalObjectSequenceExhaustedSnafu {
3313 last_object_seq: OBJECT_SEQ_LIMIT - 1,
3314 }
3315 );
3316 let epoch = object_seq + 1;
3317 let header = Header {
3318 object_seq,
3319 epoch,
3320 prev: tip,
3321 reclaim_boundary,
3322 };
3323 let created = io
3324 .put_if_absent(object_seq, encode_object(header, &[])?.bytes)
3325 .await;
3326 record_create(&created, None);
3327 match created {
3328 Ok(PutResult::Created) => return Ok((ChainLink { object_seq, epoch }, moved_past)),
3329 Ok(PutResult::AlreadyPresent) => {
3330 return UnconfirmedWalEpochStartSnafu {
3331 path: io.object_path(object_seq),
3332 epoch,
3333 }
3334 .fail();
3335 }
3336 Err(error @ Error::WalObjectConflict { .. }) => {
3337 if epoch_of(io, object_seq).await? >= epoch {
3338 return Err(error);
3339 }
3340 moved_past.push(object_seq);
3341 object_seq += 1;
3342 }
3343 Err(error) => return Err(error),
3344 }
3345 }
3346}
3347
3348async fn epoch_of(io: &dyn WalObjectIo, object_seq: u64) -> Result<u64> {
3349 let head = io.get_range(object_seq, 0, HEADER_LEN as u64).await?;
3350 let header = decode_header(&head).with_context(|_| InvalidWalObjectSnafu {
3351 path: io.object_path(object_seq),
3352 })?;
3353 Ok(header.epoch)
3354}
3355
3356async fn stale_conflict(
3361 io: &dyn WalObjectIo,
3362 object_seq: u64,
3363 epoch: u64,
3364 conflict: Error,
3365) -> Result<PutResult> {
3366 let existing_epoch = epoch_of(io, object_seq).await?;
3367 if existing_epoch >= epoch {
3368 return Err(conflict);
3369 }
3370 StaleWalObjectSnafu {
3371 path: io.object_path(object_seq),
3372 existing_epoch,
3373 epoch,
3374 }
3375 .fail()
3376}
3377
3378async fn fetch_footers(
3384 io: &dyn WalObjectIo,
3385 objects: impl Stream<Item = Result<ListedObject>>,
3386 concurrency: usize,
3387) -> Result<FetchedObjects> {
3388 let outcomes = objects
3389 .map_ok(|object| async move {
3390 match fetch_footer(io, &object).await {
3391 Ok((header, footer, releases)) => Ok(Ok(FetchedObject {
3392 object,
3393 header,
3394 footer,
3395 releases,
3396 })),
3397 Err(error) if is_not_found(&error) => Ok(Err((object.object_seq, error))),
3398 Err(error) => Err(error),
3399 }
3400 })
3401 .try_buffer_unordered(concurrency)
3402 .try_collect::<Vec<_>>()
3403 .await?;
3404 let mut objects = Vec::with_capacity(outcomes.len());
3405 let mut vanished = Vec::new();
3406 for outcome in outcomes {
3407 match outcome {
3408 Ok(object) => objects.push(object),
3409 Err(gone) => vanished.push(gone),
3410 }
3411 }
3412 objects.sort_unstable_by_key(|fetched| fetched.object.object_seq);
3413 Ok(FetchedObjects { objects, vanished })
3414}
3415
3416fn is_not_found(error: &Error) -> bool {
3417 matches!(error, Error::WalObjectStore { error, .. } if error.kind() == object_store::ErrorKind::NotFound)
3418}
3419
3420async fn fetch_footer(
3431 io: &dyn WalObjectIo,
3432 object: &ListedObject,
3433) -> Result<(Header, Vec<FooterEntry>, Option<Bytes>)> {
3434 let ListedObject {
3435 object_seq, size, ..
3436 } = *object;
3437 let invalid = |source: Error| {
3438 InvalidWalObjectSnafu {
3439 path: object.path.clone(),
3440 }
3441 .into_error(source)
3442 };
3443 let object_len = usize::try_from(size)
3444 .ok()
3445 .filter(|len| *len >= MIN_OBJECT_LEN)
3446 .with_context(|| CorruptedWalObjectSnafu {
3447 reason: format!(
3448 "truncated object, expected at least {MIN_OBJECT_LEN} bytes, actual {size}"
3449 ),
3450 })
3451 .map_err(invalid)?;
3452
3453 let window = object_len.min(RECOVERY_TAIL_WINDOW);
3454 let tail_start = object_len - window;
3455 let (head, tail) = if tail_start == 0 {
3456 let bytes = io.get(object_seq).await?;
3457 (bytes.clone(), bytes)
3458 } else {
3459 futures::try_join!(
3460 io.get_range(object_seq, 0, HEADER_LEN as u64),
3461 io.get_range(object_seq, tail_start as u64, window as u64)
3462 )?
3463 };
3464 if head.len() < HEADER_LEN || tail.len() != window {
3465 return Err(invalid(
3466 CorruptedWalObjectSnafu {
3467 reason: format!(
3468 "object holds fewer bytes than the listed {size}, head {} bytes, tail {} bytes",
3469 head.len(),
3470 tail.len()
3471 ),
3472 }
3473 .build(),
3474 ));
3475 }
3476
3477 let (header, trailer, footer_range) =
3478 locate_footer(object_seq, object_len, &head, &tail).map_err(invalid)?;
3479 let footer = if footer_range.start >= tail_start {
3480 tail.slice(footer_range.start - tail_start..footer_range.end - tail_start)
3481 } else {
3482 io.get_range(
3483 object_seq,
3484 footer_range.start as u64,
3485 footer_range.len() as u64,
3486 )
3487 .await?
3488 };
3489 let footer = decode_footer(&footer, trailer).map_err(invalid)?;
3490 verify_segment_ranges(&footer, footer_range.start).map_err(invalid)?;
3491 let releases = footer
3493 .iter()
3494 .find(|entry| entry.region_id == RELEASE_REGION_ID)
3495 .filter(|entry| {
3496 entry.segment_offset >= tail_start as u64 && entry.segment_len <= MAX_KEPT_RELEASE_BYTES
3497 })
3498 .map(|entry| {
3499 let start = entry.segment_offset as usize - tail_start;
3500 Bytes::copy_from_slice(&tail[start..start + entry.segment_len as usize])
3501 });
3502 Ok((header, footer, releases))
3503}
3504
3505fn locate_footer(
3509 object_seq: u64,
3510 object_len: usize,
3511 head: &[u8],
3512 tail: &[u8],
3513) -> Result<(Header, FixedTrailer, Range<usize>)> {
3514 let header = decode_header(head)?;
3515 ensure!(
3516 header.object_seq == object_seq,
3517 CorruptedWalObjectSnafu {
3518 reason: format!(
3519 "header sequence {} does not match key sequence {object_seq}",
3520 header.object_seq
3521 ),
3522 }
3523 );
3524 let trailer = decode_trailer(&tail[tail.len() - TRAILER_LEN..])?;
3525 let footer_range = footer_range(trailer, object_len)?;
3526 Ok((header, trailer, footer_range))
3527}
3528
3529fn durable_entry_ids(catalog: &ObjectCatalog) -> HashMap<RegionId, EntryId> {
3530 let mut entry_ids = HashMap::new();
3531 for (_, footer) in catalog.objects_in_order() {
3532 for entry in footer {
3533 entry_ids
3534 .entry(entry.region_id)
3535 .and_modify(|current: &mut EntryId| *current = (*current).max(entry.max_entry_id))
3536 .or_insert(entry.max_entry_id);
3537 }
3538 }
3539 entry_ids
3540}
3541
3542#[async_trait::async_trait]
3544pub(crate) trait WalObjectIo: Send + Sync {
3545 async fn put_if_absent(&self, object_seq: u64, content: Bytes) -> Result<PutResult>;
3546
3547 async fn get(&self, object_seq: u64) -> Result<Bytes>;
3548
3549 async fn get_range(&self, object_seq: u64, offset: u64, len: u64) -> Result<Bytes>;
3550
3551 async fn delete(&self, object_seq: u64) -> Result<()>;
3552
3553 async fn list(&self) -> Result<Vec<ListedObject>>;
3554
3555 fn list_ahead(&self) -> BoxStream<'_, Result<ListedObject>> {
3558 Box::pin(try_stream! {
3559 for object in self.list().await? {
3560 yield object;
3561 }
3562 })
3563 }
3564
3565 fn object_path(&self, object_seq: u64) -> String;
3566
3567 fn storage(&self) -> String;
3569}
3570
3571#[async_trait::async_trait]
3572impl WalObjectIo for ObjectStoreIo {
3573 async fn put_if_absent(&self, object_seq: u64, content: Bytes) -> Result<PutResult> {
3574 ObjectStoreIo::put_if_absent(self, object_seq, content).await
3575 }
3576
3577 async fn get(&self, object_seq: u64) -> Result<Bytes> {
3578 ObjectStoreIo::get(self, object_seq).await
3579 }
3580
3581 async fn get_range(&self, object_seq: u64, offset: u64, len: u64) -> Result<Bytes> {
3582 ObjectStoreIo::get_range(self, object_seq, offset, len).await
3583 }
3584
3585 async fn delete(&self, object_seq: u64) -> Result<()> {
3586 ObjectStoreIo::delete(self, object_seq).await
3587 }
3588
3589 async fn list(&self) -> Result<Vec<ListedObject>> {
3590 ObjectStoreIo::list(self).await
3591 }
3592
3593 fn list_ahead(&self) -> BoxStream<'_, Result<ListedObject>> {
3594 ObjectStoreIo::list_ahead(self)
3595 }
3596
3597 fn object_path(&self, object_seq: u64) -> String {
3598 ObjectStoreIo::object_path(self, object_seq)
3599 }
3600
3601 fn storage(&self) -> String {
3602 ObjectStoreIo::storage(self)
3603 }
3604}
3605
3606#[cfg(test)]
3607mod tests {
3608 use std::sync::atomic::AtomicUsize;
3609
3610 use common_base::readable_size::ReadableSize;
3611 use common_error::ext::{ErrorExt, RetryHint};
3612 use common_error::status_code::StatusCode;
3613 use futures::FutureExt;
3614 use object_store::layers::mock::{self, MockLayerBuilder, oio};
3615 use object_store::services::Memory;
3616 use object_store::util::with_retry_layers;
3617 use store_api::logstore::entry::{MultiplePartEntry, MultiplePartHeader};
3618 use tokio::time::timeout;
3619
3620 use super::*;
3621 use crate::error::{WalObjectConflictSnafu, WalObjectStoreSnafu};
3622 use crate::object_store_wal::batch::{POSITION_LIMIT, entry_id};
3623 use crate::object_store_wal::format::{FOOTER_ENTRY_LEN, decode_object};
3624
3625 const PREFIX: &str = "wal/datanodes/1/epochs/2";
3626 const WAIT: Duration = Duration::from_secs(30);
3627 static SERIALIZED: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
3631
3632 fn memory_store() -> ObjectStore {
3633 ObjectStore::new(Memory::default()).unwrap()
3634 }
3635
3636 fn config(flush_interval: Duration, max_batch_bytes: u64) -> ObjectStoreWalConfig {
3637 ObjectStoreWalConfig {
3638 storage_provider: String::new(),
3639 flush_interval,
3640 max_batch_bytes: ReadableSize(max_batch_bytes),
3641 ..Default::default()
3642 }
3643 }
3644
3645 fn enqueued(config: ObjectStoreWalConfig) -> ObjectStoreWalConfig {
3647 ObjectStoreWalConfig {
3648 ack_mode: AckMode::Enqueued,
3649 ..config
3650 }
3651 }
3652
3653 fn on_corruption(
3654 on_corrupted_segment: CorruptedSegmentAction,
3655 config: ObjectStoreWalConfig,
3656 ) -> ObjectStoreWalConfig {
3657 ObjectStoreWalConfig {
3658 on_corrupted_segment,
3659 ..config
3660 }
3661 }
3662
3663 fn eager() -> ObjectStoreWalConfig {
3665 config(Duration::from_secs(3600), 1)
3666 }
3667
3668 fn manual() -> ObjectStoreWalConfig {
3670 config(Duration::from_secs(3600), u64::MAX)
3671 }
3672
3673 async fn open(
3674 object_store: ObjectStore,
3675 config: &ObjectStoreWalConfig,
3676 ) -> Arc<ObjectStoreLogStore> {
3677 ObjectStoreLogStore::try_new(object_store, config, 1, 2)
3678 .await
3679 .unwrap()
3680 }
3681
3682 fn region(number: u32) -> RegionId {
3683 RegionId::new(1, number)
3684 }
3685
3686 fn provider(region_id: RegionId) -> Provider {
3687 Provider::object_store_provider(region_id, PREFIX.to_string())
3688 }
3689
3690 fn latest(store: &ObjectStoreLogStore, region_id: RegionId) -> EntryId {
3691 store.latest_entry_id(&provider(region_id)).unwrap()
3692 }
3693
3694 fn id(object_seq: u64, position: u64) -> EntryId {
3696 entry_id(object_seq, position)
3697 }
3698
3699 fn entry(store: &ObjectStoreLogStore, region_id: RegionId, data: &str) -> Entry {
3700 store
3701 .entry(data.as_bytes().to_vec(), 0, region_id, &provider(region_id))
3702 .unwrap()
3703 }
3704
3705 async fn append(
3706 store: &ObjectStoreLogStore,
3707 region_id: RegionId,
3708 data: &str,
3709 ) -> Result<AppendBatchResponse> {
3710 store
3711 .append_batch(vec![entry(store, region_id, data)])
3712 .await
3713 }
3714
3715 fn spawn_append_batch(
3716 store: &Arc<ObjectStoreLogStore>,
3717 entries: Vec<Entry>,
3718 ) -> tokio::task::JoinHandle<Result<AppendBatchResponse>> {
3719 let store = store.clone();
3720 tokio::spawn(async move { store.append_batch(entries).await })
3721 }
3722
3723 async fn spawn_appends(
3726 store: &Arc<ObjectStoreLogStore>,
3727 region_id: RegionId,
3728 count: usize,
3729 ) -> Vec<tokio::task::JoinHandle<Result<AppendBatchResponse>>> {
3730 let admitted = *store.admitted_appends.borrow();
3731 let mut handles = Vec::with_capacity(count);
3732 for index in 1..=count {
3733 let entries = vec![entry(store, region_id, &format!("a{index}"))];
3734 handles.push(spawn_append_batch(store, entries));
3735 store
3736 .wait_for_admitted_appends(admitted + index)
3737 .await
3738 .unwrap();
3739 }
3740 handles
3741 }
3742
3743 async fn object_seqs(io: &dyn WalObjectIo) -> Vec<u64> {
3744 io.list()
3745 .await
3746 .unwrap()
3747 .into_iter()
3748 .map(|object| object.object_seq)
3749 .collect()
3750 }
3751
3752 async fn collected_object_seqs(store: &ObjectStoreLogStore) -> Vec<u64> {
3754 timeout(WAIT, store.wait_for_garbage_collection())
3755 .await
3756 .unwrap();
3757 object_seqs(store.io.as_ref()).await
3758 }
3759
3760 fn unwrap_shared(error: &Error) -> &Error {
3761 match error {
3762 Error::ObjectStoreWal { source, .. } => source,
3763 other => panic!("expected a shared error, actual {other:?}"),
3764 }
3765 }
3766
3767 fn assert_stopped(error: &Error) {
3768 assert!(
3769 matches!(error, Error::ObjectStoreWalStopped { .. }),
3770 "unexpected error: {error:?}"
3771 );
3772 }
3773
3774 async fn next_parked_operation(
3776 parked: &mut mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
3777 ) -> (u64, oneshot::Sender<bool>) {
3778 timeout(WAIT, parked.recv()).await.unwrap().unwrap()
3779 }
3780
3781 async fn parked_operations(
3784 parked: &mut mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
3785 count: usize,
3786 ) -> HashMap<u64, oneshot::Sender<bool>> {
3787 let mut releases = HashMap::new();
3788 for _ in 0..count {
3789 let (object_seq, release) = next_parked_operation(parked).await;
3790 releases.insert(object_seq, release);
3791 }
3792 releases
3793 }
3794
3795 async fn open_over(
3796 io: Arc<dyn WalObjectIo>,
3797 config: &ObjectStoreWalConfig,
3798 ) -> Arc<ObjectStoreLogStore> {
3799 ObjectStoreLogStore::open(io, config, PREFIX.to_string())
3800 .await
3801 .unwrap()
3802 }
3803
3804 async fn open_parking_creates(
3805 object_store: ObjectStore,
3806 config: &ObjectStoreWalConfig,
3807 ) -> (
3808 Arc<ObjectStoreLogStore>,
3809 Arc<ParkedIo>,
3810 mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
3811 ) {
3812 let (io, parked) = ParkedIo::parking_creates(object_store);
3813 let store = open_over(io.clone(), config).await;
3814 io.creates_parked.store(true, Ordering::SeqCst);
3816 (store, io, parked)
3817 }
3818
3819 async fn round_trip_actor(store: &ObjectStoreLogStore) {
3823 store.seal_open_batch().await.unwrap();
3824 }
3825
3826 struct RefusingStore {
3831 store: ObjectStore,
3832 writes: Arc<Mutex<Option<ErrorKind>>>,
3833 reads: Arc<Mutex<Option<ErrorKind>>>,
3834 refused_writes: Arc<Mutex<Vec<Instant>>>,
3835 }
3836
3837 impl RefusingStore {
3838 fn new() -> Self {
3839 let writes = Arc::new(Mutex::new(None::<ErrorKind>));
3840 let reads = Arc::new(Mutex::new(None::<ErrorKind>));
3841 let refused_writes = Arc::new(Mutex::new(Vec::new()));
3842 let (refusing_writes, refused_reads) = (writes.clone(), reads.clone());
3843 let refused_at = refused_writes.clone();
3844 let mock = MockLayerBuilder::default()
3845 .writer_factory(Arc::new(move |_, _, inner| {
3846 match *refusing_writes.lock().unwrap() {
3847 Some(kind) => {
3848 refused_at.lock().unwrap().push(Instant::now());
3849 Box::new(RefusedWriter(kind))
3850 }
3851 None => inner,
3852 }
3853 }))
3854 .reader_factory(Arc::new(move |_, _, inner| {
3855 match *refused_reads.lock().unwrap() {
3856 Some(kind) => Box::new(RefusedReader(kind)),
3857 None => inner,
3858 }
3859 }))
3860 .build()
3861 .unwrap();
3862 Self {
3863 store: with_retry_layers(memory_store().layer(mock)),
3864 writes,
3865 reads,
3866 refused_writes,
3867 }
3868 }
3869
3870 fn refuse_writes(&self, kind: Option<ErrorKind>) {
3871 *self.writes.lock().unwrap() = kind;
3872 }
3873
3874 fn refuse_reads(&self, kind: Option<ErrorKind>) {
3875 *self.reads.lock().unwrap() = kind;
3876 }
3877 }
3878
3879 struct RefusedWriter(ErrorKind);
3880
3881 impl oio::Write for RefusedWriter {
3882 async fn write(&mut self, _buffer: mock::Buffer) -> mock::Result<()> {
3883 Ok(())
3884 }
3885
3886 async fn close(&mut self) -> mock::Result<mock::Metadata> {
3887 Err(mock::Error::new(self.0, "refused by the object store"))
3888 }
3889
3890 async fn abort(&mut self) -> mock::Result<()> {
3891 Ok(())
3892 }
3893 }
3894
3895 struct RefusedReader(ErrorKind);
3896
3897 impl oio::Read for RefusedReader {
3898 async fn open(
3899 &self,
3900 _range: mock::BytesRange,
3901 ) -> mock::Result<(mock::RpRead, Box<dyn oio::ReadStreamDyn>)> {
3902 Err(mock::Error::new(self.0, "refused by the object store"))
3903 }
3904
3905 async fn read(
3906 &self,
3907 _range: mock::BytesRange,
3908 ) -> mock::Result<(mock::RpRead, mock::Buffer)> {
3909 Err(mock::Error::new(self.0, "refused by the object store"))
3910 }
3911 }
3912
3913 fn assert_rejected(error: &Error, kind: ErrorKind) {
3917 assert!(
3918 matches!(error, Error::ObjectStoreWalFailing { source, .. }
3919 if matches!(&**source, Error::WalObjectStore { error, .. } if error.kind() == kind)),
3920 "unexpected error: {error:?}"
3921 );
3922 assert_eq!(RetryHint::Retryable, error.retry_hint());
3923 assert_eq!(StatusCode::StorageUnavailable, error.status_code());
3924 }
3925
3926 fn failing_stores() -> i64 {
3927 METRIC_OBJECT_STORE_WAL_FAILING.get()
3928 }
3929
3930 #[tokio::test]
3931 async fn test_store_rejects_invalid_config() {
3932 let _serialized = SERIALIZED.lock().await;
3933 for config in [
3934 config(Duration::from_millis(9), 1),
3935 config(Duration::from_secs(1), 0),
3936 ObjectStoreWalConfig {
3937 prefix: "/absolute".to_string(),
3938 ..config(Duration::from_secs(1), 1)
3939 },
3940 ObjectStoreWalConfig {
3941 max_unpersisted_bytes: ReadableSize(0),
3942 ..config(Duration::from_secs(1), 1)
3943 },
3944 ObjectStoreWalConfig {
3945 max_unpersisted_age: Duration::ZERO,
3946 ..config(Duration::from_secs(1), 1)
3947 },
3948 ] {
3949 let error = ObjectStoreLogStore::try_new(memory_store(), &config, 1, 2)
3950 .await
3951 .err()
3952 .unwrap();
3953 assert!(
3954 matches!(error, Error::InvalidWalObjectStore { .. }),
3955 "unexpected error for {config:?}: {error:?}"
3956 );
3957 }
3958 }
3959
3960 #[tokio::test]
3961 async fn test_store_recovers_only_its_node_and_generation() {
3962 let _serialized = SERIALIZED.lock().await;
3963 let object_store = memory_store();
3964 let config = ObjectStoreWalConfig::default();
3965 let identities = [(1, 2), (3, 2), (1, 4)];
3966 for (index, (node_id, generation)) in identities.iter().enumerate() {
3967 let io = ObjectStoreIo::new(
3968 object_store.clone(),
3969 config.node_prefix(*node_id, *generation),
3970 )
3971 .unwrap();
3972 put_fixture(
3973 &io,
3974 0,
3975 &[Record {
3976 region_id: region(1),
3977 entry_id: index as u64 + 1,
3978 payload: Bytes::from_static(b"entry"),
3979 }],
3980 )
3981 .await;
3982 }
3983 let root_io = ObjectStoreIo::new(object_store.clone(), &config.prefix).unwrap();
3985 root_io
3986 .put_if_absent(0, Bytes::from_static(b"invalid"))
3987 .await
3988 .unwrap();
3989 for (index, (node_id, generation)) in identities.iter().enumerate() {
3990 let store =
3991 ObjectStoreLogStore::try_new(object_store.clone(), &config, *node_id, *generation)
3992 .await
3993 .unwrap();
3994 for (other_index, (other_node, other_generation)) in identities.iter().enumerate() {
3995 let provider = Provider::object_store_provider(
3996 region(1),
3997 config.node_prefix(*other_node, *other_generation),
3998 );
3999 if index == other_index {
4000 assert_eq!(store.latest_entry_id(&provider).unwrap(), index as u64 + 1);
4001 } else {
4002 assert!(matches!(
4003 store.latest_entry_id(&provider),
4004 Err(Error::MismatchedWalPrefix { .. })
4005 ));
4006 }
4007 }
4008 let root_provider = Provider::object_store_provider(region(1), config.prefix.clone());
4009 assert!(matches!(
4010 store.latest_entry_id(&root_provider),
4011 Err(Error::MismatchedWalPrefix { .. })
4012 ));
4013 store.stop().await.unwrap();
4014 }
4015 }
4016
4017 struct OverwritingWriter {
4020 store: ObjectStore,
4021 path: String,
4022 content: Vec<u8>,
4023 }
4024
4025 impl oio::Write for OverwritingWriter {
4026 async fn write(&mut self, buffer: mock::Buffer) -> mock::Result<()> {
4027 self.content.extend_from_slice(&buffer.to_bytes());
4028 Ok(())
4029 }
4030
4031 async fn close(&mut self) -> mock::Result<mock::Metadata> {
4032 let content = std::mem::take(&mut self.content);
4033 self.store.write(&self.path, content).await
4034 }
4035
4036 async fn abort(&mut self) -> mock::Result<()> {
4037 Ok(())
4038 }
4039 }
4040
4041 struct LosingWriter(oio::Writer);
4044
4045 impl oio::Write for LosingWriter {
4046 async fn write(&mut self, buffer: mock::Buffer) -> mock::Result<()> {
4047 self.0.write(buffer).await
4048 }
4049
4050 async fn close(&mut self) -> mock::Result<mock::Metadata> {
4051 self.0.close().await?;
4052 Err(mock::Error::new(mock::ErrorKind::Unexpected, "lost response").set_temporary())
4053 }
4054
4055 async fn abort(&mut self) -> mock::Result<()> {
4056 self.0.abort().await
4057 }
4058 }
4059
4060 async fn keys(object_store: &ObjectStore) -> Vec<String> {
4061 let mut keys = object_store
4062 .list_with(&format!("{PREFIX}/"))
4063 .recursive(true)
4064 .await
4065 .unwrap()
4066 .into_iter()
4067 .filter(|entry| entry.metadata().is_file())
4068 .map(|entry| entry.path().to_string())
4069 .collect::<Vec<_>>();
4070 keys.sort_unstable();
4071 keys
4072 }
4073
4074 #[tokio::test]
4075 async fn test_store_open_deletes_its_conditional_create_probe() {
4076 let _serialized = SERIALIZED.lock().await;
4077 let object_store = memory_store();
4078 let store = open(object_store.clone(), &eager()).await;
4079 store.stop().await.unwrap();
4080
4081 assert_eq!(
4082 vec![object_path(&object_store, 0)],
4083 keys(&object_store).await
4084 );
4085 }
4086
4087 #[tokio::test]
4088 async fn test_store_open_refuses_an_object_store_that_ignores_conditional_create() {
4089 let _serialized = SERIALIZED.lock().await;
4090 let object_store = memory_store();
4091 let target = object_store.clone();
4092 let ignoring = object_store.clone().layer(
4093 MockLayerBuilder::default()
4094 .writer_factory(Arc::new(move |path, _, _| {
4095 Box::new(OverwritingWriter {
4096 store: target.clone(),
4097 path: path.to_string(),
4098 content: Vec::new(),
4099 })
4100 }))
4101 .build()
4102 .unwrap(),
4103 );
4104
4105 let error = ObjectStoreLogStore::try_new(ignoring, &eager(), 1, 2)
4106 .await
4107 .unwrap_err();
4108 assert!(
4109 matches!(&error, Error::InvalidWalObjectStore { reason, .. }
4110 if reason.contains("overwrote the existing object")),
4111 "unexpected error: {error:?}"
4112 );
4113 assert!(keys(&object_store).await.is_empty());
4116 }
4117
4118 #[tokio::test]
4119 async fn test_store_open_accepts_a_retried_first_probe_create() {
4120 let _serialized = SERIALIZED.lock().await;
4121 let object_store = memory_store();
4122 let lose_next = Arc::new(AtomicBool::new(true));
4123 let losing = lose_next.clone();
4124 let retrying = object_store.clone().layer(
4125 MockLayerBuilder::default()
4126 .writer_factory(Arc::new(move |_, _, inner| {
4127 if losing.swap(false, Ordering::SeqCst) {
4128 Box::new(LosingWriter(inner))
4129 } else {
4130 inner
4131 }
4132 }))
4133 .build()
4134 .unwrap(),
4135 );
4136
4137 let store = open(retrying, &eager()).await;
4139 assert!(!lose_next.load(Ordering::SeqCst));
4140 store.stop().await.unwrap();
4141 assert_eq!(
4142 vec![object_path(&object_store, 0)],
4143 keys(&object_store).await
4144 );
4145 }
4146
4147 #[tokio::test]
4148 async fn test_store_recovery_ignores_a_probe_left_behind() {
4149 let _serialized = SERIALIZED.lock().await;
4150 let object_store = memory_store();
4151 let undeletable = object_store.clone().layer(
4153 MockLayerBuilder::default()
4154 .deleter_factory(Arc::new(|_| Box::new(())))
4155 .build()
4156 .unwrap(),
4157 );
4158 open(undeletable, &eager()).await.stop().await.unwrap();
4159 let probe_prefix = format!("{PREFIX}/probes/");
4160 let probes = keys(&object_store)
4161 .await
4162 .into_iter()
4163 .filter(|key| key.starts_with(&probe_prefix))
4164 .collect::<Vec<_>>();
4165 assert_eq!(1, probes.len(), "{probes:?}");
4166
4167 let store = open(object_store.clone(), &eager()).await;
4169 let io = ObjectStoreIo::new(object_store, PREFIX).unwrap();
4170 assert_eq!(vec![0, 1], object_seqs(&io).await);
4171 store.stop().await.unwrap();
4172 }
4173
4174 async fn recover_by_decoding(io: &dyn WalObjectIo) -> Result<Recovered> {
4176 let mut objects = Vec::new();
4177 for object in io.list().await? {
4178 let bytes = io.get(object.object_seq).await?;
4179 let decoded = decode_object(&bytes)
4180 .and_then(|decoded| {
4181 ensure!(
4182 decoded.header.object_seq == object.object_seq,
4183 CorruptedWalObjectSnafu {
4184 reason: format!(
4185 "header sequence {} does not match key sequence {}",
4186 decoded.header.object_seq, object.object_seq
4187 ),
4188 }
4189 );
4190 Ok(decoded)
4191 })
4192 .with_context(|_| InvalidWalObjectSnafu {
4193 path: object.path.clone(),
4194 })?;
4195 objects.push(FetchedObject {
4196 object,
4197 header: decoded.header,
4198 footer: decoded.footer,
4199 releases: None,
4200 });
4201 }
4202 finish_recovery(FetchedObjects {
4203 objects,
4204 vanished: Vec::new(),
4205 })
4206 }
4207
4208 fn assert_same_recovery(expected: &Recovered, actual: &Recovered) {
4209 assert_eq!(
4210 catalog_contents(&expected.catalog),
4211 catalog_contents(&actual.catalog)
4212 );
4213 assert_eq!(expected.next_object_seq, actual.next_object_seq);
4214 assert_eq!(expected.durable_entry_ids, actual.durable_entry_ids);
4215 assert_eq!(expected.tip, actual.tip);
4216 assert_eq!(expected.max_epoch, actual.max_epoch);
4217 assert_eq!(expected.reclaim_boundary, actual.reclaim_boundary);
4218 assert_eq!(expected.unindexed, actual.unindexed);
4219 assert_eq!(expected.released_entry_ids, actual.released_entry_ids);
4220 }
4221
4222 fn catalog_contents(catalog: &ObjectCatalog) -> Vec<(u64, Vec<FooterEntry>)> {
4223 catalog
4224 .objects_in_order()
4225 .map(|(object_seq, footer)| (object_seq, footer.to_vec()))
4226 .collect()
4227 }
4228
4229 async fn populate(object_store: &ObjectStore, objects: usize, regions: u32) {
4230 for object in 0..objects {
4231 let records = (1..=regions)
4232 .filter(|number| !(object + *number as usize).is_multiple_of(3))
4233 .map(|number| Record {
4234 region_id: region(number),
4235 entry_id: id(object as u64, 1),
4236 payload: Bytes::from(format!("o{object}-r{number}")),
4237 })
4238 .collect::<Vec<_>>();
4239 put_records(object_store, object as u64, &records).await;
4240 }
4241 }
4242
4243 async fn put_records(object_store: &ObjectStore, object_seq: u64, records: &[Record]) {
4244 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
4245 put_fixture(&io, object_seq, records).await;
4246 }
4247
4248 async fn fixture_header(io: &ObjectStoreIo, object_seq: u64) -> Header {
4252 let below = io
4253 .list()
4254 .await
4255 .unwrap()
4256 .into_iter()
4257 .map(|object| object.object_seq)
4258 .filter(|seq| *seq < object_seq)
4259 .max();
4260 let prev = match below {
4261 Some(seq) => Some(decode_header(&io.get(seq).await.unwrap()).unwrap()),
4262 None => None,
4263 };
4264 Header {
4265 object_seq,
4266 epoch: prev.as_ref().map_or(1, |prev| prev.epoch),
4267 prev: prev.map(|prev| ChainLink {
4268 object_seq: prev.object_seq,
4269 epoch: prev.epoch,
4270 }),
4271 reclaim_boundary: 0,
4272 }
4273 }
4274
4275 async fn put_fixture(io: &ObjectStoreIo, object_seq: u64, records: &[Record]) {
4276 let header = fixture_header(io, object_seq).await;
4277 put_object_with_header(io, header, records).await;
4278 }
4279
4280 async fn put_object_with_header(io: &ObjectStoreIo, header: Header, records: &[Record]) {
4281 let object_seq = header.object_seq;
4282 let encoded = encode_object(header, records).unwrap();
4283 io.put_if_absent(object_seq, encoded.bytes).await.unwrap();
4284 }
4285
4286 async fn put_foreign(object_store: &ObjectStore, object_seq: u64, epoch: u64) {
4289 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
4290 let header = Header {
4291 object_seq,
4292 epoch,
4293 prev: None,
4294 reclaim_boundary: 0,
4295 };
4296 put_object_with_header(&io, header, &[]).await;
4297 }
4298
4299 fn object_path(object_store: &ObjectStore, object_seq: u64) -> String {
4300 ObjectStoreIo::new(object_store.clone(), PREFIX)
4301 .unwrap()
4302 .object_path(object_seq)
4303 }
4304
4305 async fn corrupt_object(
4306 object_store: &ObjectStore,
4307 path: &str,
4308 corrupt: impl FnOnce(&mut Vec<u8>),
4309 ) {
4310 let mut bytes = object_store.read(path).await.unwrap().to_vec();
4311 corrupt(&mut bytes);
4312 object_store.write(path, bytes).await.unwrap();
4313 }
4314
4315 fn footer_of(bytes: &[u8]) -> (FixedTrailer, Vec<FooterEntry>) {
4316 let trailer = decode_trailer(&bytes[bytes.len() - TRAILER_LEN..]).unwrap();
4317 let footer =
4318 decode_footer(&bytes[footer_range(trailer, bytes.len()).unwrap()], trailer).unwrap();
4319 (trailer, footer)
4320 }
4321
4322 async fn assert_recovery_rejects(object_store: &ObjectStore, path: &str, reason: &str) {
4325 for on_corrupted_segment in [CorruptedSegmentAction::Skip, CorruptedSegmentAction::Fail] {
4326 let config = on_corruption(on_corrupted_segment, eager());
4327 let error = ObjectStoreLogStore::try_new(object_store.clone(), &config, 1, 2)
4328 .await
4329 .unwrap_err();
4330 assert_invalid_object(&error, path, reason);
4331 }
4332 }
4333
4334 fn assert_invalid_object(error: &Error, path: &str, reason: &str) {
4335 match error {
4336 Error::InvalidWalObject {
4337 path: actual,
4338 source,
4339 ..
4340 } => {
4341 assert_eq!(path, actual);
4342 match &**source {
4343 Error::CorruptedWalObject { reason: actual, .. } => assert!(
4344 actual.contains(reason),
4345 "expected reason to contain {reason:?}, actual {actual:?}"
4346 ),
4347 other => panic!("expected a corrupted object error, actual {other:?}"),
4348 }
4349 }
4350 other => panic!("expected an invalid object error, actual {other:?}"),
4351 }
4352 }
4353
4354 #[tokio::test]
4355 async fn test_store_footer_recovery_matches_full_decode() {
4356 let _serialized = SERIALIZED.lock().await;
4357 let object_store = memory_store();
4358 populate(&object_store, 40, 5).await;
4359 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
4360
4361 let recovered = recover(&io).await.unwrap();
4362 let expected = recover_by_decoding(&io).await.unwrap();
4363
4364 assert_eq!(40, catalog_contents(&recovered.catalog).len());
4365 assert_same_recovery(&expected, &recovered);
4366 assert_eq!(40, recovered.next_object_seq);
4367 let durable = recovered.durable_entry_ids;
4368 assert_eq!(5, durable.len());
4369
4370 let store = open(object_store, &eager()).await;
4371 for number in 1..=5 {
4372 let region_id = region(number);
4373 let objects = (0..40u64)
4375 .filter(|object| !(object + number as u64).is_multiple_of(3))
4376 .collect::<Vec<_>>();
4377 assert_eq!(id(*objects.last().unwrap(), 1), durable[®ion_id]);
4378 assert_eq!(durable[®ion_id], latest(&store, region_id));
4379 }
4380 }
4381
4382 #[tokio::test]
4383 async fn test_store_recovery_rejects_corrupted_trailer_version_and_footer() {
4384 let _serialized = SERIALIZED.lock().await;
4385 type Corrupt = fn(&mut Vec<u8>);
4386 let cases: &[(&str, Corrupt)] = &[
4387 ("truncated object", |bytes| {
4388 bytes.truncate(MIN_OBJECT_LEN - 1)
4389 }),
4390 ("invalid header magic", |bytes| bytes[0] ^= 1),
4391 ("invalid footer range", |bytes| {
4392 let start = bytes.len() - TRAILER_LEN;
4393 bytes[start..start + 8].copy_from_slice(&0u64.to_be_bytes());
4394 }),
4395 ("overflows the object", |bytes| {
4396 let start = bytes.len() - TRAILER_LEN;
4397 bytes[start..start + 8].copy_from_slice(&u64::MAX.to_be_bytes());
4398 }),
4399 ("invalid trailer magic", |bytes| {
4400 let last = bytes.len() - 1;
4401 bytes[last] ^= 1;
4402 }),
4403 ("unsupported format version 3", |bytes| {
4404 bytes[8..10].copy_from_slice(&3u16.to_be_bytes());
4405 }),
4406 ("footer checksum mismatch", |bytes| {
4407 let (trailer, _) = footer_of(bytes);
4408 bytes[trailer.footer_offset as usize] ^= 1;
4409 }),
4410 ];
4411 for (reason, corrupt) in cases {
4412 let object_store = memory_store();
4413 populate(&object_store, 3, 2).await;
4414 let path = object_path(&object_store, 1);
4415 corrupt_object(&object_store, &path, *corrupt).await;
4416
4417 assert_recovery_rejects(&object_store, &path, reason).await;
4418 }
4419 }
4420
4421 #[tokio::test]
4422 async fn test_store_recovery_rejects_header_sequence_mismatch() {
4423 let _serialized = SERIALIZED.lock().await;
4424 let object_store = memory_store();
4425 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
4426 let encoded = encode_object(
4427 fixture_header(&io, 0).await,
4428 &[Record {
4429 region_id: region(1),
4430 entry_id: 1,
4431 payload: Bytes::from_static(b"a1"),
4432 }],
4433 )
4434 .unwrap();
4435 io.put_if_absent(5, encoded.bytes).await.unwrap();
4436
4437 assert_recovery_rejects(
4438 &object_store,
4439 &io.object_path(5),
4440 "header sequence 0 does not match key sequence 5",
4441 )
4442 .await;
4443 }
4444
4445 fn rewrite_segment_range(bytes: &mut [u8], index: usize, offset: u64, len: u64) {
4448 let trailer_start = bytes.len() - TRAILER_LEN;
4449 let (trailer, _) = footer_of(bytes);
4450 let footer = footer_range(trailer, bytes.len()).unwrap();
4451 let entry = footer.start + 4 + index * FOOTER_ENTRY_LEN;
4452 bytes[entry + 28..entry + 36].copy_from_slice(&offset.to_be_bytes());
4453 bytes[entry + 36..entry + 44].copy_from_slice(&len.to_be_bytes());
4454 let checksum = crc32fast::hash(&bytes[footer]);
4455 bytes[trailer_start + 16..trailer_start + 20].copy_from_slice(&checksum.to_be_bytes());
4456 }
4457
4458 #[tokio::test]
4459 async fn test_store_recovery_rejects_segment_ranges_that_do_not_tile_the_object() {
4460 let _serialized = SERIALIZED.lock().await;
4461 type Corrupt = fn(&mut Vec<u8>, &[FooterEntry]);
4462 let cases: [(&str, Corrupt); 5] = [
4463 ("overflows the object", |bytes, footer| {
4464 rewrite_segment_range(bytes, 0, u64::MAX, footer[0].segment_len);
4465 }),
4466 ("invalid segment range", |bytes, footer| {
4467 let second = &footer[1];
4468 rewrite_segment_range(bytes, 1, second.segment_offset + 1, second.segment_len);
4469 }),
4470 ("invalid segment range", |bytes, footer| {
4471 let second = &footer[1];
4472 rewrite_segment_range(bytes, 1, second.segment_offset - 1, second.segment_len);
4473 }),
4474 ("invalid segment range", |bytes, footer| {
4475 let second = &footer[1];
4476 rewrite_segment_range(bytes, 1, second.segment_offset, second.segment_len + 1);
4477 }),
4478 ("segments end at", |bytes, footer| {
4479 let second = &footer[1];
4480 rewrite_segment_range(bytes, 1, second.segment_offset, second.segment_len - 1);
4481 }),
4482 ];
4483 for (reason, corrupt) in cases {
4484 let object_store = memory_store();
4485 populate(&object_store, 2, 3).await;
4486 let path = object_path(&object_store, 1);
4487 corrupt_object(&object_store, &path, |bytes| {
4488 let (_, footer) = footer_of(bytes);
4489 assert_eq!(2, footer.len());
4490 corrupt(bytes, &footer);
4491 footer_of(bytes);
4493 })
4494 .await;
4495
4496 assert_recovery_rejects(&object_store, &path, reason).await;
4497 let io = ObjectStoreIo::new(object_store, PREFIX).unwrap();
4498 let error = recover_by_decoding(&io).await.unwrap_err();
4499 assert_invalid_object(&error, &path, reason);
4500 }
4501 }
4502 #[tokio::test]
4503 async fn test_store_recovery_fetches_a_footer_longer_than_the_tail_window() {
4504 let _serialized = SERIALIZED.lock().await;
4505 let object_store = memory_store();
4506 put_wide_object(&object_store).await;
4507 put_object(&object_store, 1, region(1), &[id(1, 1)]).await;
4508
4509 let (io, reads) = RecordingIo::over(object_store.clone());
4510 let objects = io.list().await.unwrap();
4511 let wide = &objects[0];
4512 let (trailer, _) = footer_of(&object_store.read(&wide.path).await.unwrap().to_vec());
4513 assert!(trailer.footer_len > RECOVERY_TAIL_WINDOW as u64);
4514
4515 let recovered = recover(io.as_ref()).await.unwrap();
4516 let expected = recover_by_decoding(io.as_ref()).await.unwrap();
4517 assert_same_recovery(&expected, &recovered);
4518 assert_eq!(WIDE_REGIONS as usize, recovered.durable_entry_ids.len());
4519
4520 let mut wide_reads = reads
4523 .lock()
4524 .unwrap()
4525 .iter()
4526 .filter(|(object_seq, _, _)| *object_seq == wide.object_seq)
4527 .map(|(_, offset, len)| (*offset, *len))
4528 .collect::<Vec<_>>();
4529 wide_reads.sort_unstable();
4530 assert_eq!(
4531 vec![
4532 (0, HEADER_LEN as u64),
4533 (trailer.footer_offset, trailer.footer_len),
4534 (
4535 wide.size - RECOVERY_TAIL_WINDOW as u64,
4536 RECOVERY_TAIL_WINDOW as u64
4537 ),
4538 ],
4539 wide_reads
4540 );
4541 assert!(
4542 reads
4543 .lock()
4544 .unwrap()
4545 .iter()
4546 .all(|(object_seq, _, _)| *object_seq == wide.object_seq)
4547 );
4548
4549 let store = open(object_store, &eager()).await;
4550 assert_eq!(id(1, 1), latest(&store, region(1)));
4551 assert_eq!(1, latest(&store, region(WIDE_REGIONS)));
4552 store.stop().await.unwrap();
4553 }
4554
4555 async fn put_object(
4556 object_store: &ObjectStore,
4557 object_seq: u64,
4558 region_id: RegionId,
4559 entry_ids: &[EntryId],
4560 ) {
4561 let records = entry_ids
4562 .iter()
4563 .map(|entry_id| Record {
4564 region_id,
4565 entry_id: *entry_id,
4566 payload: Bytes::from(format!("e{entry_id}")),
4567 })
4568 .collect::<Vec<_>>();
4569 put_records(object_store, object_seq, &records).await;
4570 }
4571
4572 const WIDE_REGIONS: u32 = (RECOVERY_TAIL_WINDOW / FOOTER_ENTRY_LEN + 100) as u32;
4576
4577 async fn put_wide_object(object_store: &ObjectStore) -> (u64, u64) {
4580 let records = (1..=WIDE_REGIONS)
4581 .map(|number| Record {
4582 region_id: region(number),
4583 entry_id: 1,
4584 payload: Bytes::from_static(b"wide"),
4585 })
4586 .collect::<Vec<_>>();
4587 put_records(object_store, 0, &records).await;
4588 let bytes = object_store
4589 .read(&object_path(object_store, 0))
4590 .await
4591 .unwrap()
4592 .to_vec();
4593 let (trailer, _) = footer_of(&bytes);
4594 let tail_start = (bytes.len() - RECOVERY_TAIL_WINDOW) as u64;
4595 assert!(trailer.footer_offset < tail_start);
4596 (tail_start, trailer.footer_offset)
4597 }
4598
4599 #[tokio::test]
4600 async fn test_store_resumes_sequence_and_durable_ids() {
4601 let _serialized = SERIALIZED.lock().await;
4602 let object_store = memory_store();
4603 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
4604 let recovered = recover(&io).await.unwrap();
4605 assert_eq!(0, recovered.next_object_seq);
4606 assert!(recovered.durable_entry_ids.is_empty());
4607
4608 put_object(&object_store, 2, region(1), &[1, id(5, 7)]).await;
4609 put_object(&object_store, 4, region(2), &[8]).await;
4610 let recovered = recover(&io).await.unwrap();
4611 assert_eq!(6, recovered.next_object_seq);
4612 assert_eq!(
4613 HashMap::from([(region(1), id(5, 7)), (region(2), 8)]),
4614 recovered.durable_entry_ids
4615 );
4616 let store = open(object_store.clone(), &eager()).await;
4617 assert_eq!(id(5, 7), latest(&store, region(1)));
4618 assert_eq!(8, latest(&store, region(2)));
4619 assert_eq!(0, latest(&store, region(3)));
4620
4621 let response = store
4625 .append_batch(vec![
4626 entry(&store, region(1), "a"),
4627 entry(&store, region(2), "b"),
4628 ])
4629 .await
4630 .unwrap();
4631 assert_eq!(
4632 HashMap::from([(region(1), id(7, 1)), (region(2), id(7, 1))]),
4633 response.last_entry_ids
4634 );
4635 assert_eq!(vec![2, 4, 6, 7], object_seqs(store.io.as_ref()).await);
4636 assert_eq!(
4637 expected_entries(
4638 region(1),
4639 &[
4640 (1, "e1"),
4641 (id(5, 7), &format!("e{}", id(5, 7))),
4642 (id(7, 1), "a")
4643 ]
4644 ),
4645 read_entries(&store, region(1), 0).await
4646 );
4647 store.stop().await.unwrap();
4648
4649 let store = open(object_store, &eager()).await;
4651 assert_eq!(id(7, 1), latest(&store, region(1)));
4652 let response = append(&store, region(2), "b2").await.unwrap();
4653 assert_eq!(
4654 HashMap::from([(region(2), id(9, 1))]),
4655 response.last_entry_ids
4656 );
4657 assert_eq!(
4658 expected_entries(region(2), &[(8, "e8"), (id(7, 1), "b"), (id(9, 1), "b2")]),
4659 read_entries(&store, region(2), 0).await
4660 );
4661 store.stop().await.unwrap();
4662 }
4663
4664 async fn missing_history(
4667 store: &ObjectStoreLogStore,
4668 region_id: RegionId,
4669 entry_id: EntryId,
4670 ) -> Option<String> {
4671 match store
4672 .check_region_history(&provider(region_id), entry_id)
4673 .await
4674 {
4675 Ok(()) => None,
4676 Err(error @ Error::MissingWalHistory { .. }) => {
4677 assert_eq!(
4678 common_error::status_code::StatusCode::InvalidArguments,
4679 error.status_code()
4680 );
4681 Some(error.to_string())
4682 }
4683 Err(error) => panic!("unexpected error: {error:?}"),
4684 }
4685 }
4686
4687 async fn copy_objects(from: &ObjectStore, to: &ObjectStore) {
4689 let io = ObjectStoreIo::new(from.clone(), PREFIX).unwrap();
4690 for object in io.list().await.unwrap() {
4691 let bytes = from.read(&object.path).await.unwrap().to_vec();
4692 to.write(&object.path, bytes).await.unwrap();
4693 }
4694 }
4695
4696 #[tokio::test]
4697 async fn test_store_history_is_missing_from_a_prefix_found_empty() {
4698 let _serialized = SERIALIZED.lock().await;
4699 let config = ObjectStoreWalConfig {
4700 storage_provider: "wal-store".to_string(),
4701 ..eager()
4702 };
4703 let object_store = memory_store();
4704 let store = open(object_store.clone(), &config).await;
4705
4706 let written = append(&store, region(2), "a").await.unwrap().last_entry_ids[®ion(2)];
4709 assert_eq!(None, missing_history(&store, region(2), 0).await);
4710 assert_eq!(None, missing_history(&store, region(2), written).await);
4711 assert!(
4712 missing_history(&store, region(2), written + 1)
4713 .await
4714 .is_some()
4715 );
4716
4717 for entry_id in [0, id(0, 1), id(7, 1)] {
4721 let message = missing_history(&store, region(1), entry_id).await.unwrap();
4722 for expected in [
4723 ®ion(1).to_string(),
4724 PREFIX,
4725 "storage provider \"wal-store\" (memory, bucket ",
4727 "root \"/\")",
4728 "the prefix held no object when the WAL opened",
4729 "copy every object under the prefix",
4730 "`[wal].accept_missing_history = true`",
4731 ] {
4732 assert!(message.contains(expected), "{message}");
4733 }
4734 }
4735 assert_eq!(vec![1], object_seqs(store.io.as_ref()).await);
4736
4737 store.start_region_history(&provider(region(3))).unwrap();
4740 assert_eq!(None, missing_history(&store, region(3), 0).await);
4741
4742 let foreign = Provider::object_store_provider(region(1), "wal/datanodes/9/epochs/2".into());
4744 assert!(matches!(
4745 store.check_region_history(&foreign, 0).await.unwrap_err(),
4746 Error::MismatchedWalPrefix { .. }
4747 ));
4748 store.stop().await.unwrap();
4749 }
4750
4751 #[tokio::test]
4752 async fn test_store_withdraws_its_start_object_when_it_refuses_a_region() {
4753 let _serialized = SERIALIZED.lock().await;
4754 let object_store = memory_store();
4755
4756 for _ in 0..2 {
4760 let store = open(object_store.clone(), &eager()).await;
4761 assert_eq!(vec![0], object_seqs(store.io.as_ref()).await);
4762 let indexed_objects = METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.get();
4763 let indexed_bytes = METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.get();
4764 assert!(missing_history(&store, region(1), 0).await.is_some());
4765 assert!(object_seqs(store.io.as_ref()).await.is_empty());
4766 assert!(!store.catalog.read().unwrap().contains_object(0));
4768 assert_eq!(
4769 (indexed_objects - 1, indexed_bytes - object_len(&[]) as i64),
4770 (
4771 METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.get(),
4772 METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.get()
4773 )
4774 );
4775
4776 assert!(missing_history(&store, region(2), 0).await.is_some());
4779 let entry = Entry::Naive(NaiveEntry {
4780 provider: provider(region(2)),
4781 region_id: region(2),
4782 entry_id: 0,
4783 data: b"a".to_vec(),
4784 });
4785 for error in [
4786 store.append_batch(vec![entry]).await.unwrap_err(),
4787 store
4788 .start_region_history(&provider(region(3)))
4789 .unwrap_err(),
4790 ] {
4791 assert!(
4792 matches!(unwrap_shared(&error), Error::WalStartWithdrawn { .. }),
4793 "{error:?}"
4794 );
4795 }
4796 store.stop().await.unwrap();
4797 }
4798
4799 let store = open(object_store.clone(), &eager()).await;
4801 store.start_region_history(&provider(region(3))).unwrap();
4802 assert!(missing_history(&store, region(1), 0).await.is_some());
4803 assert_eq!(vec![0], object_seqs(store.io.as_ref()).await);
4804 assert_eq!(None, missing_history(&store, region(3), 0).await);
4805 store.stop().await.unwrap();
4806 }
4807
4808 #[tokio::test]
4809 async fn test_store_withdrawal_outlives_its_caller_and_holds_up_stop() {
4810 let _serialized = SERIALIZED.lock().await;
4811 let object_store = memory_store();
4812 let (io, mut parked) = ParkedIo::parking_creates(object_store.clone());
4813 let store = open_over(io.clone(), &eager()).await;
4814 io.deletes_parked.store(true, Ordering::SeqCst);
4815
4816 let first = {
4819 let store = store.clone();
4820 tokio::spawn(async move { store.check_region_history(&provider(region(1)), 0).await })
4821 };
4822 let (object_seq, release) = next_parked_operation(&mut parked).await;
4823 assert_eq!(0, object_seq);
4824 first.abort();
4825
4826 let (withdrawn_tx, mut withdrawn) = oneshot::channel();
4830 store
4831 .command_tx
4832 .send(Command::Withdraw {
4833 response: withdrawn_tx,
4834 })
4835 .await
4836 .unwrap();
4837 store.begin_stop();
4838 let (stopped_tx, mut stopped) = oneshot::channel();
4839 store
4840 .command_tx
4841 .send(Command::Stop {
4842 response: stopped_tx,
4843 })
4844 .await
4845 .unwrap();
4846 let (answered_tx, answered) = oneshot::channel();
4847 store
4848 .command_tx
4849 .send(Command::WaitDurable {
4850 region_id: region(1),
4851 entry_id: 1,
4852 response: answered_tx,
4853 })
4854 .await
4855 .unwrap();
4856 timeout(WAIT, answered).await.unwrap().unwrap().unwrap_err();
4857 assert!(withdrawn.try_recv().is_err());
4858 assert!(stopped.try_recv().is_err());
4859
4860 release.send(true).unwrap();
4861 timeout(WAIT, withdrawn).await.unwrap().unwrap();
4862 timeout(WAIT, stopped).await.unwrap().unwrap().unwrap();
4863 assert!(object_seqs(io.as_ref()).await.is_empty());
4864 assert!(missing_history(&store, region(2), 0).await.is_some());
4865
4866 let store = open(object_store, &eager()).await;
4868 assert!(missing_history(&store, region(1), 0).await.is_some());
4869 store.stop().await.unwrap();
4870 }
4871
4872 #[tokio::test]
4873 async fn test_store_withdrawal_keeps_the_chain_of_a_successor() {
4874 let _serialized = SERIALIZED.lock().await;
4875 let object_store = memory_store();
4876 let (io, mut parked) = ParkedIo::parking_creates(object_store.clone());
4877 let store = open_over(io.clone(), &eager()).await;
4878 io.deletes_parked.store(true, Ordering::SeqCst);
4879
4880 let refused = {
4884 let store = store.clone();
4885 tokio::spawn(async move { store.check_region_history(&provider(region(1)), 0).await })
4886 };
4887 let (object_seq, release) = next_parked_operation(&mut parked).await;
4888 assert_eq!(0, object_seq);
4889 let successor = open(object_store.clone(), &eager()).await;
4890 assert_eq!(vec![0, 1], object_seqs(successor.io.as_ref()).await);
4891 successor.stop().await.unwrap();
4892 release.send(true).unwrap();
4893 timeout(WAIT, refused).await.unwrap().unwrap().unwrap_err();
4894 store.stop().await.unwrap();
4895
4896 let store = open(object_store, &eager()).await;
4898 assert_eq!(vec![1, 2], object_seqs(store.io.as_ref()).await);
4899 store.stop().await.unwrap();
4900 }
4901
4902 #[tokio::test]
4903 async fn test_store_keeps_indexing_a_start_object_whose_withdrawal_failed() {
4904 let _serialized = SERIALIZED.lock().await;
4905 let (io, mut parked) = ParkedIo::parking_creates(memory_store());
4906 let store = open_over(io.clone(), &eager()).await;
4907 io.deletes_parked.store(true, Ordering::SeqCst);
4908 let indexed_objects = METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.get();
4909 let indexed_bytes = METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.get();
4910
4911 let refused = {
4912 let store = store.clone();
4913 tokio::spawn(async move { store.check_region_history(&provider(region(1)), 0).await })
4914 };
4915 let (object_seq, release) = next_parked_operation(&mut parked).await;
4916 assert_eq!(0, object_seq);
4917 release.send(false).unwrap();
4918 assert!(matches!(
4919 timeout(WAIT, refused).await.unwrap().unwrap().unwrap_err(),
4920 Error::MissingWalHistory { .. }
4921 ));
4922
4923 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
4926 assert!(store.catalog.read().unwrap().contains_object(0));
4927 assert_eq!(
4928 (indexed_objects, indexed_bytes),
4929 (
4930 METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.get(),
4931 METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.get()
4932 )
4933 );
4934 store.stop().await.unwrap();
4935 }
4936
4937 #[tokio::test]
4938 async fn test_store_history_is_missing_above_the_newest_object_holding_entries() {
4939 let _serialized = SERIALIZED.lock().await;
4940 let object_store = memory_store();
4941 let store = open(object_store.clone(), &eager()).await;
4943 append(&store, region(1), "a").await.unwrap();
4944 append(&store, region(1), "b").await.unwrap();
4945 store.stop().await.unwrap();
4946
4947 for start_object in [3, 4] {
4951 let store = open(object_store.clone(), &eager()).await;
4952 assert!(
4953 store.storage.starts_with("the default storage (memory, ")
4954 && store.storage.ends_with("root \"/\")"),
4955 "{}",
4956 store.storage
4957 );
4958 for region_id in [region(1), region(2)] {
4959 for entry_id in [0, id(1, 1), id(2, 1), id(2, POSITION_LIMIT - 1)] {
4960 assert_eq!(None, missing_history(&store, region_id, entry_id).await);
4961 }
4962 for entry_id in [id(3, 1), id(9, 1)] {
4963 let message = missing_history(&store, region_id, entry_id).await.unwrap();
4964 let reason = format!(
4965 "its watermark, entry {entry_id}, names object {}, but the prefix held entries only in objects below 3 when the WAL opened",
4966 entry_id >> 20
4967 );
4968 assert!(message.contains(&reason), "{message}");
4969 }
4970 }
4971 assert_eq!(
4972 Some(&start_object),
4973 object_seqs(store.io.as_ref()).await.last()
4974 );
4975 store.stop().await.unwrap();
4976 }
4977
4978 let store = open(object_store, &eager()).await;
4981 let written = append(&store, region(2), "c").await.unwrap().last_entry_ids[®ion(2)];
4982 assert_eq!(id(6, 1), written);
4983 assert_eq!(None, missing_history(&store, region(2), written).await);
4984 assert!(missing_history(&store, region(1), written).await.is_some());
4985 store.stop().await.unwrap();
4986
4987 let object_store = memory_store();
4989 open(object_store.clone(), &eager())
4990 .await
4991 .stop()
4992 .await
4993 .unwrap();
4994 let store = open(object_store, &eager()).await;
4995 assert_eq!(None, missing_history(&store, region(1), 0).await);
4996 let message = missing_history(&store, region(1), id(0, 1)).await.unwrap();
4997 assert!(
4998 message.contains("names object 0, but the prefix held no entry when the WAL opened"),
4999 "{message}"
5000 );
5001 store.stop().await.unwrap();
5002 }
5003
5004 #[tokio::test]
5005 async fn test_store_history_survives_collection_down_to_the_newest_object() {
5006 let _serialized = SERIALIZED.lock().await;
5007 let object_store = memory_store();
5008 let store = open(object_store.clone(), &eager()).await;
5009 let flushed_1 = append(&store, region(1), "a").await.unwrap().last_entry_ids[®ion(1)];
5012 store
5013 .obsolete(&provider(region(1)), region(1), flushed_1)
5014 .await
5015 .unwrap();
5016 let flushed_2 = append(&store, region(2), "b").await.unwrap().last_entry_ids[®ion(2)];
5017 store
5018 .obsolete(&provider(region(2)), region(2), flushed_2)
5019 .await
5020 .unwrap();
5021 assert_eq!(vec![2], collected_object_seqs(&store).await);
5022 store.stop().await.unwrap();
5023
5024 let store = open(object_store, &eager()).await;
5027 assert_eq!(0, latest(&store, region(1)));
5028 assert_eq!(None, missing_history(&store, region(1), flushed_1).await);
5029 assert_eq!(None, missing_history(&store, region(2), flushed_2).await);
5030 store.stop().await.unwrap();
5031 }
5032
5033 #[tokio::test]
5034 async fn test_store_history_survives_collection_down_to_a_release_object() {
5035 let _serialized = SERIALIZED.lock().await;
5036 let object_store = memory_store();
5037 let store = open(object_store.clone(), &eager()).await;
5038 let flushed = append(&store, region(1), "a").await.unwrap().last_entry_ids[®ion(1)];
5042 store
5043 .obsolete(&provider(region(1)), region(1), flushed)
5044 .await
5045 .unwrap();
5046 store
5047 .release_region(&provider(region(2)), region(2))
5048 .await
5049 .unwrap();
5050 assert_eq!(vec![2], collected_object_seqs(&store).await);
5051 store.stop().await.unwrap();
5052
5053 let store = open(object_store, &eager()).await;
5055 assert_eq!(None, missing_history(&store, region(1), flushed).await);
5056 store.stop().await.unwrap();
5057 }
5058
5059 #[tokio::test]
5060 async fn test_store_keeps_its_objects_after_a_release_when_it_refuses_a_region() {
5061 let _serialized = SERIALIZED.lock().await;
5062 let object_store = memory_store();
5063 let store = open(object_store.clone(), &eager()).await;
5064 store
5066 .release_region(&provider(region(2)), region(2))
5067 .await
5068 .unwrap();
5069 assert_eq!(vec![1], collected_object_seqs(&store).await);
5070
5071 assert!(missing_history(&store, region(1), 0).await.is_some());
5074 assert_eq!(vec![1], object_seqs(store.io.as_ref()).await);
5075 append(&store, region(3), "a").await.unwrap();
5076 store.stop().await.unwrap();
5077 }
5078
5079 #[tokio::test]
5080 async fn test_store_history_moves_with_a_copy_of_the_objects() {
5081 let _serialized = SERIALIZED.lock().await;
5082 let old_location = memory_store();
5083 let store = open(old_location.clone(), &eager()).await;
5084 let flushed = append(&store, region(1), "a").await.unwrap().last_entry_ids[®ion(1)];
5085 store
5086 .obsolete(&provider(region(1)), region(1), flushed)
5087 .await
5088 .unwrap();
5089 let unflushed = append(&store, region(1), "b").await.unwrap().last_entry_ids[®ion(1)];
5090 assert_eq!(vec![2], collected_object_seqs(&store).await);
5091 store.stop().await.unwrap();
5092
5093 let store = open(memory_store(), &eager()).await;
5095 assert!(missing_history(&store, region(1), flushed).await.is_some());
5096 store.stop().await.unwrap();
5097
5098 let new_location = memory_store();
5100 copy_objects(&old_location, &new_location).await;
5101 let store = open(new_location, &eager()).await;
5102 assert_eq!(None, missing_history(&store, region(1), flushed).await);
5103 assert_eq!(
5104 expected_entries(region(1), &[(unflushed, "b")]),
5105 read_entries(&store, region(1), flushed + 1).await
5106 );
5107 store.stop().await.unwrap();
5108 }
5109
5110 #[tokio::test]
5111 async fn test_store_accepts_a_missing_history_when_configured() {
5112 let _serialized = SERIALIZED.lock().await;
5113 let config = ObjectStoreWalConfig {
5114 accept_missing_history: true,
5115 ..eager()
5116 };
5117 let store = open(memory_store(), &config).await;
5118 let accepted = METRIC_OBJECT_STORE_WAL_MISSING_HISTORY_ACCEPTED_TOTAL.get();
5119 store
5120 .check_region_history(&provider(region(1)), id(5, 1))
5121 .await
5122 .unwrap();
5123 assert_eq!(
5124 accepted + 1,
5125 METRIC_OBJECT_STORE_WAL_MISSING_HISTORY_ACCEPTED_TOTAL.get()
5126 );
5127
5128 store
5132 .check_region_history(&provider(region(1)), id(5, 1))
5133 .await
5134 .unwrap();
5135 let written = append(&store, region(2), "a").await.unwrap().last_entry_ids[®ion(2)];
5136 store
5137 .check_region_history(&provider(region(2)), written)
5138 .await
5139 .unwrap();
5140 assert_eq!(
5141 accepted + 1,
5142 METRIC_OBJECT_STORE_WAL_MISSING_HISTORY_ACCEPTED_TOTAL.get()
5143 );
5144
5145 store
5148 .obsolete(&provider(region(1)), region(1), id(5, 1))
5149 .await
5150 .unwrap();
5151 assert_eq!(
5152 HashMap::from([(region(1), id(6, 1))]),
5153 append(&store, region(1), "b").await.unwrap().last_entry_ids
5154 );
5155 store.stop().await.unwrap();
5156 }
5157
5158 #[tokio::test]
5159 async fn test_store_rejects_exhausted_sequence() {
5160 let _serialized = SERIALIZED.lock().await;
5161 for (object_seq, entry_id) in [(u64::MAX, 1), (OBJECT_SEQ_LIMIT - 1, 1), (0, u64::MAX)] {
5162 let object_store = memory_store();
5163 put_object(&object_store, object_seq, region(1), &[entry_id]).await;
5164 let error = ObjectStoreLogStore::try_new(object_store, &eager(), 1, 2)
5165 .await
5166 .unwrap_err();
5167 assert!(
5168 matches!(error, Error::WalObjectSequenceExhausted { .. }),
5169 "{error:?}"
5170 );
5171 }
5172 }
5173
5174 #[tokio::test]
5175 async fn test_store_recovery_rejects_conflicting_entry_ranges() {
5176 let _serialized = SERIALIZED.lock().await;
5177 for entries in [[1, 2], [2, 3]] {
5178 let object_store = memory_store();
5179 put_object(&object_store, 0, region(1), &[1, 2]).await;
5180 put_object(&object_store, 1, region(1), &entries).await;
5181 let error = ObjectStoreLogStore::try_new(object_store.clone(), &eager(), 1, 2)
5182 .await
5183 .unwrap_err();
5184 assert_invalid_object(&error, &object_path(&object_store, 1), "entry");
5185 }
5186 }
5187
5188 #[tokio::test]
5189 async fn test_store_rejects_foreign_providers() {
5190 let _serialized = SERIALIZED.lock().await;
5191 let store = open(memory_store(), &eager()).await;
5192 let region_id = region(1);
5193 let other = Provider::object_store_provider(region_id, "other/prefix".to_string());
5194 let raft = Provider::raft_engine_provider(region_id.as_u64());
5195 for foreign in [&other, &raft] {
5196 let entry = Entry::Naive(NaiveEntry {
5197 provider: foreign.clone(),
5198 region_id,
5199 entry_id: 0,
5200 data: b"a1".to_vec(),
5201 });
5202 let errors = [
5203 store.append_batch(vec![entry]).await.unwrap_err(),
5204 store.latest_entry_id(foreign).unwrap_err(),
5205 store.read(foreign, 0).await.err().unwrap(),
5206 store.create_namespace(foreign).await.unwrap_err(),
5207 store.delete_namespace(foreign).await.unwrap_err(),
5208 store.obsolete(foreign, region_id, 1).await.unwrap_err(),
5209 store.obsolete_all(foreign, region_id).await.unwrap_err(),
5210 store.entry(Vec::new(), 1, region_id, foreign).unwrap_err(),
5211 ];
5212 for error in errors {
5213 if foreign == &raft {
5214 assert!(matches!(error, Error::InvalidProvider { .. }), "{error:?}");
5215 } else {
5216 assert!(
5217 matches!(&error,
5218 Error::MismatchedWalPrefix { expected, actual, .. }
5219 if expected == PREFIX && actual == "other/prefix"),
5220 "{error:?}"
5221 );
5222 }
5223 }
5224 }
5225 store.stop().await.unwrap();
5226 }
5227
5228 #[tokio::test]
5229 async fn test_store_stop_is_idempotent() {
5230 let _serialized = SERIALIZED.lock().await;
5231 let store = open(memory_store(), &eager()).await;
5232 let (first, second) = tokio::join!(store.stop(), store.stop());
5233 first.unwrap();
5234 second.unwrap();
5235 timeout(WAIT, store.command_tx.closed()).await.unwrap();
5236 store.stop().await.unwrap();
5237 assert!(store.stopped.load(Ordering::Acquire));
5238 assert_stopped(&store.append_batch(Vec::new()).await.unwrap_err());
5240 }
5241
5242 fn store_without_actor() -> (ObjectStoreLogStore, mpsc::Receiver<Command>) {
5244 let (command_tx, command_rx) = mpsc::channel(COMMAND_BUFFER);
5245 let (append_tx, _) = mpsc::channel(APPEND_BUFFER);
5246 let store = ObjectStoreLogStore {
5247 prefix: PREFIX.to_string(),
5248 storage: String::new(),
5249 ack_mode: AckMode::Durable,
5250 on_corrupted_segment: CorruptedSegmentAction::Skip,
5251 accept_missing_history: false,
5252 found_empty: false,
5253 history_end: 0,
5254 started_regions: StartedRegions::default(),
5255 io: Arc::new(ObjectStoreIo::new(memory_store(), PREFIX).unwrap()),
5256 catalog: Arc::default(),
5257 obsolete_entry_ids: ObsoleteEntryIds::default(),
5258 wal_holes: WalHoles::default(),
5259 deleting: Arc::default(),
5260 read_prefetch_budget: Arc::new(Semaphore::new(READ_PREFETCH_BYTES)),
5261 terminal_error: Arc::default(),
5262 stopped: Arc::new(AtomicBool::new(false)),
5263 command_tx,
5264 append_tx,
5265 admitted_appends: watch::channel(0).1,
5266 creates_held: watch::channel(false).0,
5267 creates_fail: Arc::default(),
5268 next_create_fails_after_write: Arc::default(),
5269 parked_creates: watch::channel(0).1,
5270 durability_waits: watch::channel(0).1,
5271 };
5272 (store, command_rx)
5273 }
5274
5275 #[tokio::test]
5276 async fn test_store_stop_with_actor_gone() {
5277 let _serialized = SERIALIZED.lock().await;
5278 let (store, command_rx) = store_without_actor();
5279 drop(command_rx);
5280 store.stop().await.unwrap();
5281 store.stop().await.unwrap();
5282 }
5283
5284 #[tokio::test]
5285 async fn test_store_actor_exits_when_the_store_is_dropped() {
5286 let _serialized = SERIALIZED.lock().await;
5287 let store = open(memory_store(), &eager()).await;
5288 let io = Arc::downgrade(&store.io);
5289 let stopped = Arc::downgrade(&store.stopped);
5290 drop(store);
5291 timeout(WAIT, async {
5292 while stopped.strong_count() != 0 {
5293 tokio::task::yield_now().await;
5294 }
5295 })
5296 .await
5297 .unwrap();
5298 assert!(io.upgrade().is_none());
5299 }
5300
5301 #[tokio::test]
5302 async fn test_store_retains_first_terminal_error() {
5303 let _serialized = SERIALIZED.lock().await;
5304 let store = open(memory_store(), &eager()).await;
5305 let first = set_terminal(
5306 &store.terminal_error,
5307 CorruptedWalObjectSnafu { reason: "first" }.build(),
5308 );
5309 let second = set_terminal(
5310 &store.terminal_error,
5311 CorruptedWalObjectSnafu { reason: "second" }.build(),
5312 );
5313 assert!(Arc::ptr_eq(&first, &second));
5314 let region_id = region(1);
5315 let provider = provider(region_id);
5316 let errors = [
5317 store.append_batch(Vec::new()).await.unwrap_err(),
5318 store.latest_entry_id(&provider).unwrap_err(),
5319 store.read(&provider, 0).await.err().unwrap(),
5320 store.create_namespace(&provider).await.unwrap_err(),
5321 store.delete_namespace(&provider).await.unwrap_err(),
5322 store.list_namespaces().await.unwrap_err(),
5323 store.obsolete(&provider, region_id, 1).await.unwrap_err(),
5324 store.obsolete_all(&provider, region_id).await.unwrap_err(),
5325 store
5326 .entry(Vec::new(), 1, region_id, &provider)
5327 .unwrap_err(),
5328 ];
5329 for error in errors {
5330 match error {
5331 Error::ObjectStoreWal { source, .. } => assert!(Arc::ptr_eq(&first, &source)),
5332 error => panic!("{error:?}"),
5333 }
5334 }
5335 assert!(store.obsolete_entry_ids.lock().unwrap().is_empty());
5336 store.stop().await.unwrap();
5337 }
5338
5339 #[tokio::test]
5340 async fn test_store_recovery_bounds_concurrency_and_orders_by_sequence() {
5341 let _serialized = SERIALIZED.lock().await;
5342 let object_store = memory_store();
5343 let depth = RECOVERY_CONCURRENCY as u64;
5344 populate(&object_store, 2 * RECOVERY_CONCURRENCY, 3).await;
5345 let (io, mut parked) = ParkedIo::over(object_store);
5346 let objects = io.list().await.unwrap();
5347
5348 let mut fetch = {
5349 let io = io.clone();
5350 tokio::spawn(async move {
5351 fetch_footers(io.as_ref(), listing(objects), RECOVERY_CONCURRENCY)
5352 .await
5353 .unwrap()
5354 .objects
5355 .into_iter()
5356 .map(|fetched| fetched.object.object_seq)
5357 .collect::<Vec<_>>()
5358 })
5359 };
5360 let mut wave = Vec::new();
5361 for _ in 0..RECOVERY_CONCURRENCY {
5362 wave.push(timeout(WAIT, parked.recv()).await.unwrap().unwrap());
5363 }
5364 assert_eq!(
5365 (0..depth).collect::<Vec<_>>(),
5366 wave.iter()
5367 .map(|(object_seq, _)| *object_seq)
5368 .collect::<Vec<_>>()
5369 );
5370 for _ in 0..16 {
5372 tokio::task::yield_now().await;
5373 }
5374 assert!(parked.try_recv().is_err());
5375
5376 let mut replacements = Vec::new();
5378 for (expected_next, (_, release)) in (depth..2 * depth).zip(wave.into_iter().rev()) {
5379 release.send(true).unwrap();
5380 let (object_seq, release) = timeout(WAIT, parked.recv()).await.unwrap().unwrap();
5381 assert_eq!(expected_next, object_seq);
5382 assert!(!fetch.is_finished());
5383 replacements.push(release);
5384 }
5385 for release in replacements {
5386 release.send(true).unwrap();
5387 }
5388 assert_eq!(
5389 (0..2 * depth).collect::<Vec<u64>>(),
5390 timeout(WAIT, &mut fetch).await.unwrap().unwrap()
5391 );
5392 assert_eq!(
5393 RECOVERY_CONCURRENCY,
5394 io.max_in_flight.load(Ordering::SeqCst)
5395 );
5396 }
5397
5398 #[tokio::test]
5399 async fn test_store_recovery_abandons_pending_fetches_on_failure() {
5400 let _serialized = SERIALIZED.lock().await;
5401 let object_store = memory_store();
5402 let objects = 2 * RECOVERY_CONCURRENCY as u64;
5403 populate(&object_store, objects as usize, 3).await;
5404 let (io, mut parked) = ParkedIo::over(object_store.clone());
5405 let fetch = {
5406 let io = io.clone();
5407 tokio::spawn(async move {
5408 ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string()).await
5409 })
5410 };
5411 let mut wave = Vec::new();
5412 for _ in 0..RECOVERY_CONCURRENCY {
5413 wave.push(timeout(WAIT, parked.recv()).await.unwrap().unwrap());
5414 }
5415 let (failed_seq, release) = wave.pop().unwrap();
5416 release.send(false).unwrap();
5417 let error = timeout(WAIT, fetch).await.unwrap().unwrap().unwrap_err();
5418 assert!(
5419 matches!(&error, Error::WalObjectStore { operation: "read", path, .. }
5420 if path == &io.object_path(failed_seq))
5421 );
5422 assert_eq!(RetryHint::Retryable, error.retry_hint());
5423 for (_, release) in wave {
5424 assert!(release.is_closed());
5425 }
5426 assert!(parked.try_recv().is_err());
5427 let store = open(object_store, &eager()).await;
5428 assert_eq!(id(objects - 1, 1), latest(&store, region(1)));
5429 assert_eq!(
5432 objects + 1,
5433 store.catalog.read().unwrap().next_object_seq().unwrap()
5434 );
5435 store.stop().await.unwrap();
5436 }
5437
5438 fn listing(objects: Vec<ListedObject>) -> impl Stream<Item = Result<ListedObject>> {
5439 futures::stream::iter(objects.into_iter().map(Ok))
5440 }
5441
5442 const LISTING_PAGE: usize = 4;
5443
5444 struct PagedLister {
5448 inner: oio::Lister,
5449 served: usize,
5450 pages: watch::Receiver<usize>,
5451 fail_after: Option<usize>,
5452 panics: bool,
5453 dropped: Arc<AtomicBool>,
5454 }
5455
5456 impl oio::List for PagedLister {
5457 async fn next(&mut self) -> mock::Result<Option<oio::Entry>> {
5458 if self.fail_after == Some(self.served) {
5459 assert!(!self.panics, "injected listing panic");
5460 return Err(mock::Error::new(
5461 mock::ErrorKind::Unexpected,
5462 "injected listing failure",
5463 ));
5464 }
5465 let page = self.served / LISTING_PAGE;
5466 if self.pages.wait_for(|pages| *pages > page).await.is_err() {
5467 return Err(mock::Error::new(
5468 mock::ErrorKind::Unexpected,
5469 "listing abandoned",
5470 ));
5471 }
5472 self.served += 1;
5473 self.inner.next().await
5474 }
5475 }
5476
5477 impl Drop for PagedLister {
5478 fn drop(&mut self) {
5479 self.dropped.store(true, Ordering::SeqCst);
5480 }
5481 }
5482
5483 fn paged_listing(
5486 object_store: &ObjectStore,
5487 pages: watch::Receiver<usize>,
5488 fail_after: Option<usize>,
5489 panics: bool,
5490 read: mock::MockReaderFactory,
5491 ) -> (ObjectStoreIo, Arc<AtomicBool>) {
5492 let dropped = Arc::new(AtomicBool::new(false));
5493 let lister_dropped = dropped.clone();
5494 let mock = MockLayerBuilder::default()
5495 .lister_factory(Arc::new(move |_, _, inner| {
5496 Box::new(PagedLister {
5497 inner,
5498 served: 0,
5499 pages: pages.clone(),
5500 fail_after,
5501 panics,
5502 dropped: lister_dropped.clone(),
5503 })
5504 }))
5505 .reader_factory(read)
5506 .build()
5507 .unwrap();
5508 let io = ObjectStoreIo::new(object_store.clone().layer(mock), PREFIX).unwrap();
5509 (io, dropped)
5510 }
5511
5512 #[tokio::test]
5513 async fn test_store_recovery_reads_footers_while_the_listing_goes_on() {
5514 let _serialized = SERIALIZED.lock().await;
5515 let object_store = memory_store();
5516 populate(&object_store, 3 * LISTING_PAGE + 1, 3).await;
5517 let (pages, listed_pages) = watch::channel(1);
5520 let (io, _) = paged_listing(
5521 &object_store,
5522 listed_pages,
5523 None,
5524 false,
5525 Arc::new(move |_, _, inner| {
5526 pages.send_modify(|pages| *pages += 1);
5527 inner
5528 }),
5529 );
5530
5531 let recovered = timeout(WAIT, recover(&io)).await.unwrap().unwrap();
5532 let expected = recover_by_decoding(&ObjectStoreIo::new(object_store, PREFIX).unwrap())
5533 .await
5534 .unwrap();
5535 assert_same_recovery(&expected, &recovered);
5536 assert_eq!(3 * LISTING_PAGE as u64 + 1, recovered.recovered_objects);
5537 }
5538
5539 #[tokio::test]
5540 async fn test_store_recovery_fails_on_a_listing_that_fails_part_way() {
5541 let _serialized = SERIALIZED.lock().await;
5542 let object_store = memory_store();
5543 populate(&object_store, 3 * LISTING_PAGE, 3).await;
5544 for (panics, reason) in [
5545 (false, "injected listing failure"),
5546 (true, "the listing task did not complete"),
5547 ] {
5548 let (_pages, listed_pages) = watch::channel(usize::MAX);
5549 let (io, _) = paged_listing(
5550 &object_store,
5551 listed_pages,
5552 Some(LISTING_PAGE + 1),
5553 panics,
5554 Arc::new(|_, _, inner| inner),
5555 );
5556
5557 let error = timeout(WAIT, recover(&io)).await.unwrap().unwrap_err();
5558 assert!(
5559 matches!(&error, Error::WalObjectStore { operation: "list", error, .. }
5560 if error.to_string().contains(reason)),
5561 "unexpected error: {error:?}"
5562 );
5563 }
5564 }
5565
5566 #[tokio::test]
5567 async fn test_store_recovery_stops_the_listing_when_a_footer_read_fails() {
5568 let _serialized = SERIALIZED.lock().await;
5569 let object_store = memory_store();
5570 populate(&object_store, 3 * LISTING_PAGE, 3).await;
5571 let (_pages, listed_pages) = watch::channel(1);
5574 let (io, dropped) = paged_listing(
5575 &object_store,
5576 listed_pages,
5577 None,
5578 false,
5579 Arc::new(|_, _, _| Box::new(RefusedReader(ErrorKind::PermissionDenied))),
5580 );
5581
5582 let error = timeout(WAIT, recover(&io)).await.unwrap().unwrap_err();
5583 assert!(
5584 matches!(
5585 &error,
5586 Error::WalObjectStore {
5587 operation: "read",
5588 ..
5589 }
5590 ),
5591 "unexpected error: {error:?}"
5592 );
5593 timeout(WAIT, async {
5594 while !dropped.load(Ordering::SeqCst) {
5595 tokio::time::sleep(Duration::from_millis(10)).await;
5596 }
5597 })
5598 .await
5599 .unwrap();
5600 }
5601
5602 #[tokio::test]
5603 async fn test_store_recovery_reads_only_header_and_tail_of_large_object() {
5604 let _serialized = SERIALIZED.lock().await;
5605 let object_store = memory_store();
5606 put_records(
5607 &object_store,
5608 0,
5609 &[Record {
5610 region_id: region(1),
5611 entry_id: 1,
5612 payload: Bytes::from(vec![0; RECOVERY_TAIL_WINDOW * 2]),
5613 }],
5614 )
5615 .await;
5616 let path = object_path(&object_store, 0);
5617 corrupt_object(&object_store, &path, |bytes| bytes[HEADER_LEN + 20] ^= 1).await;
5618 let (io, reads) = RecordingIo::over(object_store);
5619 let object = io.list().await.unwrap().remove(0);
5620 let store = ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string())
5621 .await
5622 .unwrap();
5623 assert_eq!(1, latest(&store, region(1)));
5624 let mut reads = reads.lock().unwrap().clone();
5625 reads.sort_unstable();
5626 assert_eq!(
5627 vec![
5628 (0, 0, HEADER_LEN as u64),
5629 (
5630 0,
5631 object.size - RECOVERY_TAIL_WINDOW as u64,
5632 RECOVERY_TAIL_WINDOW as u64
5633 )
5634 ],
5635 reads
5636 );
5637 store.stop().await.unwrap();
5638 }
5639
5640 #[tokio::test]
5641 async fn test_store_recovery_rejects_short_read() {
5642 let _serialized = SERIALIZED.lock().await;
5643 let object_store = memory_store();
5644 put_object(&object_store, 0, region(1), &[1]).await;
5645 let io = ObjectStoreIo::new(object_store, PREFIX).unwrap();
5646 let mut object = io.list().await.unwrap().remove(0);
5647 object.size += 1;
5648 let error = fetch_footer(&io, &object).await.unwrap_err();
5649 assert_invalid_object(&error, &object.path, "fewer bytes than the listed");
5650 }
5651
5652 async fn read_entries(
5653 store: &ObjectStoreLogStore,
5654 region_id: RegionId,
5655 start: EntryId,
5656 ) -> Vec<Entry> {
5657 store
5658 .read(&provider(region_id), start)
5659 .await
5660 .unwrap()
5661 .try_collect::<Vec<_>>()
5662 .await
5663 .unwrap()
5664 .into_iter()
5665 .flatten()
5666 .collect()
5667 }
5668
5669 fn expected_entries(region_id: RegionId, entries: &[(EntryId, &str)]) -> Vec<Entry> {
5670 entries
5671 .iter()
5672 .map(|(entry_id, data)| {
5673 Entry::Naive(NaiveEntry {
5674 provider: provider(region_id),
5675 region_id,
5676 entry_id: *entry_id,
5677 data: data.as_bytes().to_vec(),
5678 })
5679 })
5680 .collect()
5681 }
5682
5683 #[tokio::test]
5684 async fn test_store_reads_only_region_segments_in_entry_order() {
5685 let _serialized = SERIALIZED.lock().await;
5686 let object_store = memory_store();
5687 let mut expected_ranges = HashMap::<_, Vec<_>>::new();
5688 for (seq, first) in [(1, 10), (3, 30), (7, 70)] {
5689 let records = [region(2), region(1)]
5690 .into_iter()
5691 .flat_map(|region_id| {
5692 (first..first + 3).map(move |entry_id| Record {
5693 region_id,
5694 entry_id,
5695 payload: Bytes::from(format!("r{}-e{entry_id}", region_id.region_number())),
5696 })
5697 })
5698 .collect::<Vec<_>>();
5699 put_records(&object_store, seq, &records).await;
5700 let bytes = object_store
5701 .read(&object_path(&object_store, seq))
5702 .await
5703 .unwrap()
5704 .to_vec();
5705 for entry in footer_of(&bytes).1 {
5706 expected_ranges.entry(entry.region_id).or_default().push((
5707 seq,
5708 entry.segment_offset,
5709 entry.segment_len,
5710 ));
5711 }
5712 }
5713 let (io, reads) = RecordingIo::over(object_store);
5714 let store = ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string())
5715 .await
5716 .unwrap();
5717 for number in [1, 2] {
5718 reads.lock().unwrap().clear();
5719 let entries = read_entries(&store, region(number), 11).await;
5720 let ids = [11, 12, 30, 31, 32, 70, 71, 72];
5721 let expected = ids
5722 .into_iter()
5723 .map(|entry_id| {
5724 Entry::Naive(NaiveEntry {
5725 provider: provider(region(number)),
5726 region_id: region(number),
5727 entry_id,
5728 data: format!("r{number}-e{entry_id}").into_bytes(),
5729 })
5730 })
5731 .collect::<Vec<_>>();
5732 assert_eq!(expected, entries);
5733 let mut reads = reads.lock().unwrap().clone();
5735 reads.sort_unstable();
5736 assert_eq!(expected_ranges[®ion(number)], reads);
5737 }
5738 reads.lock().unwrap().clear();
5739 assert_eq!(
5740 Vec::<Entry>::new(),
5741 read_entries(&store, region(1), 73).await
5742 );
5743 assert_eq!(
5744 Vec::<Entry>::new(),
5745 read_entries(&store, region(3), 0).await
5746 );
5747 assert!(reads.lock().unwrap().is_empty());
5748 assert_eq!(
5749 vec![provider(region(1)), provider(region(2))],
5750 store.list_namespaces().await.unwrap()
5751 );
5752 store.create_namespace(&provider(region(3))).await.unwrap();
5753 store.delete_namespace(&provider(region(1))).await.unwrap();
5754 assert_eq!(
5755 vec![provider(region(1)), provider(region(2))],
5756 store.list_namespaces().await.unwrap()
5757 );
5758 store.stop().await.unwrap();
5759 }
5760
5761 #[tokio::test]
5762 async fn test_store_obsolete_hides_entries_from_read_only() {
5763 let _serialized = SERIALIZED.lock().await;
5764 let object_store = memory_store();
5765 put_object(&object_store, 0, region(1), &[10, 11, 12]).await;
5766 put_object(&object_store, 1, region(1), &[20]).await;
5767 put_object(&object_store, 2, region(2), &[10]).await;
5768 let store = open(object_store.clone(), &eager()).await;
5769 let p = provider(region(1));
5770 let all = expected_entries(
5771 region(1),
5772 &[(10, "e10"), (11, "e11"), (12, "e12"), (20, "e20")],
5773 );
5774 store.obsolete(&p, region(1), 9).await.unwrap();
5775 assert_eq!(all, read_entries(&store, region(1), 0).await);
5776 store.obsolete(&p, region(1), 11).await.unwrap();
5777 let remaining = expected_entries(region(1), &[(12, "e12"), (20, "e20")]);
5778 assert_eq!(remaining, read_entries(&store, region(1), 0).await);
5779 assert_eq!(
5780 expected_entries(region(1), &[(20, "e20")]),
5781 read_entries(&store, region(1), 20).await
5782 );
5783 assert_eq!(20, latest(&store, region(1)));
5784 store.obsolete(&p, region(1), 10).await.unwrap();
5785 assert_eq!(remaining, read_entries(&store, region(1), 0).await);
5786 store.stop().await.unwrap();
5787
5788 let reopened = open(object_store.clone(), &eager()).await;
5790 assert_eq!(all, read_entries(&reopened, region(1), 0).await);
5791 reopened.obsolete(&p, region(1), 20).await.unwrap();
5792 assert_eq!(
5793 Vec::<Entry>::new(),
5794 read_entries(&reopened, region(1), 0).await
5795 );
5796 assert_eq!(20, latest(&reopened, region(1)));
5797
5798 reopened.obsolete_all(&p, region(1)).await.unwrap();
5801 reopened.obsolete(&p, region(1), 0).await.unwrap();
5802 assert_eq!(
5803 Vec::<Entry>::new(),
5804 read_entries(&reopened, region(1), 0).await
5805 );
5806 assert_eq!(20, latest(&reopened, region(1)));
5807 reopened.stop().await.unwrap();
5808 let reopened = open(object_store, &eager()).await;
5809 assert_eq!(
5810 Vec::<Entry>::new(),
5811 read_entries(&reopened, region(1), 0).await
5812 );
5813 reopened.obsolete(&p, region(1), 0).await.unwrap();
5814 assert_eq!(
5815 Vec::<Entry>::new(),
5816 read_entries(&reopened, region(1), 0).await
5817 );
5818 assert_eq!(
5819 expected_entries(region(2), &[(10, "e10")]),
5820 read_entries(&reopened, region(2), 0).await
5821 );
5822 reopened.stop().await.unwrap();
5823 }
5824
5825 #[tokio::test]
5826 async fn test_store_entry_and_watermarks_reject_another_region() {
5827 let _serialized = SERIALIZED.lock().await;
5828 let store = open(memory_store(), &eager()).await;
5829 let p = provider(region(2));
5830 let errors = [
5831 store
5832 .entry(b"payload".to_vec(), 7, region(1), &p)
5833 .unwrap_err(),
5834 store.obsolete(&p, region(1), 7).await.unwrap_err(),
5835 store.obsolete_all(&p, region(1)).await.unwrap_err(),
5836 ];
5837 for error in errors {
5838 assert!(
5839 matches!(&error, Error::MismatchedWalRegion { region_id, reason, .. }
5840 if *region_id == region(1) && reason == &format!("provider belongs to region {}", region(2)))
5841 );
5842 assert_eq!(
5843 common_error::status_code::StatusCode::InvalidArguments,
5844 error.status_code()
5845 );
5846 }
5847 assert!(store.obsolete_entry_ids.lock().unwrap().is_empty());
5848 assert_eq!(
5849 expected_entries(region(2), &[(7, "payload")]),
5850 vec![store.entry(b"payload".to_vec(), 7, region(2), &p).unwrap()]
5851 );
5852 assert!(store.list_namespaces().await.unwrap().is_empty());
5853 store.stop().await.unwrap();
5854 }
5855
5856 fn range_reads_of(reads: &RangeReads, object_seq: u64) -> Vec<(u64, u64, u64)> {
5859 reads
5860 .lock()
5861 .unwrap()
5862 .iter()
5863 .filter(|read| read.0 == object_seq)
5864 .copied()
5865 .collect()
5866 }
5867
5868 async fn write_corrupted_segment(object_store: &ObjectStore) -> (String, Vec<FooterEntry>) {
5872 put_records(
5873 object_store,
5874 0,
5875 &[
5876 Record {
5877 region_id: region(1),
5878 entry_id: 1,
5879 payload: Bytes::from_static(b"a1"),
5880 },
5881 Record {
5882 region_id: region(2),
5883 entry_id: 1,
5884 payload: Bytes::from_static(b"b1"),
5885 },
5886 ],
5887 )
5888 .await;
5889 put_object(object_store, 1, region(2), &[10]).await;
5890 let path = object_path(object_store, 0);
5891 let bytes = object_store.read(&path).await.unwrap().to_vec();
5892 let footer = footer_of(&bytes).1;
5893 let corrupt = &footer[1];
5894 assert_eq!(region(2), corrupt.region_id);
5895 corrupt_object(object_store, &path, |bytes| {
5896 bytes[(corrupt.segment_offset + corrupt.segment_len - 1) as usize] ^= 1;
5897 })
5898 .await;
5899 (path, footer)
5900 }
5901
5902 #[tokio::test]
5903 async fn test_store_skips_a_corrupted_segment_and_records_a_hole() {
5904 let _serialized = SERIALIZED.lock().await;
5905 let object_store = memory_store();
5906 let (path, footer) = write_corrupted_segment(&object_store).await;
5907 let (io, reads) = RecordingIo::over(object_store);
5908 let store = open_over(io, &eager()).await;
5909 assert_eq!(CorruptedSegmentAction::Skip, store.on_corrupted_segment);
5910 let skipped = METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get();
5911
5912 assert_eq!(
5915 expected_entries(region(1), &[(1, "a1")]),
5916 read_entries(&store, region(1), 0).await
5917 );
5918 assert_eq!(
5919 expected_entries(region(2), &[(10, "e10")]),
5920 read_entries(&store, region(2), 10).await
5921 );
5922 assert!(store.wal_holes(&provider(region(2))).unwrap().is_empty());
5923 assert_eq!(
5924 skipped,
5925 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
5926 );
5927
5928 reads.lock().unwrap().clear();
5931 assert_eq!(
5932 expected_entries(region(2), &[(10, "e10")]),
5933 read_entries(&store, region(2), 0).await
5934 );
5935 let corrupt = (0, footer[1].segment_offset, footer[1].segment_len);
5936 assert_eq!(vec![corrupt, corrupt], range_reads_of(&reads, 0));
5937 let hole = WalHole {
5938 path,
5939 object_seq: 0,
5940 min_entry_id: 1,
5941 max_entry_id: 1,
5942 };
5943 assert_eq!(
5944 vec![hole.clone()],
5945 store.wal_holes(&provider(region(2))).unwrap()
5946 );
5947 assert!(store.wal_holes(&provider(region(1))).unwrap().is_empty());
5948 assert_eq!(
5949 skipped + 1,
5950 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
5951 );
5952
5953 assert_eq!(
5955 expected_entries(region(2), &[(10, "e10")]),
5956 read_entries(&store, region(2), 0).await
5957 );
5958 assert_eq!(vec![hole], store.wal_holes(&provider(region(2))).unwrap());
5959 assert_eq!(
5960 skipped + 2,
5961 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
5962 );
5963 store.stop().await.unwrap();
5964 }
5965
5966 #[tokio::test]
5967 async fn test_store_corrupted_segment_fails_the_read_that_decodes_it() {
5968 let _serialized = SERIALIZED.lock().await;
5969 let object_store = memory_store();
5970 let (path, footer) = write_corrupted_segment(&object_store).await;
5971 let (io, reads) = RecordingIo::over(object_store);
5972 let config = on_corruption(CorruptedSegmentAction::Fail, eager());
5973 let store = open_over(io, &config).await;
5974 let skipped = METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get();
5975 assert_eq!(1, latest(&store, region(1)));
5976 assert_eq!(10, latest(&store, region(2)));
5977 reads.lock().unwrap().clear();
5978 assert_eq!(
5979 expected_entries(region(1), &[(1, "a1")]),
5980 read_entries(&store, region(1), 0).await
5981 );
5982 assert_eq!(
5983 vec![(0, footer[0].segment_offset, footer[0].segment_len)],
5984 *reads.lock().unwrap()
5985 );
5986 assert_eq!(
5987 expected_entries(region(2), &[(10, "e10")]),
5988 read_entries(&store, region(2), 10).await
5989 );
5990 reads.lock().unwrap().clear();
5991 let error = store
5992 .read(&provider(region(2)), 0)
5993 .await
5994 .unwrap()
5995 .try_collect::<Vec<_>>()
5996 .await
5997 .unwrap_err();
5998 assert_invalid_object(
5999 &error,
6000 &path,
6001 &format!("segment of region {} checksum mismatch", region(2)),
6002 );
6003 let corrupt = (0, footer[1].segment_offset, footer[1].segment_len);
6004 assert_eq!(vec![corrupt, corrupt], range_reads_of(&reads, 0));
6005 assert!(store.wal_holes(&provider(region(2))).unwrap().is_empty());
6006 assert_eq!(
6007 skipped,
6008 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
6009 );
6010 store.stop().await.unwrap();
6011 }
6012
6013 #[tokio::test]
6014 async fn test_store_reads_a_segment_whose_first_download_was_damaged() {
6015 let _serialized = SERIALIZED.lock().await;
6016 for on_corrupted_segment in [CorruptedSegmentAction::Skip, CorruptedSegmentAction::Fail] {
6017 let object_store = memory_store();
6018 put_object(&object_store, 0, region(1), &[1, 2]).await;
6019 let (io, reads) = RecordingIo::over(object_store);
6020 let config = on_corruption(on_corrupted_segment, eager());
6021 let store = open_over(io.clone(), &config).await;
6022 let skipped = METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get();
6023
6024 reads.lock().unwrap().clear();
6027 io.damage_next_range_read.store(true, Ordering::Relaxed);
6028 assert_eq!(
6029 expected_entries(region(1), &[(1, "e1"), (2, "e2")]),
6030 read_entries(&store, region(1), 0).await
6031 );
6032 assert_eq!(2, reads.lock().unwrap().len());
6033 assert!(store.wal_holes(&provider(region(1))).unwrap().is_empty());
6034 assert_eq!(
6035 skipped,
6036 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
6037 );
6038 store.stop().await.unwrap();
6039 }
6040 }
6041
6042 #[tokio::test]
6043 async fn test_store_failed_segment_fetch_fails_the_read() {
6044 let _serialized = SERIALIZED.lock().await;
6045 for on_corrupted_segment in [CorruptedSegmentAction::Skip, CorruptedSegmentAction::Fail] {
6046 let object_store = memory_store();
6047 put_object(&object_store, 0, region(1), &[1]).await;
6048 let path = object_path(&object_store, 0);
6049 let (io, _) = RecordingIo::over(object_store.clone());
6050 let config = on_corruption(on_corrupted_segment, eager());
6051 let store = open_over(io, &config).await;
6052 let skipped = METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get();
6053
6054 object_store.delete(&path).await.unwrap();
6055 let error = store
6056 .read(&provider(region(1)), 0)
6057 .await
6058 .unwrap()
6059 .try_collect::<Vec<_>>()
6060 .await
6061 .unwrap_err();
6062 assert!(
6063 matches!(&error, Error::WalObjectStore { path: actual, .. } if *actual == path),
6064 "unexpected error: {error:?}"
6065 );
6066 assert!(store.wal_holes(&provider(region(1))).unwrap().is_empty());
6067 assert_eq!(
6068 skipped,
6069 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
6070 );
6071 store.stop().await.unwrap();
6072 }
6073 }
6074
6075 fn object_entries(object_seqs: impl IntoIterator<Item = u64>) -> Vec<Entry> {
6078 object_seqs
6079 .into_iter()
6080 .map(|object_seq| {
6081 let entry_id = object_seq + 1;
6082 Entry::Naive(NaiveEntry {
6083 provider: provider(region(1)),
6084 region_id: region(1),
6085 entry_id,
6086 data: format!("e{entry_id}").into_bytes(),
6087 })
6088 })
6089 .collect()
6090 }
6091
6092 async fn put_object_per_entry(object_store: &ObjectStore, objects: u64) {
6095 for object_seq in 0..objects {
6096 put_object(object_store, object_seq, region(1), &[object_seq + 1]).await;
6097 }
6098 }
6099
6100 async fn open_parking_range_reads(
6103 objects: u64,
6104 ) -> (
6105 Arc<ObjectStoreLogStore>,
6106 Arc<ParkedIo>,
6107 mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
6108 ) {
6109 let object_store = memory_store();
6110 put_object_per_entry(&object_store, objects).await;
6111 let (io, mut parked) = ParkedIo::over(object_store);
6112 let mut opening = {
6113 let io = io.clone();
6114 tokio::spawn(async move {
6115 ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string()).await
6116 })
6117 };
6118 let store = loop {
6119 tokio::select! {
6120 opened = &mut opening => break opened.unwrap().unwrap(),
6121 Some((_, release)) = parked.recv() => release.send(true).unwrap(),
6122 }
6123 };
6124 io.range_reads_parked.store(true, Ordering::SeqCst);
6125 io.max_in_flight.store(0, Ordering::SeqCst);
6126 (store, io, parked)
6127 }
6128
6129 async fn wait_until(settled: impl Fn() -> bool) {
6131 let deadline = Instant::now() + WAIT;
6132 while !settled() {
6133 assert!(Instant::now() < deadline, "the condition never held");
6134 tokio::time::sleep(Duration::from_millis(1)).await;
6135 }
6136 }
6137
6138 async fn assert_nothing_parks(
6139 parked: &mut mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
6140 ) {
6141 assert!(
6142 timeout(Duration::from_millis(100), parked.recv())
6143 .await
6144 .is_err()
6145 );
6146 }
6147
6148 async fn next_batch(stream: &mut SendableEntryStream<'static, Entry, Error>) -> Vec<Entry> {
6149 timeout(WAIT, stream.next())
6150 .await
6151 .unwrap()
6152 .unwrap()
6153 .unwrap()
6154 }
6155
6156 #[tokio::test]
6157 async fn test_store_read_prefetches_up_to_the_depth_in_order() {
6158 let _serialized = SERIALIZED.lock().await;
6159 let depth = READ_PREFETCH_DEPTH as u64;
6160 let objects = depth + 4;
6161 let (store, io, mut parked) = open_parking_range_reads(objects).await;
6162 let reads = METRIC_OBJECT_STORE_WAL_READ_SECONDS.get_sample_count();
6163 let mut stream = store.read(&provider(region(1)), 0).await.unwrap();
6164
6165 assert!(stream.next().now_or_never().is_none());
6167 let mut wave = parked_operations(&mut parked, READ_PREFETCH_DEPTH).await;
6168 assert_eq!(
6169 (0..depth).collect::<BTreeSet<_>>(),
6170 wave.keys().copied().collect()
6171 );
6172 assert_nothing_parks(&mut parked).await;
6173
6174 for object_seq in (1..depth).rev() {
6177 wave.remove(&object_seq).unwrap().send(true).unwrap();
6178 }
6179 wait_until(|| io.in_flight.load(Ordering::SeqCst) == 1).await;
6180 assert!(stream.next().now_or_never().is_none());
6181 assert_nothing_parks(&mut parked).await;
6182 wave.remove(&0).unwrap().send(true).unwrap();
6183 assert_eq!(object_entries([0]), next_batch(&mut stream).await);
6184
6185 assert_nothing_parks(&mut parked).await;
6188 let rest = tokio::spawn(stream.try_collect::<Vec<_>>());
6189 let refills = parked_operations(&mut parked, (objects - depth) as usize).await;
6190 assert_eq!(
6191 (depth..objects).collect::<BTreeSet<_>>(),
6192 refills.keys().copied().collect()
6193 );
6194 for release in refills.into_values() {
6195 release.send(true).unwrap();
6196 }
6197 let rest = timeout(WAIT, rest).await.unwrap().unwrap().unwrap();
6198 assert_eq!(
6199 object_entries(1..objects),
6200 rest.into_iter().flatten().collect::<Vec<_>>()
6201 );
6202 assert_eq!(READ_PREFETCH_DEPTH, io.max_in_flight.load(Ordering::SeqCst));
6203 assert_eq!(
6204 reads + 1,
6205 METRIC_OBJECT_STORE_WAL_READ_SECONDS.get_sample_count()
6206 );
6207 store.stop().await.unwrap();
6208 }
6209
6210 #[tokio::test]
6211 async fn test_store_read_fails_at_a_prefetched_object_in_order() {
6212 let _serialized = SERIALIZED.lock().await;
6213 let depth = READ_PREFETCH_DEPTH as u64;
6214 let objects = depth + 4;
6215 let failed = 5;
6216 let (store, io, mut parked) = open_parking_range_reads(objects).await;
6217 let reads = METRIC_OBJECT_STORE_WAL_READ_SECONDS.get_sample_count();
6218 let mut stream = store.read(&provider(region(1)), 0).await.unwrap();
6219 assert!(stream.next().now_or_never().is_none());
6220 let mut wave = parked_operations(&mut parked, READ_PREFETCH_DEPTH).await;
6221
6222 wave.remove(&failed).unwrap().send(false).unwrap();
6224 wait_until(|| io.in_flight.load(Ordering::SeqCst) == READ_PREFETCH_DEPTH - 1).await;
6225 for release in wave.into_values() {
6226 release.send(true).unwrap();
6227 }
6228 let mut refills = Vec::new();
6229 for object_seq in 0..failed {
6230 assert_eq!(object_entries([object_seq]), next_batch(&mut stream).await);
6231 if object_seq > 0 {
6232 refills.push(next_parked_operation(&mut parked).await);
6233 }
6234 }
6235 let error = timeout(WAIT, stream.next())
6236 .await
6237 .unwrap()
6238 .unwrap()
6239 .unwrap_err();
6240 assert!(
6241 matches!(&error, Error::WalObjectStore { operation: "read", path, .. }
6242 if *path == io.object_path(failed)),
6243 "unexpected error: {error:?}"
6244 );
6245 assert!(timeout(WAIT, stream.next()).await.unwrap().is_none());
6246
6247 assert_eq!(
6249 (depth..objects).collect::<Vec<_>>(),
6250 refills
6251 .iter()
6252 .map(|(object_seq, _)| *object_seq)
6253 .collect::<Vec<_>>()
6254 );
6255 for (_, mut release) in refills {
6256 timeout(WAIT, release.closed()).await.unwrap();
6257 }
6258 assert_nothing_parks(&mut parked).await;
6259 assert_eq!(
6260 reads,
6261 METRIC_OBJECT_STORE_WAL_READ_SECONDS.get_sample_count()
6262 );
6263 store.stop().await.unwrap();
6264 }
6265
6266 #[tokio::test]
6267 async fn test_store_read_skips_prefetched_collected_and_corrupted_objects() {
6268 let _serialized = SERIALIZED.lock().await;
6269 let objects = READ_PREFETCH_DEPTH as u64 + 4;
6270 let (corrupted, collected) = (3, 9);
6271 let object_store = memory_store();
6272 put_object_per_entry(&object_store, objects).await;
6273 let path = object_path(&object_store, corrupted);
6274 let bytes = object_store.read(&path).await.unwrap().to_vec();
6275 let segment = footer_of(&bytes).1.remove(0);
6276 corrupt_object(&object_store, &path, |bytes| {
6277 bytes[(segment.segment_offset + segment.segment_len - 1) as usize] ^= 1;
6278 })
6279 .await;
6280 let store = open(object_store.clone(), &eager()).await;
6281 assert_eq!(CorruptedSegmentAction::Skip, store.on_corrupted_segment);
6282 let skipped = METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get();
6283
6284 let stream = store.read(&provider(region(1)), 0).await.unwrap();
6286 store.catalog.write().unwrap().remove_object(collected);
6287 object_store
6288 .delete(&object_path(&object_store, collected))
6289 .await
6290 .unwrap();
6291 let entries = timeout(WAIT, stream.try_collect::<Vec<_>>())
6292 .await
6293 .unwrap()
6294 .unwrap()
6295 .into_iter()
6296 .flatten()
6297 .collect::<Vec<_>>();
6298 assert_eq!(
6299 object_entries((0..objects).filter(|seq| ![corrupted, collected].contains(seq))),
6300 entries
6301 );
6302 assert_eq!(
6303 vec![WalHole {
6304 path,
6305 object_seq: corrupted,
6306 min_entry_id: corrupted + 1,
6307 max_entry_id: corrupted + 1,
6308 }],
6309 store.wal_holes(&provider(region(1))).unwrap()
6310 );
6311 assert_eq!(
6312 skipped + 1,
6313 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
6314 );
6315 store.stop().await.unwrap();
6316 }
6317
6318 #[tokio::test]
6319 async fn test_store_dropped_read_abandons_its_prefetches() {
6320 let _serialized = SERIALIZED.lock().await;
6321 let objects = READ_PREFETCH_DEPTH as u64 + 4;
6322 let (store, _io, mut parked) = open_parking_range_reads(objects).await;
6323 let reads = METRIC_OBJECT_STORE_WAL_READ_SECONDS.get_sample_count();
6324 let mut stream = store.read(&provider(region(1)), 0).await.unwrap();
6325 assert!(stream.next().now_or_never().is_none());
6326 let mut wave = parked_operations(&mut parked, READ_PREFETCH_DEPTH).await;
6327 wave.remove(&0).unwrap().send(true).unwrap();
6328 assert_eq!(object_entries([0]), next_batch(&mut stream).await);
6329
6330 drop(stream);
6331 for (_, mut release) in wave {
6332 timeout(WAIT, release.closed()).await.unwrap();
6333 }
6334 assert_nothing_parks(&mut parked).await;
6335 assert_eq!(
6336 reads,
6337 METRIC_OBJECT_STORE_WAL_READ_SECONDS.get_sample_count()
6338 );
6339 store.stop().await.unwrap();
6340 }
6341
6342 #[tokio::test]
6343 async fn test_store_read_fails_at_a_panicking_fetch() {
6344 let _serialized = SERIALIZED.lock().await;
6345 let object_store = memory_store();
6346 put_object_per_entry(&object_store, 3).await;
6347 let (io, _) = RecordingIo::over(object_store);
6348 let store = open_over(io.clone(), &eager()).await;
6349 *io.panic_range_read_of.lock().unwrap() = Some(1);
6350 let mut stream = store.read(&provider(region(1)), 0).await.unwrap();
6351 assert_eq!(object_entries([0]), next_batch(&mut stream).await);
6352 let error = timeout(WAIT, stream.next())
6353 .await
6354 .unwrap()
6355 .unwrap()
6356 .unwrap_err();
6357 assert!(
6358 matches!(&error, Error::WalSegmentFetch { path, .. } if *path == io.object_path(1)),
6359 "unexpected error: {error:?}"
6360 );
6361 assert!(timeout(WAIT, stream.next()).await.unwrap().is_none());
6362 store.stop().await.unwrap();
6363 }
6364
6365 #[tokio::test]
6366 async fn test_store_reads_fetch_ahead_within_a_shared_budget() {
6367 let _serialized = SERIALIZED.lock().await;
6368 let depth = READ_PREFETCH_DEPTH as u64;
6369 let objects = depth + 4;
6370 let (store, _io, mut parked) = open_parking_range_reads(objects).await;
6371
6372 let held = store
6375 .read_prefetch_budget
6376 .clone()
6377 .try_acquire_many_owned(READ_PREFETCH_BYTES as u32)
6378 .unwrap();
6379 let mut stream = store.read(&provider(region(1)), 0).await.unwrap();
6380 assert!(stream.next().now_or_never().is_none());
6381 let (object_seq, release) = next_parked_operation(&mut parked).await;
6382 assert_eq!(0, object_seq);
6383 assert_nothing_parks(&mut parked).await;
6384 release.send(true).unwrap();
6385 assert_eq!(object_entries([0]), next_batch(&mut stream).await);
6386
6387 drop(held);
6389 assert!(stream.next().now_or_never().is_none());
6390 let wave = parked_operations(&mut parked, READ_PREFETCH_DEPTH).await;
6391 assert_eq!(
6392 (1..=depth).collect::<BTreeSet<_>>(),
6393 wave.keys().copied().collect()
6394 );
6395 assert_nothing_parks(&mut parked).await;
6396 for release in wave.into_values() {
6397 release.send(true).unwrap();
6398 }
6399 let rest = tokio::spawn(stream.try_collect::<Vec<_>>());
6400 for release in parked_operations(&mut parked, (objects - depth - 1) as usize)
6401 .await
6402 .into_values()
6403 {
6404 release.send(true).unwrap();
6405 }
6406 let rest = timeout(WAIT, rest).await.unwrap().unwrap().unwrap();
6407 assert_eq!(
6408 object_entries(1..objects),
6409 rest.into_iter().flatten().collect::<Vec<_>>()
6410 );
6411 assert_eq!(
6412 READ_PREFETCH_BYTES,
6413 store.read_prefetch_budget.available_permits()
6414 );
6415 store.stop().await.unwrap();
6416 }
6417
6418 #[tokio::test]
6419 async fn test_store_dropped_read_keeps_reservations_until_its_fetches_end() {
6420 let _serialized = SERIALIZED.lock().await;
6421 let object_store = memory_store();
6422 put_object_per_entry(&object_store, 2).await;
6423 let (io, _) = RecordingIo::over(object_store);
6424 let store = open_over(io.clone(), &eager()).await;
6425 let budget = store.read_prefetch_budget.clone();
6426 let (began_tx, began_rx) = std::sync::mpsc::channel();
6427 let (release_tx, release_rx) = std::sync::mpsc::channel();
6428 *io.block_range_read_of.lock().unwrap() = Some((1, began_tx, release_rx));
6429
6430 let mut stream = store.read(&provider(region(1)), 0).await.unwrap();
6433 let _ = stream.next().now_or_never();
6434 began_rx.recv_timeout(WAIT).unwrap();
6435 assert!(budget.available_permits() < READ_PREFETCH_BYTES);
6436
6437 drop(stream);
6439 for _ in 0..16 {
6440 tokio::task::yield_now().await;
6441 }
6442 assert!(budget.available_permits() < READ_PREFETCH_BYTES);
6443 release_tx.send(()).unwrap();
6444 wait_until(|| budget.available_permits() == READ_PREFETCH_BYTES).await;
6445 store.stop().await.unwrap();
6446 }
6447
6448 #[tokio::test]
6449 async fn test_store_assigns_ids_from_the_object_sequence() {
6450 let _serialized = SERIALIZED.lock().await;
6451 let store = open(memory_store(), &manual()).await;
6452 let region_one = region(1);
6453 let region_two = region(2);
6454
6455 let first = spawn_append_batch(
6459 &store,
6460 vec![
6461 entry(&store, region_one, "a1"),
6462 entry(&store, region_two, "b1"),
6463 entry(&store, region_one, "a2"),
6464 ],
6465 );
6466 store.wait_for_admitted_appends(1).await.unwrap();
6467 let second = spawn_append_batch(&store, vec![entry(&store, region_two, "b2")]);
6468 store.wait_for_admitted_appends(2).await.unwrap();
6469 assert!(!first.is_finished());
6470 assert_eq!(0, latest(&store, region_one));
6471 store.seal_open_batch().await.unwrap();
6472 assert_eq!(
6473 HashMap::from([(region_one, id(1, 2)), (region_two, id(1, 1))]),
6474 first.await.unwrap().unwrap().last_entry_ids
6475 );
6476 assert_eq!(
6477 HashMap::from([(region_two, id(1, 2))]),
6478 second.await.unwrap().unwrap().last_entry_ids
6479 );
6480
6481 let third = spawn_append_batch(
6483 &store,
6484 vec![
6485 entry(&store, region_two, "b3"),
6486 entry(&store, region_one, "a3"),
6487 ],
6488 );
6489 store.wait_for_admitted_appends(3).await.unwrap();
6490 store.seal_open_batch().await.unwrap();
6491 assert_eq!(
6492 HashMap::from([(region_one, id(2, 1)), (region_two, id(2, 1))]),
6493 third.await.unwrap().unwrap().last_entry_ids
6494 );
6495 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
6496 assert_eq!(
6497 expected_entries(
6498 region_one,
6499 &[(id(1, 1), "a1"), (id(1, 2), "a2"), (id(2, 1), "a3")]
6500 ),
6501 read_entries(&store, region_one, 1).await
6502 );
6503 assert_eq!(
6504 expected_entries(region_two, &[(id(1, 2), "b2"), (id(2, 1), "b3")]),
6505 read_entries(&store, region_two, id(1, 2)).await
6506 );
6507 assert_eq!(id(2, 1), latest(&store, region_one));
6508 assert_eq!(id(2, 1), latest(&store, region_two));
6509 store.stop().await.unwrap();
6510 }
6511
6512 #[tokio::test]
6513 async fn test_store_seals_when_a_region_exhausts_its_positions() {
6514 let _serialized = SERIALIZED.lock().await;
6515 let store = open(memory_store(), &manual()).await;
6516 let region_id = region(1);
6517 let entries_of = |count: u64| {
6518 (0..count)
6519 .map(|_| entry(&store, region_id, ""))
6520 .collect::<Vec<_>>()
6521 };
6522
6523 let full = spawn_append_batch(&store, entries_of(POSITION_LIMIT - 1));
6525 store.wait_for_admitted_appends(1).await.unwrap();
6526 assert_eq!(vec![0], object_seqs(store.io.as_ref()).await);
6527
6528 let next = spawn_append_batch(&store, vec![entry(&store, region_id, "next")]);
6531 store.wait_for_admitted_appends(2).await.unwrap();
6532 let response = timeout(WAIT, full).await.unwrap().unwrap().unwrap();
6533 assert_eq!(
6534 HashMap::from([(region_id, id(1, POSITION_LIMIT - 1))]),
6535 response.last_entry_ids
6536 );
6537 assert_eq!(vec![1], collected_object_seqs(&store).await);
6538
6539 let error = store
6543 .append_batch(entries_of(POSITION_LIMIT))
6544 .await
6545 .unwrap_err();
6546 assert!(
6547 matches!(error, Error::WalEntryPositionExhausted { .. }),
6548 "unexpected error: {error:?}"
6549 );
6550 let response = timeout(WAIT, next).await.unwrap().unwrap().unwrap();
6551 assert_eq!(
6552 HashMap::from([(region_id, id(2, 1))]),
6553 response.last_entry_ids
6554 );
6555 let after = spawn_append_batch(&store, vec![entry(&store, region_id, "after")]);
6556 store.wait_for_admitted_appends(3).await.unwrap();
6557 store.seal_open_batch().await.unwrap();
6558 let response = timeout(WAIT, after).await.unwrap().unwrap().unwrap();
6559 assert_eq!(
6560 HashMap::from([(region_id, id(3, 1))]),
6561 response.last_entry_ids
6562 );
6563 assert_eq!(vec![1, 2, 3], collected_object_seqs(&store).await);
6564 assert_eq!(
6565 expected_entries(region_id, &[(id(2, 1), "next"), (id(3, 1), "after")]),
6566 read_entries(&store, region_id, id(2, 1)).await
6567 );
6568 store.stop().await.unwrap();
6569 }
6570
6571 #[tokio::test]
6572 async fn test_store_flushes_at_the_minimum_interval() {
6573 let _serialized = SERIALIZED.lock().await;
6574 let interval = MIN_FLUSH_INTERVAL;
6579 let store = open(memory_store(), &config(interval, u64::MAX)).await;
6580 let data = ["a1", "a2", "a3"];
6581 for (index, data) in data.iter().enumerate() {
6582 tokio::time::sleep(interval * 5).await;
6583 timeout(WAIT, append(&store, region(1), data))
6584 .await
6585 .unwrap()
6586 .unwrap();
6587 let expected_seqs = (1..=index as u64 + 1).collect::<Vec<_>>();
6589 assert_eq!(expected_seqs, collected_object_seqs(&store).await);
6590 }
6591
6592 tokio::time::sleep(interval * 5).await;
6594 let seqs = collected_object_seqs(&store).await;
6595 assert_eq!(vec![1, 2, 3], seqs);
6596 for object_seq in seqs {
6597 let bytes = store.io.get(object_seq).await.unwrap();
6598 let decoded = decode_object(&bytes).unwrap();
6599 assert_eq!(1, decoded.records.len(), "object {object_seq} is empty");
6600 }
6601 assert_eq!(
6602 expected_entries(
6603 region(1),
6604 &[(id(1, 1), "a1"), (id(2, 1), "a2"), (id(3, 1), "a3")]
6605 ),
6606 read_entries(&store, region(1), 1).await
6607 );
6608 }
6609
6610 #[tokio::test]
6611 async fn test_store_rejects_entries_of_another_region() {
6612 let _serialized = SERIALIZED.lock().await;
6613 let store = open(memory_store(), &manual()).await;
6614 let region_id = region(1);
6615
6616 let entry = Entry::Naive(NaiveEntry {
6617 provider: provider(region_id),
6618 region_id: region(2),
6619 entry_id: 0,
6620 data: Vec::new(),
6621 });
6622 let error = store.append_batch(vec![entry]).await.unwrap_err();
6623 assert!(
6624 matches!(error, Error::MismatchedWalRegion { .. }),
6625 "unexpected error: {error:?}"
6626 );
6627 let incomplete = Entry::MultiplePart(MultiplePartEntry {
6628 provider: provider(region_id),
6629 region_id,
6630 entry_id: 0,
6631 headers: vec![MultiplePartHeader::First],
6632 parts: vec![b"a1".to_vec()],
6633 });
6634 let error = store.append_batch(vec![incomplete]).await.unwrap_err();
6635 assert!(
6636 matches!(error, Error::IncompleteWalEntry { region_id: actual, .. } if actual == region_id),
6637 "unexpected error: {error:?}"
6638 );
6639 assert_eq!(
6640 common_error::status_code::StatusCode::InvalidArguments,
6641 error.status_code()
6642 );
6643 assert!(
6644 store
6645 .append_batch(Vec::new())
6646 .await
6647 .unwrap()
6648 .last_entry_ids
6649 .is_empty()
6650 );
6651 store.seal_open_batch().await.unwrap();
6653 assert_eq!(vec![0], object_seqs(store.io.as_ref()).await);
6654 store.stop().await.unwrap();
6655 }
6656
6657 #[tokio::test]
6658 async fn test_store_pipelines_creates_and_acknowledges_in_sequence_order() {
6659 let _serialized = SERIALIZED.lock().await;
6660 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
6661 let region_id = region(1);
6662 let appends = spawn_appends(&store, region_id, MAX_IN_FLIGHT_CREATES + 2).await;
6663
6664 let mut releases = parked_operations(&mut parked, MAX_IN_FLIGHT_CREATES).await;
6666 assert_eq!(
6667 (1..=MAX_IN_FLIGHT_CREATES as u64).collect::<BTreeSet<_>>(),
6668 releases.keys().copied().collect::<BTreeSet<_>>()
6669 );
6670 round_trip_actor(&store).await;
6671 assert!(parked.try_recv().is_err());
6672 assert!(appends.iter().all(|append| !append.is_finished()));
6673
6674 for (released, expected_next) in [(3, 5), (2, 6)] {
6677 releases.remove(&released).unwrap().send(true).unwrap();
6678 let (object_seq, release) = next_parked_operation(&mut parked).await;
6679 assert_eq!(expected_next, object_seq);
6680 releases.insert(object_seq, release);
6681 }
6682 round_trip_actor(&store).await;
6683 assert_eq!(vec![0, 2, 3], object_seqs(io.as_ref()).await);
6684 assert!(appends.iter().all(|append| !append.is_finished()));
6685 assert_eq!(0, latest(&store, region_id));
6686
6687 releases.remove(&1).unwrap().send(true).unwrap();
6689 let mut appends = appends.into_iter();
6690 for object_seq in 1..4 {
6691 let response = timeout(WAIT, appends.next().unwrap())
6692 .await
6693 .unwrap()
6694 .unwrap()
6695 .unwrap();
6696 assert_eq!(
6697 HashMap::from([(region_id, id(object_seq, 1))]),
6698 response.last_entry_ids
6699 );
6700 }
6701 assert_eq!(id(3, 1), latest(&store, region_id));
6702
6703 releases.remove(&4).unwrap().send(true).unwrap();
6705 releases.remove(&6).unwrap().send(true).unwrap();
6706 let fourth = timeout(WAIT, appends.next().unwrap())
6707 .await
6708 .unwrap()
6709 .unwrap()
6710 .unwrap();
6711 assert_eq!(
6712 HashMap::from([(region_id, id(4, 1))]),
6713 fourth.last_entry_ids
6714 );
6715 let appends = appends.collect::<Vec<_>>();
6716 round_trip_actor(&store).await;
6717 assert!(appends.iter().all(|append| !append.is_finished()));
6718 releases.remove(&5).unwrap().send(true).unwrap();
6719 for (append, object_seq) in appends.into_iter().zip(5..) {
6720 let response = timeout(WAIT, append).await.unwrap().unwrap().unwrap();
6721 assert_eq!(
6722 HashMap::from([(region_id, id(object_seq, 1))]),
6723 response.last_entry_ids
6724 );
6725 }
6726 assert_eq!(
6727 MAX_IN_FLIGHT_CREATES,
6728 io.max_in_flight.load(Ordering::SeqCst)
6729 );
6730 assert_eq!(vec![1, 2, 3, 4, 5, 6], collected_object_seqs(&store).await);
6731 for object_seq in 1..=6 {
6734 let header = decode_header(&io.get(object_seq).await.unwrap()).unwrap();
6735 assert_eq!(
6736 Some(object_seq - 1),
6737 header.prev.map(|link| link.object_seq)
6738 );
6739 }
6740 assert_eq!(
6741 expected_entries(
6742 region_id,
6743 &[
6744 (id(1, 1), "a1"),
6745 (id(2, 1), "a2"),
6746 (id(3, 1), "a3"),
6747 (id(4, 1), "a4"),
6748 (id(5, 1), "a5"),
6749 (id(6, 1), "a6")
6750 ]
6751 ),
6752 read_entries(&store, region_id, 1).await
6753 );
6754 }
6755
6756 #[tokio::test]
6757 async fn test_store_transient_failure_fails_every_batch_that_is_not_indexed() {
6758 let _serialized = SERIALIZED.lock().await;
6759 let (store, _, mut parked) = open_parking_creates(memory_store(), &eager()).await;
6760 let region_id = region(1);
6761 let appends = spawn_appends(&store, region_id, MAX_IN_FLIGHT_CREATES + 1).await;
6763 let mut releases = parked_operations(&mut parked, MAX_IN_FLIGHT_CREATES).await;
6764 round_trip_actor(&store).await;
6765 assert!(parked.try_recv().is_err());
6766
6767 releases.remove(&1).unwrap().send(false).unwrap();
6770 for append in appends {
6771 let error = timeout(WAIT, append).await.unwrap().unwrap().unwrap_err();
6772 assert!(
6773 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
6774 "unexpected error: {error:?}"
6775 );
6776 assert_eq!(RetryHint::Retryable, error.retry_hint());
6777 }
6778 assert_eq!(0, latest(&store, region_id));
6779
6780 let retries = spawn_appends(&store, region_id, 2).await;
6785 let (object_seq, first) = next_parked_operation(&mut parked).await;
6786 assert_eq!(6, object_seq);
6787 round_trip_actor(&store).await;
6788 assert!(parked.try_recv().is_err());
6789 for release in releases.into_values() {
6790 release.send(true).unwrap();
6791 }
6792 let (object_seq, second) = next_parked_operation(&mut parked).await;
6793 assert_eq!(7, object_seq);
6794 first.send(true).unwrap();
6795 second.send(true).unwrap();
6796 for (retry, object_seq) in retries.into_iter().zip(6..) {
6797 let response = timeout(WAIT, retry).await.unwrap().unwrap().unwrap();
6798 assert_eq!(
6799 HashMap::from([(region_id, id(object_seq, 1))]),
6800 response.last_entry_ids
6801 );
6802 }
6803 assert_eq!(vec![2, 3, 4, 6, 7], collected_object_seqs(&store).await);
6804 assert_eq!(
6805 expected_entries(region_id, &[(id(6, 1), "a1"), (id(7, 1), "a2")]),
6806 read_entries(&store, region_id, 1).await
6807 );
6808 }
6809
6810 #[tokio::test]
6811 async fn test_store_transient_failure_before_a_created_object_keeps_it_off_the_chain() {
6812 let _serialized = SERIALIZED.lock().await;
6813 let object_store = memory_store();
6814 let (store, io, mut parked) = open_parking_creates(object_store.clone(), &eager()).await;
6815 let region_id = region(1);
6816 let appends = spawn_appends(&store, region_id, 2).await;
6817 let mut releases = parked_operations(&mut parked, 2).await;
6818
6819 releases.remove(&2).unwrap().send(true).unwrap();
6822 timeout(WAIT, async {
6823 while object_seqs(io.as_ref()).await != [0, 2] {
6824 tokio::time::sleep(Duration::from_millis(1)).await;
6825 }
6826 })
6827 .await
6828 .unwrap();
6829 releases.remove(&1).unwrap().send(false).unwrap();
6830 for append in appends {
6831 let error = timeout(WAIT, append).await.unwrap().unwrap().unwrap_err();
6832 assert!(
6833 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
6834 "unexpected error: {error:?}"
6835 );
6836 }
6837 assert_eq!(0, latest(&store, region_id));
6838
6839 let retry = spawn_appends(&store, region_id, 1).await;
6841 let (object_seq, release) = next_parked_operation(&mut parked).await;
6842 assert_eq!(3, object_seq);
6843 release.send(true).unwrap();
6844 let response = timeout(WAIT, retry.into_iter().next().unwrap())
6845 .await
6846 .unwrap()
6847 .unwrap()
6848 .unwrap();
6849 assert_eq!(
6850 HashMap::from([(region_id, id(3, 1))]),
6851 response.last_entry_ids
6852 );
6853 let bytes = io.get(3).await.unwrap();
6854 let header = decode_header(&bytes).unwrap();
6855 assert_eq!(
6856 Some(ChainLink {
6857 object_seq: 0,
6858 epoch: 1,
6859 }),
6860 header.prev
6861 );
6862 store.stop().await.unwrap();
6863
6864 let store = open(object_store, &eager()).await;
6867 assert_eq!(id(3, 1), latest(&store, region_id));
6868 assert_eq!(
6869 expected_entries(region_id, &[(id(3, 1), "a1")]),
6870 read_entries(&store, region_id, 1).await
6871 );
6872 }
6873
6874 #[tokio::test]
6875 async fn test_store_permanent_failure_before_a_durable_object_poisons() {
6876 let _serialized = SERIALIZED.lock().await;
6877 let object_store = memory_store();
6878 let (store, _, mut parked) = open_parking_creates(object_store.clone(), &eager()).await;
6879 let region_id = region(1);
6880 let foreign = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
6881 put_foreign(&object_store, 1, 1).await;
6883 let appends = spawn_appends(&store, region_id, 2).await;
6884 let mut releases = parked_operations(&mut parked, 2).await;
6885
6886 releases.remove(&2).unwrap().send(true).unwrap();
6888 timeout(WAIT, async {
6889 while object_seqs(&foreign).await != [0, 1, 2] {
6890 tokio::time::sleep(Duration::from_millis(1)).await;
6891 }
6892 })
6893 .await
6894 .unwrap();
6895 round_trip_actor(&store).await;
6896 assert!(appends.iter().all(|append| !append.is_finished()));
6897 releases.remove(&1).unwrap().send(true).unwrap();
6898 for append in appends {
6899 let error = timeout(WAIT, append).await.unwrap().unwrap().unwrap_err();
6900 assert!(
6901 matches!(unwrap_shared(&error), Error::WalObjectConflict { .. }),
6902 "unexpected error: {error:?}"
6903 );
6904 }
6905 let error = store.latest_entry_id(&provider(region_id)).unwrap_err();
6906 assert!(
6907 matches!(unwrap_shared(&error), Error::WalObjectConflict { .. }),
6908 "unexpected error: {error:?}"
6909 );
6910 store.stop().await.unwrap();
6911 }
6912
6913 #[tokio::test]
6914 async fn test_store_conflict_with_an_earlier_epoch_fails_the_batch_without_poisoning() {
6915 let _serialized = SERIALIZED.lock().await;
6916 let object_store = memory_store();
6917 let (store, _, mut parked) = open_parking_creates(object_store.clone(), &eager()).await;
6918 let region_id = region(1);
6919 put_foreign(&object_store, 1, 0).await;
6921 let append = spawn_appends(&store, region_id, 1).await;
6922 next_parked_operation(&mut parked)
6923 .await
6924 .1
6925 .send(true)
6926 .unwrap();
6927 let error = timeout(WAIT, append.into_iter().next().unwrap())
6928 .await
6929 .unwrap()
6930 .unwrap()
6931 .unwrap_err();
6932 assert!(
6933 matches!(
6934 unwrap_shared(&error),
6935 Error::StaleWalObject {
6936 existing_epoch: 0,
6937 epoch: 1,
6938 ..
6939 }
6940 ),
6941 "unexpected error: {error:?}"
6942 );
6943 assert_eq!(RetryHint::Retryable, error.retry_hint());
6944
6945 let retry = spawn_appends(&store, region_id, 1).await;
6947 let (object_seq, release) = next_parked_operation(&mut parked).await;
6948 assert_eq!(2, object_seq);
6949 release.send(true).unwrap();
6950 let response = timeout(WAIT, retry.into_iter().next().unwrap())
6951 .await
6952 .unwrap()
6953 .unwrap()
6954 .unwrap();
6955 assert_eq!(
6956 HashMap::from([(region_id, id(2, 1))]),
6957 response.last_entry_ids
6958 );
6959 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
6960 store.stop().await.unwrap();
6961
6962 let store = open(object_store, &eager()).await;
6964 assert_eq!(
6965 expected_entries(region_id, &[(id(2, 1), "a1")]),
6966 read_entries(&store, region_id, 1).await
6967 );
6968 }
6969
6970 #[tokio::test]
6971 async fn test_store_obsolete_raises_the_sequence_floor() {
6972 let _serialized = SERIALIZED.lock().await;
6973 let store = open(memory_store(), &manual()).await;
6974 let region_one = region(1);
6975 let region_two = region(2);
6976 let mut admitted = 0;
6977 let mut spawn_append = |region_id, data: &str| {
6978 admitted += 1;
6979 (
6980 spawn_append_batch(&store, vec![entry(&store, region_id, data)]),
6981 admitted,
6982 )
6983 };
6984 let (first, count) = spawn_append(region_one, "a1");
6985 store.wait_for_admitted_appends(count).await.unwrap();
6986 store.seal_open_batch().await.unwrap();
6987 let response = timeout(WAIT, first).await.unwrap().unwrap().unwrap();
6988 assert_eq!(
6989 HashMap::from([(region_one, id(1, 1))]),
6990 response.last_entry_ids
6991 );
6992 let (second, count) = spawn_append(region_one, "a2");
6993 store.wait_for_admitted_appends(count).await.unwrap();
6994
6995 store
6998 .obsolete(&provider(region_one), region_one, id(1, 1))
6999 .await
7000 .unwrap();
7001 let (third, count) = spawn_append(region_one, "a3");
7002 store.wait_for_admitted_appends(count).await.unwrap();
7003
7004 let error = store
7009 .obsolete(&provider(region_two), region_two, id(3, 7))
7010 .await
7011 .unwrap_err();
7012 assert!(
7013 matches!(
7014 error,
7015 Error::WalObjectSequenceUnsettled { object_seq: 2, .. }
7016 ),
7017 "unexpected error: {error:?}"
7018 );
7019 assert_eq!(RetryHint::Retryable, error.retry_hint());
7020 assert!(
7021 store
7022 .obsolete_entry_ids
7023 .lock()
7024 .unwrap()
7025 .get(®ion_two)
7026 .is_none()
7027 );
7028
7029 store.seal_open_batch().await.unwrap();
7032 let response = timeout(WAIT, second).await.unwrap().unwrap().unwrap();
7033 assert_eq!(
7034 HashMap::from([(region_one, id(2, 1))]),
7035 response.last_entry_ids
7036 );
7037 let response = timeout(WAIT, third).await.unwrap().unwrap().unwrap();
7038 assert_eq!(
7039 HashMap::from([(region_one, id(2, 2))]),
7040 response.last_entry_ids
7041 );
7042 assert_eq!(vec![2], collected_object_seqs(&store).await);
7044 store
7045 .obsolete(&provider(region_two), region_two, id(3, 7))
7046 .await
7047 .unwrap();
7048 assert_eq!(
7049 Some(&id(3, 7)),
7050 store.obsolete_entry_ids.lock().unwrap().get(®ion_two)
7051 );
7052 let (fourth, count) = spawn_append(region_two, "b1");
7053 store.wait_for_admitted_appends(count).await.unwrap();
7054 store.seal_open_batch().await.unwrap();
7055 let response = timeout(WAIT, fourth).await.unwrap().unwrap().unwrap();
7056 assert_eq!(
7057 HashMap::from([(region_two, id(4, 1))]),
7058 response.last_entry_ids
7059 );
7060 assert_eq!(vec![2, 4], collected_object_seqs(&store).await);
7061 assert_eq!(
7064 expected_entries(region_two, &[(id(4, 1), "b1")]),
7065 read_entries(&store, region_two, 1).await
7066 );
7067 store.stop().await.unwrap();
7068 }
7069
7070 #[tokio::test]
7071 async fn test_store_does_not_reuse_the_sequence_of_a_failed_create() {
7072 let _serialized = SERIALIZED.lock().await;
7073 let (io, _) = RecordingIo::over(memory_store());
7074 let store = open_over(io.clone(), &manual()).await;
7075 let region_one = region(1);
7076 let region_two = region(2);
7077
7078 let first = spawn_append_batch(&store, vec![entry(&store, region_one, "a1")]);
7081 store.wait_for_admitted_appends(1).await.unwrap();
7082 let second = spawn_append_batch(&store, vec![entry(&store, region_two, "b1")]);
7083 store.wait_for_admitted_appends(2).await.unwrap();
7084 io.fail_after_next_put.store(true, Ordering::Relaxed);
7085 store.seal_open_batch().await.unwrap_err();
7086 for append in [first, second] {
7087 timeout(WAIT, append).await.unwrap().unwrap().unwrap_err();
7088 }
7089 assert_eq!(vec![0, 1], object_seqs(io.as_ref()).await);
7090 assert_eq!(0, latest(&store, region_one));
7091
7092 for (region_id, data, object_seq) in [(region_one, "a1", 2), (region_two, "b1", 3)] {
7095 let retry = spawn_append_batch(&store, vec![entry(&store, region_id, data)]);
7096 store
7097 .wait_for_admitted_appends(object_seq as usize + 1)
7098 .await
7099 .unwrap();
7100 store.seal_open_batch().await.unwrap();
7101 let response = timeout(WAIT, retry).await.unwrap().unwrap().unwrap();
7102 assert_eq!(
7103 HashMap::from([(region_id, id(object_seq, 1))]),
7104 response.last_entry_ids
7105 );
7106 }
7107 assert_eq!(vec![2, 3], collected_object_seqs(&store).await);
7110 store.stop().await.unwrap();
7111
7112 let store = open_over(io, &manual()).await;
7114 assert_eq!(
7115 expected_entries(region_one, &[(id(2, 1), "a1")]),
7116 read_entries(&store, region_one, 0).await
7117 );
7118 assert_eq!(
7119 expected_entries(region_two, &[(id(3, 1), "b1")]),
7120 read_entries(&store, region_two, 0).await
7121 );
7122 store.stop().await.unwrap();
7123 }
7124
7125 #[tokio::test]
7126 async fn test_store_floor_applies_while_a_create_is_in_flight() {
7127 let _serialized = SERIALIZED.lock().await;
7128 let (store, _, mut parked) = open_parking_creates(memory_store(), &eager()).await;
7129 let region_one = region(1);
7130 let region_two = region(2);
7131 let pending = spawn_append_batch(&store, vec![entry(&store, region_one, "a1")]);
7132 let (_, release) = next_parked_operation(&mut parked).await;
7133
7134 store
7137 .obsolete(&provider(region_two), region_two, id(5, 1))
7138 .await
7139 .unwrap();
7140 release.send(false).unwrap();
7141 timeout(WAIT, pending).await.unwrap().unwrap().unwrap_err();
7142
7143 let write = spawn_append_batch(&store, vec![entry(&store, region_two, "b1")]);
7144 next_parked_operation(&mut parked)
7145 .await
7146 .1
7147 .send(true)
7148 .unwrap();
7149 let response = timeout(WAIT, write).await.unwrap().unwrap().unwrap();
7150 assert_eq!(
7151 HashMap::from([(region_two, id(6, 1))]),
7152 response.last_entry_ids
7153 );
7154 assert_eq!(vec![6], collected_object_seqs(&store).await);
7155 }
7156
7157 #[tokio::test]
7158 async fn test_store_obsolete_queued_behind_stop_records_the_watermark() {
7159 let _serialized = SERIALIZED.lock().await;
7160 let (store, mut command_rx) = store_without_actor();
7161 let region_id = region(1);
7162 let provider_one = provider(region_id);
7163
7164 let (result, ()) = tokio::join!(store.obsolete(&provider_one, region_id, 1), async {
7167 let command = timeout(WAIT, command_rx.recv()).await.unwrap();
7168 assert!(matches!(
7169 command,
7170 Some(Command::Obsolete { entry_id: 1, .. })
7171 ));
7172 });
7173 result.unwrap();
7174 assert_eq!(
7175 Some(&1),
7176 store.obsolete_entry_ids.lock().unwrap().get(®ion_id)
7177 );
7178
7179 drop(command_rx);
7181 store
7182 .obsolete(&provider(region(2)), region(2), id(1, 1))
7183 .await
7184 .unwrap();
7185 assert_eq!(
7186 Some(&id(1, 1)),
7187 store.obsolete_entry_ids.lock().unwrap().get(®ion(2))
7188 );
7189 }
7190
7191 #[tokio::test]
7192 async fn test_store_hooks_hold_and_fail_creates() {
7193 let _serialized = SERIALIZED.lock().await;
7194 let store = open(memory_store(), &eager()).await;
7195 let region_id = region(1);
7196 store.hold_creates();
7197 let held = spawn_append_batch(&store, vec![entry(&store, region_id, "a1")]);
7198 store.wait_for_admitted_appends(1).await.unwrap();
7199 round_trip_actor(&store).await;
7200 assert!(!held.is_finished());
7201 assert_eq!(vec![0], object_seqs(store.io.as_ref()).await);
7202 store.release_creates();
7203 timeout(WAIT, held).await.unwrap().unwrap().unwrap();
7204 assert_eq!(vec![1], collected_object_seqs(&store).await);
7205
7206 store.fail_creates();
7207 let error = append(&store, region_id, "a2").await.unwrap_err();
7208 assert!(
7209 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
7210 "unexpected error: {error:?}"
7211 );
7212 assert_eq!(vec![1], object_seqs(store.io.as_ref()).await);
7213
7214 store.begin_stop();
7215 assert_stopped(&append(&store, region_id, "a3").await.unwrap_err());
7216 store.stop().await.unwrap();
7217 }
7218
7219 #[tokio::test]
7220 async fn test_store_hook_fails_the_next_create_after_it_writes() {
7221 let _serialized = SERIALIZED.lock().await;
7222 let store = open(memory_store(), &eager()).await;
7223 let region_id = region(1);
7224
7225 store.fail_next_create_after_write();
7228 let error = append(&store, region_id, "a1").await.unwrap_err();
7229 assert!(
7230 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
7231 "unexpected error: {error:?}"
7232 );
7233 assert_eq!(vec![0, 1], object_seqs(store.io.as_ref()).await);
7234 assert_eq!(0, latest(&store, region_id));
7235
7236 let response = append(&store, region_id, "a2").await.unwrap();
7238 assert_eq!(
7239 HashMap::from([(region_id, id(2, 1))]),
7240 response.last_entry_ids
7241 );
7242 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
7243 store.stop().await.unwrap();
7244 }
7245
7246 #[tokio::test]
7247 async fn test_store_hooks_observe_parked_creates_waits_and_crash() {
7248 let _serialized = SERIALIZED.lock().await;
7249 let store = open(memory_store(), &enqueued(eager())).await;
7250 let region_id = region(1);
7251
7252 store.hold_creates();
7255 append(&store, region_id, "a1").await.unwrap();
7256 timeout(WAIT, store.wait_for_parked_creates(1))
7257 .await
7258 .unwrap()
7259 .unwrap();
7260 let wait = {
7261 let store = store.clone();
7262 tokio::spawn(async move { store.wait_durable(&provider(region_id), id(1, 1)).await })
7263 };
7264 timeout(WAIT, store.wait_for_durability_waits(1))
7265 .await
7266 .unwrap()
7267 .unwrap();
7268 assert!(!wait.is_finished());
7269
7270 store.crash().await;
7273 assert_stopped(&timeout(WAIT, wait).await.unwrap().unwrap().unwrap_err());
7274 assert_stopped(&append(&store, region_id, "a2").await.unwrap_err());
7275 assert_eq!(vec![0], object_seqs(store.io.as_ref()).await);
7276 }
7277
7278 #[tokio::test]
7279 async fn test_store_create_held_when_the_store_is_dropped_never_runs() {
7280 let _serialized = SERIALIZED.lock().await;
7281 let (io, _) = RecordingIo::over(memory_store());
7282 let store = open_over(io.clone(), &eager()).await;
7283 store.hold_creates();
7284 let append = spawn_append_batch(&store, vec![entry(&store, region(1), "a1")]);
7285 store.wait_for_admitted_appends(1).await.unwrap();
7286 append.abort();
7287 let _ = append.await;
7288 let command_tx = store.command_tx.clone();
7292 let terminal_error = store.terminal_error.clone();
7293 drop(store);
7294 timeout(WAIT, async {
7295 while terminal(&terminal_error).is_none() && object_seqs(io.as_ref()).await == [0] {
7296 tokio::time::sleep(Duration::from_millis(1)).await;
7297 }
7298 })
7299 .await
7300 .unwrap();
7301 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
7302 let error = terminal(&terminal_error).unwrap();
7303 assert!(
7304 matches!(error.as_ref(), Error::ObjectStoreWalStopped { .. }),
7305 "unexpected error: {error:?}"
7306 );
7307 drop(command_tx);
7308 }
7309
7310 #[tokio::test]
7311 async fn test_store_rollback_and_poison_drop_the_open_batch() {
7312 let _serialized = SERIALIZED.lock().await;
7313 let object_store = memory_store();
7314 let (store, _, mut parked) = open_parking_creates(object_store.clone(), &manual()).await;
7315 let region_id = region(1);
7316 let spawn_seal = || {
7317 let store = store.clone();
7318 tokio::spawn(async move { store.seal_open_batch().await })
7319 };
7320
7321 let sealed = spawn_append_batch(&store, vec![entry(&store, region_id, "a1")]);
7324 store.wait_for_admitted_appends(1).await.unwrap();
7325 let seal = spawn_seal();
7326 let (_, release) = next_parked_operation(&mut parked).await;
7327 let open = spawn_append_batch(&store, vec![entry(&store, region_id, "a2")]);
7328 store.wait_for_admitted_appends(2).await.unwrap();
7329 release.send(false).unwrap();
7330 for append in [sealed, open] {
7331 let error = timeout(WAIT, append).await.unwrap().unwrap().unwrap_err();
7332 assert!(
7333 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
7334 "unexpected error: {error:?}"
7335 );
7336 }
7337 timeout(WAIT, seal).await.unwrap().unwrap().unwrap_err();
7338
7339 let retry = spawn_append_batch(&store, vec![entry(&store, region_id, "b1")]);
7342 store.wait_for_admitted_appends(3).await.unwrap();
7343 let seal = spawn_seal();
7344 next_parked_operation(&mut parked)
7345 .await
7346 .1
7347 .send(true)
7348 .unwrap();
7349 timeout(WAIT, seal).await.unwrap().unwrap().unwrap();
7350 let response = timeout(WAIT, retry).await.unwrap().unwrap().unwrap();
7351 assert_eq!(
7352 HashMap::from([(region_id, id(2, 1))]),
7353 response.last_entry_ids
7354 );
7355 assert_eq!(
7356 expected_entries(region_id, &[(id(2, 1), "b1")]),
7357 read_entries(&store, region_id, 0).await
7358 );
7359
7360 let sealed = spawn_append_batch(&store, vec![entry(&store, region_id, "c1")]);
7362 store.wait_for_admitted_appends(4).await.unwrap();
7363 let seal = spawn_seal();
7364 let (_, release) = next_parked_operation(&mut parked).await;
7365 let open = spawn_append_batch(&store, vec![entry(&store, region_id, "c2")]);
7366 store.wait_for_admitted_appends(5).await.unwrap();
7367 put_foreign(&object_store, 3, 1).await;
7368 release.send(true).unwrap();
7369 for append in [sealed, open] {
7370 let error = timeout(WAIT, append).await.unwrap().unwrap().unwrap_err();
7371 assert!(
7372 matches!(unwrap_shared(&error), Error::WalObjectConflict { .. }),
7373 "unexpected error: {error:?}"
7374 );
7375 }
7376 timeout(WAIT, seal).await.unwrap().unwrap().unwrap_err();
7377 assert_eq!(vec![2, 3], collected_object_seqs(&store).await);
7378 store.stop().await.unwrap();
7379 }
7380
7381 #[tokio::test]
7382 async fn test_store_poisons_once_the_last_sequence_is_taken() {
7383 let _serialized = SERIALIZED.lock().await;
7384 let object_store = memory_store();
7385 let last = OBJECT_SEQ_LIMIT - 1;
7386 put_object(&object_store, last - 2, region(1), &[id(last - 2, 1)]).await;
7388 let store = open(object_store, &eager()).await;
7389 let next = entry(&store, region(1), "next");
7390
7391 let response = append(&store, region(1), "last").await.unwrap();
7394 assert_eq!(
7395 HashMap::from([(region(1), id(last, 1))]),
7396 response.last_entry_ids
7397 );
7398 for error in [
7399 store.latest_entry_id(&provider(region(1))).unwrap_err(),
7400 store.append_batch(vec![next]).await.unwrap_err(),
7401 ] {
7402 assert!(
7403 matches!(
7404 unwrap_shared(&error),
7405 Error::WalObjectSequenceExhausted { last_object_seq, .. } if *last_object_seq == last
7406 ),
7407 "unexpected error: {error:?}"
7408 );
7409 }
7410 store.stop().await.unwrap();
7411 }
7412
7413 #[tokio::test]
7414 async fn test_store_holds_appends_back_while_the_sealed_batches_are_full() {
7415 let _serialized = SERIALIZED.lock().await;
7416 let (store, _, mut parked) = open_parking_creates(memory_store(), &eager()).await;
7417 let region_id = region(1);
7418 let admitted = || *store.admitted_appends.borrow();
7419 let stalled = METRIC_OBJECT_STORE_WAL_STALLED_APPENDS_TOTAL.get();
7420 let stalled_waits = METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS.get_sample_count();
7421
7422 let mut appends = spawn_appends(&store, region_id, MAX_SEALED_BATCHES - 1).await;
7426 for index in 0..3 {
7427 let entries = vec![entry(&store, region_id, &format!("w{index}"))];
7428 appends.push(spawn_append_batch(&store, entries));
7429 }
7430 let mut releases = parked_operations(&mut parked, MAX_IN_FLIGHT_CREATES).await;
7431 wait_until(|| METRIC_OBJECT_STORE_WAL_STALLED_APPENDS_TOTAL.get() == stalled + 1).await;
7432 round_trip_actor(&store).await;
7433 assert_eq!(MAX_SEALED_BATCHES - 1, admitted());
7434
7435 releases.remove(&1).unwrap().send(true).unwrap();
7438 timeout(WAIT, store.wait_for_admitted_appends(MAX_SEALED_BATCHES))
7439 .await
7440 .unwrap()
7441 .unwrap();
7442 wait_until(|| METRIC_OBJECT_STORE_WAL_STALLED_APPENDS_TOTAL.get() == stalled + 2).await;
7443 round_trip_actor(&store).await;
7444 assert_eq!(MAX_SEALED_BATCHES, admitted());
7445 assert_eq!(
7446 stalled_waits + 1,
7447 METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS.get_sample_count()
7448 );
7449
7450 let stop = begin_spawned_stop(&store).await;
7453 for release in releases.into_values() {
7454 release.send(true).unwrap();
7455 }
7456 next_parked_operation(&mut parked)
7457 .await
7458 .1
7459 .send(true)
7460 .unwrap();
7461 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
7462 let mut acknowledged = 0;
7463 for append in appends {
7464 match timeout(WAIT, append).await.unwrap().unwrap() {
7465 Ok(_) => acknowledged += 1,
7466 Err(error) => assert_stopped(&error),
7467 }
7468 }
7469 assert_eq!(MAX_IN_FLIGHT_CREATES + 1, acknowledged);
7470 }
7471
7472 async fn begin_spawned_stop(
7474 store: &Arc<ObjectStoreLogStore>,
7475 ) -> tokio::task::JoinHandle<Result<()>> {
7476 let stop = {
7477 let store = store.clone();
7478 tokio::spawn(async move { store.stop().await })
7479 };
7480 while !store.stopped.load(Ordering::Acquire) {
7481 tokio::task::yield_now().await;
7482 }
7483 stop
7484 }
7485
7486 #[tokio::test]
7487 async fn test_store_stop_during_failed_flush_reports_stopped() {
7488 let _serialized = SERIALIZED.lock().await;
7489 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
7490 let pending = spawn_append_batch(&store, vec![entry(&store, region(1), "a1")]);
7491 let (_, release) = next_parked_operation(&mut parked).await;
7492 let stop = begin_spawned_stop(&store).await;
7493 assert!(!stop.is_finished());
7494
7495 release.send(false).unwrap();
7497 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
7498 assert_stopped(&timeout(WAIT, pending).await.unwrap().unwrap().unwrap_err());
7499 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
7500 assert_stopped(&append(&store, region(1), "a2").await.unwrap_err());
7501 }
7502
7503 #[tokio::test]
7504 async fn test_store_stop_with_several_creates_in_flight() {
7505 let _serialized = SERIALIZED.lock().await;
7506 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
7507 let region_id = region(1);
7508 let appends = spawn_appends(&store, region_id, MAX_IN_FLIGHT_CREATES + 1).await;
7510 let mut releases = parked_operations(&mut parked, MAX_IN_FLIGHT_CREATES).await;
7511 assert!(parked.try_recv().is_err());
7512
7513 let stop = begin_spawned_stop(&store).await;
7514 assert!(!stop.is_finished());
7515 store
7518 .obsolete(&provider(region(2)), region(2), id(9, 1))
7519 .await
7520 .unwrap();
7521 assert_eq!(
7522 Some(&id(9, 1)),
7523 store.obsolete_entry_ids.lock().unwrap().get(®ion(2))
7524 );
7525
7526 for object_seq in [3, 1, 4, 2] {
7529 releases.remove(&object_seq).unwrap().send(true).unwrap();
7530 }
7531 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
7532 let mut appends = appends.into_iter();
7533 for object_seq in 1..=MAX_IN_FLIGHT_CREATES as u64 {
7534 let response = timeout(WAIT, appends.next().unwrap())
7535 .await
7536 .unwrap()
7537 .unwrap()
7538 .unwrap();
7539 assert_eq!(
7540 HashMap::from([(region_id, id(object_seq, 1))]),
7541 response.last_entry_ids
7542 );
7543 }
7544 let error = timeout(WAIT, appends.next().unwrap())
7545 .await
7546 .unwrap()
7547 .unwrap()
7548 .unwrap_err();
7549 assert_stopped(&error);
7550 assert!(parked.try_recv().is_err());
7552 assert_eq!(vec![0, 1, 2, 3, 4], object_seqs(io.as_ref()).await);
7553 assert_eq!(id(4, 1), latest(&store, region_id));
7554 timeout(WAIT, store.command_tx.closed()).await.unwrap();
7555 }
7556
7557 #[tokio::test]
7558 async fn test_store_stop_during_conflicting_flush_reports_stopped_and_poisons() {
7559 let _serialized = SERIALIZED.lock().await;
7560 let object_store = memory_store();
7561 let (store, _, mut parked) = open_parking_creates(object_store.clone(), &eager()).await;
7562 let region_id = region(1);
7563 let pending = spawn_append_batch(&store, vec![entry(&store, region_id, "a1")]);
7564 let (_, release) = next_parked_operation(&mut parked).await;
7565 let stop = begin_spawned_stop(&store).await;
7566 put_foreign(&object_store, 1, 1).await;
7569
7570 release.send(true).unwrap();
7571 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
7572 assert_stopped(&timeout(WAIT, pending).await.unwrap().unwrap().unwrap_err());
7573 for error in [
7574 store.latest_entry_id(&provider(region_id)).unwrap_err(),
7575 store.read(&provider(region_id), 1).await.err().unwrap(),
7576 ] {
7577 assert!(
7578 matches!(unwrap_shared(&error), Error::WalObjectConflict { .. }),
7579 "unexpected error: {error:?}"
7580 );
7581 }
7582 }
7583
7584 fn injected_failure<T>(operation: &'static str, path: String) -> Result<T> {
7585 Err(
7586 object_store::Error::new(object_store::ErrorKind::Unexpected, "injected failure")
7587 .set_temporary(),
7588 )
7589 .context(WalObjectStoreSnafu { operation, path })
7590 }
7591
7592 fn chain_header(object_seq: u64, epoch: u64, prev: Option<(u64, u64)>) -> Header {
7593 Header {
7594 object_seq,
7595 epoch,
7596 prev: prev.map(|(object_seq, epoch)| ChainLink { object_seq, epoch }),
7597 reclaim_boundary: 0,
7598 }
7599 }
7600
7601 fn chain_of(headers: &[Header]) -> Vec<u64> {
7602 select_chain(
7603 &headers
7604 .iter()
7605 .map(|header| (header.object_seq, header))
7606 .collect(),
7607 )
7608 }
7609
7610 #[test]
7611 fn test_store_selects_the_chain_of_the_latest_complete_object() {
7612 assert!(chain_of(&[]).is_empty());
7613 assert_eq!(
7615 vec![0, 1, 2],
7616 chain_of(&[
7617 chain_header(0, 1, None),
7618 chain_header(1, 1, Some((0, 1))),
7619 chain_header(2, 1, Some((1, 1))),
7620 ])
7621 );
7622 assert_eq!(
7624 vec![0, 2],
7625 chain_of(&[
7626 chain_header(0, 1, None),
7627 chain_header(1, 1, Some((0, 1))),
7628 chain_header(2, 1, Some((0, 1))),
7629 ])
7630 );
7631 assert_eq!(
7634 vec![0, 1],
7635 chain_of(&[
7636 chain_header(0, 1, None),
7637 chain_header(1, 1, Some((0, 1))),
7638 chain_header(3, 1, Some((2, 1))),
7639 ])
7640 );
7641 assert_eq!(
7644 vec![0],
7645 chain_of(&[chain_header(0, 1, None), chain_header(1, 2, Some((0, 2))),])
7646 );
7647 assert_eq!(
7650 vec![0, 1],
7651 chain_of(&[
7652 chain_header(0, 1, None),
7653 chain_header(1, 2, Some((0, 1))),
7654 chain_header(2, 1, Some((0, 1))),
7655 ])
7656 );
7657 assert!(
7661 chain_of(&[
7662 chain_header(3, 1, Some((2, 1))),
7663 chain_header(4, 1, Some((3, 1))),
7664 ])
7665 .is_empty()
7666 );
7667 assert!(
7668 chain_of(&[
7669 chain_header(2, 1, Some((1, 1))),
7670 chain_header(4, 2, Some((3, 1))),
7671 chain_header(5, 2, Some((4, 2))),
7672 ])
7673 .is_empty()
7674 );
7675 assert_eq!(
7676 vec![1],
7677 chain_of(&[chain_header(1, 1, None), chain_header(3, 1, Some((2, 1))),])
7678 );
7679 assert!(chain_of(&[chain_header(5, 1, Some((5, 1)))]).is_empty());
7681
7682 let bounded = |object_seq, prev, reclaim_boundary| Header {
7686 reclaim_boundary,
7687 ..chain_header(object_seq, 1, prev)
7688 };
7689 assert_eq!(
7690 vec![3, 4],
7691 chain_of(&[bounded(3, Some((2, 1)), 3), bounded(4, Some((3, 1)), 3)])
7692 );
7693 assert!(chain_of(&[bounded(4, Some((3, 1)), 3)]).is_empty());
7694 assert_eq!(
7697 vec![3, 4],
7698 chain_of(&[bounded(3, Some((2, 1)), 0), bounded(4, Some((3, 1)), 3)])
7699 );
7700 assert_eq!(
7701 vec![4],
7702 chain_of(&[bounded(3, Some((2, 1)), 0), bounded(4, Some((3, 1)), 4)])
7703 );
7704 }
7705
7706 #[tokio::test]
7707 async fn test_store_recovery_indexes_only_the_chain() {
7708 let _serialized = SERIALIZED.lock().await;
7709 let object_store = memory_store();
7710 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
7711 let record = |entry_id: EntryId, data: &'static str| Record {
7712 region_id: region(1),
7713 entry_id,
7714 payload: Bytes::from_static(data.as_bytes()),
7715 };
7716 put_fixture(&io, 0, &[record(1, "a")]).await;
7717 let first = fixture_header(&io, 1).await;
7718 put_object_with_header(&io, first.clone(), &[record(id(1, 1), "old")]).await;
7721 let second = Header {
7722 object_seq: 2,
7723 ..first
7724 };
7725 put_object_with_header(&io, second, &[record(id(2, 1), "new")]).await;
7726
7727 for restart in 0..2 {
7728 let store = open(object_store.clone(), &eager()).await;
7729 assert_eq!(
7730 expected_entries(region(1), &[(1, "a"), (id(2, 1), "new")]),
7731 read_entries(&store, region(1), 0).await
7732 );
7733 assert_eq!(id(2, 1), latest(&store, region(1)));
7734 store.stop().await.unwrap();
7735 let start = decode_header(&io.get(3 + restart).await.unwrap()).unwrap();
7739 assert_eq!(4 + restart, start.epoch);
7740 assert_eq!(Some(2 + restart), start.prev.map(|prev| prev.object_seq));
7741 }
7742 assert_eq!(vec![0, 1, 2, 3, 4], object_seqs(&io).await);
7743 }
7744
7745 #[tokio::test]
7746 async fn test_store_recovery_never_reuses_the_sequence_of_an_orphan() {
7747 let _serialized = SERIALIZED.lock().await;
7748 let io = ObjectStoreIo::new(memory_store(), PREFIX).unwrap();
7749 put_fixture(&io, 0, &[]).await;
7750 let orphan = Header {
7752 prev: Some(ChainLink {
7753 object_seq: 1,
7754 epoch: 1,
7755 }),
7756 ..fixture_header(&io, 2).await
7757 };
7758 put_object_with_header(&io, orphan, &[]).await;
7759 let recovered = recover(&io).await.unwrap();
7760 assert_eq!(Some(0), recovered.tip.map(|tip| tip.object_seq));
7761 assert_eq!(3, recovered.next_object_seq);
7762 }
7763
7764 #[tokio::test]
7765 async fn test_store_recovery_rejects_objects_without_a_complete_chain() {
7766 let _serialized = SERIALIZED.lock().await;
7767 let object_store = memory_store();
7768 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
7769 put_object_with_header(&io, chain_header(1, 1, Some((0, 1))), &[]).await;
7771 let error = ObjectStoreLogStore::try_new(object_store, &eager(), 1, 2)
7772 .await
7773 .unwrap_err();
7774 assert!(
7775 matches!(&error, Error::CorruptedWalObject { reason, .. }
7776 if reason.contains("no object among 1 present objects completes a chain")),
7777 "unexpected error: {error:?}"
7778 );
7779 assert_eq!(vec![1], object_seqs(&io).await);
7780 }
7781
7782 #[tokio::test]
7783 async fn test_store_recovery_keeps_the_later_epoch_over_a_late_object() {
7784 let _serialized = SERIALIZED.lock().await;
7785 let object_store = memory_store();
7786 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
7787 let record = |entry_id: EntryId, data: &'static str| Record {
7788 region_id: region(1),
7789 entry_id,
7790 payload: Bytes::from_static(data.as_bytes()),
7791 };
7792 put_object_with_header(&io, chain_header(0, 1, None), &[record(1, "a")]).await;
7793 put_object_with_header(
7796 &io,
7797 chain_header(1, 2, Some((0, 1))),
7798 &[record(id(1, 1), "b")],
7799 )
7800 .await;
7801 put_object_with_header(
7802 &io,
7803 chain_header(2, 1, Some((0, 1))),
7804 &[record(id(2, 1), "late")],
7805 )
7806 .await;
7807
7808 let store = open(object_store, &eager()).await;
7809 assert_eq!(
7810 expected_entries(region(1), &[(1, "a"), (id(1, 1), "b")]),
7811 read_entries(&store, region(1), 0).await
7812 );
7813 store.stop().await.unwrap();
7814 let start = decode_header(&io.get(3).await.unwrap()).unwrap();
7815 assert_eq!(4, start.epoch);
7816 assert_eq!(
7817 Some(ChainLink {
7818 object_seq: 1,
7819 epoch: 2,
7820 }),
7821 start.prev
7822 );
7823 }
7824
7825 #[tokio::test]
7826 async fn test_store_open_starts_the_first_epoch_on_an_empty_prefix() {
7827 let _serialized = SERIALIZED.lock().await;
7828 let object_store = memory_store();
7829 let store = open(object_store.clone(), &eager()).await;
7830 store.stop().await.unwrap();
7831 let io = ObjectStoreIo::new(object_store, PREFIX).unwrap();
7832 let start = decode_header(&io.get(0).await.unwrap()).unwrap();
7833 assert_eq!(1, start.epoch);
7834 assert_eq!(None, start.prev);
7835 assert_eq!(vec![0], object_seqs(&io).await);
7836 }
7837
7838 #[tokio::test]
7839 async fn test_store_start_object_moves_past_an_earlier_epoch_only() {
7840 let _serialized = SERIALIZED.lock().await;
7841 for (epoch, moves) in [(1, true), (2, false)] {
7842 let object_store = memory_store();
7843 let fixtures = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
7844 put_fixture(&fixtures, 0, &[]).await;
7845 let late = encode_object(chain_header(1, epoch, None), &[])
7848 .unwrap()
7849 .bytes;
7850 let io = Arc::new(RacingIo {
7851 inner: ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap(),
7852 late: Mutex::new(Some((1, late))),
7853 });
7854 let result = ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string()).await;
7855 if moves {
7856 let store = result.unwrap();
7857 let start = decode_header(&fixtures.get(2).await.unwrap()).unwrap();
7858 assert_eq!(3, start.epoch);
7859 assert_eq!(Some(0), start.prev.map(|prev| prev.object_seq));
7860 append(&store, region(1), "a").await.unwrap();
7863 assert_eq!(vec![3], collected_object_seqs(&store).await);
7864 store.stop().await.unwrap();
7865 } else {
7866 let error = result.unwrap_err();
7867 assert!(
7868 matches!(&error, Error::WalObjectConflict { path, .. } if path == &fixtures.object_path(1)),
7869 "unexpected error: {error:?}"
7870 );
7871 assert_eq!(vec![0, 1], object_seqs(&fixtures).await);
7872 }
7873 }
7874 }
7875
7876 #[tokio::test]
7877 async fn test_store_open_fails_when_its_start_object_is_already_present() {
7878 let _serialized = SERIALIZED.lock().await;
7879 for present in [false, true] {
7883 let object_store = memory_store();
7884 let fixtures = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
7885 let (start_seq, epoch, tip) = if present {
7886 put_fixture(&fixtures, 0, &[]).await;
7887 (1, 2, Some((0, 1)))
7888 } else {
7889 (0, 1, None)
7890 };
7891 let header = Header {
7893 reclaim_boundary: start_seq,
7894 ..chain_header(start_seq, epoch, tip)
7895 };
7896 let same = encode_object(header, &[]).unwrap().bytes;
7897 let io = Arc::new(RacingIo {
7898 inner: ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap(),
7899 late: Mutex::new(Some((start_seq, same))),
7900 });
7901 let error = ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string())
7902 .await
7903 .unwrap_err();
7904 assert!(
7905 matches!(&error, Error::UnconfirmedWalEpochStart { epoch: actual, path, .. }
7906 if *actual == epoch && path == &fixtures.object_path(start_seq)),
7907 "unexpected error: {error:?}"
7908 );
7909 assert_eq!(RetryHint::Retryable, error.retry_hint());
7910
7911 open(object_store, &eager()).await.stop().await.unwrap();
7913 let start = decode_header(&fixtures.get(start_seq + 1).await.unwrap()).unwrap();
7914 assert_eq!(epoch + 1, start.epoch);
7915 assert_eq!(
7916 Some(ChainLink {
7917 object_seq: start_seq,
7918 epoch,
7919 }),
7920 start.prev
7921 );
7922 }
7923 }
7924
7925 #[tokio::test]
7926 async fn test_store_opens_racing_across_a_late_object_take_distinct_epochs() {
7927 let _serialized = SERIALIZED.lock().await;
7928 let object_store = memory_store();
7929 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
7930 put_fixture(&io, 0, &[]).await;
7931 let recovered = recover(&io).await.unwrap();
7933 put_object_with_header(&io, chain_header(2, 1, Some((0, 1))), &[]).await;
7936 open(object_store, &eager()).await.stop().await.unwrap();
7937 let b = decode_header(&io.get(3).await.unwrap()).unwrap();
7938 assert_eq!(4, b.epoch);
7939
7940 let a = start_epoch(
7943 &io,
7944 recovered.next_object_seq,
7945 recovered.tip,
7946 recovered.reclaim_boundary,
7947 )
7948 .await
7949 .unwrap()
7950 .0;
7951 assert_eq!(
7952 ChainLink {
7953 object_seq: 1,
7954 epoch: 2,
7955 },
7956 a
7957 );
7958 }
7959
7960 #[tokio::test]
7961 async fn test_store_open_rejects_an_epoch_above_the_next_sequence() {
7962 let _serialized = SERIALIZED.lock().await;
7963 let object_store = memory_store();
7964 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
7965 put_object_with_header(&io, chain_header(0, 5, None), &[]).await;
7967 let error = ObjectStoreLogStore::try_new(object_store, &eager(), 1, 2)
7968 .await
7969 .unwrap_err();
7970 assert!(
7971 matches!(&error, Error::CorruptedWalObject { reason, .. }
7972 if reason.contains("carries epoch 5, above the next sequence 1")),
7973 "unexpected error: {error:?}"
7974 );
7975 assert_eq!(vec![0], object_seqs(&io).await);
7976 }
7977
7978 #[tokio::test]
7979 async fn test_store_open_fails_when_its_start_object_lands_with_an_unknown_outcome() {
7980 let _serialized = SERIALIZED.lock().await;
7981 let object_store = memory_store();
7982 let fixtures = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
7983 put_fixture(&fixtures, 0, &[]).await;
7984 let io = Arc::new(LostResponseIo {
7986 inner: ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap(),
7987 lose_next: AtomicBool::new(true),
7988 });
7989 let error = ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string())
7990 .await
7991 .unwrap_err();
7992 assert!(
7993 matches!(error, Error::WalObjectStore { .. }),
7994 "unexpected error: {error:?}"
7995 );
7996 assert_eq!(vec![0, 1], object_seqs(&fixtures).await);
7998 let stored = decode_header(&fixtures.get(1).await.unwrap()).unwrap();
7999 assert_eq!(2, stored.epoch);
8000
8001 open(object_store, &eager()).await.stop().await.unwrap();
8004 let start = decode_header(&fixtures.get(2).await.unwrap()).unwrap();
8005 assert_eq!(3, start.epoch);
8006 assert_eq!(
8007 Some(ChainLink {
8008 object_seq: 1,
8009 epoch: 2,
8010 }),
8011 start.prev
8012 );
8013 }
8014
8015 #[tokio::test]
8016 async fn test_store_enqueued_append_returns_before_the_object_exists() {
8017 let _serialized = SERIALIZED.lock().await;
8018 let store = open(memory_store(), &enqueued(manual())).await;
8019 let region_id = region(1);
8020
8021 let response = timeout(WAIT, append(&store, region_id, "a1"))
8022 .await
8023 .unwrap()
8024 .unwrap();
8025 assert_eq!(
8026 HashMap::from([(region_id, id(1, 1))]),
8027 response.last_entry_ids
8028 );
8029 assert_eq!(vec![0], object_seqs(store.io.as_ref()).await);
8030 assert_eq!(0, latest(&store, region_id));
8031 assert_eq!(0, store.durable_entry_id(&provider(region_id)).unwrap());
8032 assert!(read_entries(&store, region_id, 1).await.is_empty());
8033 let response = append(&store, region_id, "a2").await.unwrap();
8034 assert_eq!(
8035 HashMap::from([(region_id, id(1, 2))]),
8036 response.last_entry_ids
8037 );
8038
8039 store.seal_open_batch().await.unwrap();
8040 assert_eq!(vec![1], collected_object_seqs(&store).await);
8041 assert_eq!(id(1, 2), latest(&store, region_id));
8042 assert_eq!(
8043 id(1, 2),
8044 store.durable_entry_id(&provider(region_id)).unwrap()
8045 );
8046 assert_eq!(
8047 expected_entries(region_id, &[(id(1, 1), "a1"), (id(1, 2), "a2")]),
8048 read_entries(&store, region_id, 1).await
8049 );
8050 }
8051
8052 #[tokio::test]
8053 async fn test_store_enqueued_durable_id_advances_after_create_and_indexing() {
8054 let _serialized = SERIALIZED.lock().await;
8055 let (store, _, mut parked) = open_parking_creates(memory_store(), &enqueued(eager())).await;
8056 let region_one = region(1);
8057 let region_two = region(2);
8058
8059 let response = timeout(WAIT, append(&store, region_one, "a1"))
8060 .await
8061 .unwrap()
8062 .unwrap();
8063 assert_eq!(
8064 HashMap::from([(region_one, id(1, 1))]),
8065 response.last_entry_ids
8066 );
8067 let (object_seq, release) = next_parked_operation(&mut parked).await;
8068 assert_eq!(1, object_seq);
8069 assert_eq!(0, store.durable_entry_id(&provider(region_one)).unwrap());
8070 let wait = |entry_id| {
8071 let store = store.clone();
8072 tokio::spawn(async move { store.wait_durable(&provider(region_one), entry_id).await })
8073 };
8074 let waits = [wait(id(1, 1)), wait(id(1, 7))];
8077 timeout(WAIT, store.wait_durable(&provider(region_two), id(1, 1)))
8079 .await
8080 .unwrap()
8081 .unwrap();
8082 for _ in 0..16 {
8083 tokio::task::yield_now().await;
8084 }
8085 assert!(waits.iter().all(|wait| !wait.is_finished()));
8086
8087 release.send(true).unwrap();
8088 for wait in waits {
8089 timeout(WAIT, wait).await.unwrap().unwrap().unwrap();
8090 }
8091 assert_eq!(
8092 id(1, 1),
8093 store.durable_entry_id(&provider(region_one)).unwrap()
8094 );
8095 assert_eq!(0, store.durable_entry_id(&provider(region_two)).unwrap());
8096 assert_eq!(vec![1], collected_object_seqs(&store).await);
8097 }
8098
8099 #[tokio::test]
8100 async fn test_store_enqueued_wait_ignores_later_pending_entries_of_the_region() {
8101 let _serialized = SERIALIZED.lock().await;
8102 let (store, _io, mut parked) =
8103 open_parking_creates(memory_store(), &enqueued(eager())).await;
8104 let region_a = region(1);
8105 let region_b = region(2);
8106 for (region_id, data) in [(region_a, "a1"), (region_b, "b1")] {
8108 let response = append(&store, region_id, data).await.unwrap();
8109 let (_, release) = next_parked_operation(&mut parked).await;
8110 release.send(true).unwrap();
8111 let entry_id = response.last_entry_ids[®ion_id];
8112 timeout(WAIT, store.wait_durable(&provider(region_id), entry_id))
8113 .await
8114 .unwrap()
8115 .unwrap();
8116 }
8117 let response = append(&store, region_a, "a2").await.unwrap();
8118 assert_eq!(
8119 HashMap::from([(region_a, id(3, 1))]),
8120 response.last_entry_ids
8121 );
8122 let (object_seq, release) = next_parked_operation(&mut parked).await;
8123 assert_eq!(3, object_seq);
8124
8125 timeout(WAIT, store.wait_durable(&provider(region_a), id(2, 1)))
8128 .await
8129 .unwrap()
8130 .unwrap();
8131 let (response_tx, mut response_rx) = oneshot::channel();
8134 store
8135 .command_tx
8136 .send(Command::WaitDurable {
8137 region_id: region_a,
8138 entry_id: id(3, 1),
8139 response: response_tx,
8140 })
8141 .await
8142 .unwrap();
8143 round_trip_actor(&store).await;
8144 assert!(matches!(
8145 response_rx.try_recv(),
8146 Err(oneshot::error::TryRecvError::Empty)
8147 ));
8148 release.send(true).unwrap();
8149 timeout(WAIT, response_rx).await.unwrap().unwrap().unwrap();
8150 }
8151
8152 #[tokio::test]
8153 async fn test_store_enqueued_obsolete_never_passes_the_durable_id() {
8154 let _serialized = SERIALIZED.lock().await;
8155 let store = open(memory_store(), &enqueued(manual())).await;
8156 let region_id = region(1);
8157 append(&store, region_id, "a1").await.unwrap();
8158 append(&store, region_id, "a2").await.unwrap();
8159
8160 store
8161 .obsolete(&provider(region_id), region_id, id(1, 2))
8162 .await
8163 .unwrap();
8164 assert_eq!(
8165 Some(&0),
8166 store.obsolete_entry_ids.lock().unwrap().get(®ion_id)
8167 );
8168 store.seal_open_batch().await.unwrap();
8169 assert_eq!(
8170 expected_entries(region_id, &[(id(1, 1), "a1"), (id(1, 2), "a2")]),
8171 read_entries(&store, region_id, 1).await
8172 );
8173 store
8174 .obsolete(&provider(region_id), region_id, id(1, 2))
8175 .await
8176 .unwrap();
8177 assert_eq!(
8178 Some(&id(1, 2)),
8179 store.obsolete_entry_ids.lock().unwrap().get(®ion_id)
8180 );
8181 assert!(read_entries(&store, region_id, 1).await.is_empty());
8182 }
8183
8184 async fn stall_second_append(
8189 config: ObjectStoreWalConfig,
8190 ) -> (
8191 Arc<ObjectStoreLogStore>,
8192 Arc<ParkedIo>,
8193 mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
8194 tokio::task::JoinHandle<Result<AppendBatchResponse>>,
8195 oneshot::Sender<bool>,
8196 ) {
8197 let (store, io, mut parked) = open_parking_creates(memory_store(), &config).await;
8198 let region_id = region(1);
8199 let response = timeout(WAIT, append(&store, region_id, "a1"))
8200 .await
8201 .unwrap()
8202 .unwrap();
8203 assert_eq!(
8204 HashMap::from([(region_id, id(1, 1))]),
8205 response.last_entry_ids
8206 );
8207 if config.max_unpersisted_age < Duration::from_secs(1) {
8208 tokio::time::sleep(config.max_unpersisted_age * 2).await;
8209 }
8210
8211 let stalled = spawn_append_batch(&store, vec![entry(&store, region_id, "a2")]);
8212 let (object_seq, release) = next_parked_operation(&mut parked).await;
8214 assert_eq!(1, object_seq);
8215 for _ in 0..16 {
8216 tokio::task::yield_now().await;
8217 }
8218 assert!(!stalled.is_finished());
8219 assert!(parked.try_recv().is_err());
8220 (store, io, parked, stalled, release)
8221 }
8222
8223 #[tokio::test]
8224 async fn test_store_enqueued_backlog_bytes_stall_admission_until_an_upload_completes() {
8225 let _serialized = SERIALIZED.lock().await;
8226 let config = ObjectStoreWalConfig {
8227 max_unpersisted_bytes: ReadableSize(1),
8228 ..enqueued(manual())
8229 };
8230 let (store, _, mut parked, stalled, release) = stall_second_append(config).await;
8231 let region_id = region(1);
8232 let third = spawn_append_batch(&store, vec![entry(&store, region_id, "a3")]);
8234 let fourth = spawn_append_batch(&store, vec![entry(&store, region_id, "a4")]);
8235 for _ in 0..16 {
8236 tokio::task::yield_now().await;
8237 }
8238 assert!(!third.is_finished() && !fourth.is_finished());
8239 assert!(parked.try_recv().is_err());
8240
8241 release.send(true).unwrap();
8245 let response = timeout(WAIT, stalled).await.unwrap().unwrap().unwrap();
8246 assert_eq!(
8247 HashMap::from([(region_id, id(2, 1))]),
8248 response.last_entry_ids
8249 );
8250 assert_eq!(vec![1], collected_object_seqs(&store).await);
8251 assert_eq!(id(1, 1), latest(&store, region_id));
8252 for (append, object_seq) in [(third, 2), (fourth, 3)] {
8253 let (parked_seq, release) = next_parked_operation(&mut parked).await;
8254 assert_eq!(object_seq, parked_seq);
8255 release.send(true).unwrap();
8256 let response = timeout(WAIT, append).await.unwrap().unwrap().unwrap();
8257 assert_eq!(
8258 HashMap::from([(region_id, id(object_seq + 1, 1))]),
8259 response.last_entry_ids
8260 );
8261 }
8262 assert_eq!(vec![1, 2, 3], collected_object_seqs(&store).await);
8263 assert_eq!(
8264 expected_entries(
8265 region_id,
8266 &[(id(1, 1), "a1"), (id(2, 1), "a2"), (id(3, 1), "a3")]
8267 ),
8268 read_entries(&store, region_id, 1).await
8269 );
8270 }
8271
8272 #[tokio::test]
8273 async fn test_store_enqueued_backlog_age_stalls_admission_until_an_upload_completes() {
8274 let _serialized = SERIALIZED.lock().await;
8275 let config = ObjectStoreWalConfig {
8276 max_unpersisted_age: Duration::from_millis(50),
8277 ..enqueued(manual())
8278 };
8279 let (store, _, _parked, stalled, release) = stall_second_append(config).await;
8280 let region_id = region(1);
8281
8282 release.send(true).unwrap();
8283 let response = timeout(WAIT, stalled).await.unwrap().unwrap().unwrap();
8284 assert_eq!(
8285 HashMap::from([(region_id, id(2, 1))]),
8286 response.last_entry_ids
8287 );
8288 assert_eq!(vec![1], collected_object_seqs(&store).await);
8289 assert_eq!(
8290 expected_entries(region_id, &[(id(1, 1), "a1")]),
8291 read_entries(&store, region_id, 1).await
8292 );
8293 }
8294
8295 #[tokio::test]
8296 async fn test_store_enqueued_stop_fails_a_stalled_append_and_uploads_the_backlog() {
8297 let _serialized = SERIALIZED.lock().await;
8298 let config = ObjectStoreWalConfig {
8299 max_unpersisted_bytes: ReadableSize(1),
8300 ..enqueued(manual())
8301 };
8302 let (store, io, mut parked, stalled, release) = stall_second_append(config).await;
8303
8304 let stop = {
8305 let store = store.clone();
8306 tokio::spawn(async move { store.stop().await })
8307 };
8308 assert_stopped(&timeout(WAIT, stalled).await.unwrap().unwrap().unwrap_err());
8309 release.send(true).unwrap();
8310 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
8311 assert_eq!(vec![0, 1], object_seqs(io.as_ref()).await);
8312 assert!(parked.try_recv().is_err());
8313 }
8314
8315 #[tokio::test]
8316 async fn test_store_enqueued_repeats_a_create_that_failed_transiently() {
8317 let _serialized = SERIALIZED.lock().await;
8318 let (io, _) = RecordingIo::over(memory_store());
8319 let store = open_over(io.clone(), &enqueued(eager())).await;
8320 let region_id = region(1);
8321
8322 io.fail_after_next_put.store(true, Ordering::Relaxed);
8325 let response = append(&store, region_id, "a1").await.unwrap();
8326 assert_eq!(
8327 HashMap::from([(region_id, id(1, 1))]),
8328 response.last_entry_ids
8329 );
8330 timeout(WAIT, store.wait_durable(&provider(region_id), id(1, 1)))
8331 .await
8332 .unwrap()
8333 .unwrap();
8334 assert_eq!(vec![1], collected_object_seqs(&store).await);
8335 assert_eq!(
8336 expected_entries(region_id, &[(id(1, 1), "a1")]),
8337 read_entries(&store, region_id, 1).await
8338 );
8339
8340 let rejected = METRIC_OBJECT_STORE_WAL_REJECTED_APPENDS_TOTAL.get();
8344 io.fail_next_put_persistently();
8345 append(&store, region_id, "a2").await.unwrap();
8346 append(&store, region_id, "a3").await.unwrap();
8347 timeout(WAIT, store.wait_durable(&provider(region_id), id(3, 1)))
8348 .await
8349 .unwrap()
8350 .unwrap();
8351 assert_eq!(vec![1, 2, 3], collected_object_seqs(&store).await);
8352 assert_eq!(
8353 rejected,
8354 METRIC_OBJECT_STORE_WAL_REJECTED_APPENDS_TOTAL.get()
8355 );
8356 }
8357
8358 #[tokio::test]
8359 async fn test_store_classifies_object_store_errors_by_kind() {
8360 let refusing = RefusingStore::new();
8361 let io = ObjectStoreIo::new(refusing.store.clone(), PREFIX).unwrap();
8362 let content = Bytes::from_static(b"wal");
8363 for (kind, requires) in [
8369 (ErrorKind::PermissionDenied, true),
8370 (ErrorKind::ConfigInvalid, true),
8371 (ErrorKind::NotFound, true),
8372 (ErrorKind::Unexpected, false),
8373 ] {
8374 refusing.refuse_writes(Some(kind));
8375 let error = io.put_if_absent(1, content.clone()).await.unwrap_err();
8376 assert!(
8377 matches!(&error, Error::WalObjectStore { operation: "write", error, .. }
8378 if error.kind() == kind && error.is_persistent()),
8379 "unexpected error for {kind:?}: {error:?}"
8380 );
8381 assert_eq!(requires, requires_intervention(&error), "{kind:?}");
8382 }
8383
8384 refusing.refuse_writes(None);
8388 io.put_if_absent(1, content.clone()).await.unwrap();
8389 for (kind, requires) in [
8390 (ErrorKind::PermissionDenied, true),
8391 (ErrorKind::NotFound, false),
8392 ] {
8393 refusing.refuse_reads(Some(kind));
8394 let error = io
8395 .put_if_absent(1, Bytes::from_static(b"other"))
8396 .await
8397 .unwrap_err();
8398 assert!(
8399 matches!(&error, Error::WalObjectStore { operation: "read", error, .. }
8400 if error.kind() == kind && error.is_persistent()),
8401 "unexpected error for {kind:?}: {error:?}"
8402 );
8403 assert_eq!(requires, requires_intervention(&error), "{kind:?}");
8404 }
8405 assert!(!requires_intervention(
8406 &WalObjectConflictSnafu {
8407 path: io.object_path(1)
8408 }
8409 .build()
8410 ));
8411 }
8412
8413 #[test]
8414 fn test_store_failing_delay_doubles_up_to_the_cap() {
8415 let now = Instant::now();
8416 let mut failing = Failing {
8417 error: Arc::new(ObjectStoreWalStoppedSnafu.build()),
8418 since: now,
8419 delay: CREATE_RETRY_DELAY,
8420 probe_at: None,
8421 failed_creates: 1,
8422 rejected_appends: 0,
8423 logged_at: now,
8424 };
8425 let mut delays = Vec::new();
8426 for _ in 0..12 {
8427 let delay = failing.delay;
8428 let scheduled_at = Instant::now();
8429 failing.schedule();
8430 assert!(failing.probe_at.unwrap() >= scheduled_at + delay);
8431 delays.push(delay.as_millis());
8432 }
8433 assert_eq!(
8434 vec![
8435 100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 30000, 30000, 30000
8436 ],
8437 delays
8438 );
8439 }
8440
8441 async fn open_failing(kind: ErrorKind) -> (Arc<ObjectStoreLogStore>, RefusingStore) {
8445 let refusing = RefusingStore::new();
8446 let store = open(refusing.store.clone(), &enqueued(eager())).await;
8447 let region_id = region(1);
8448 refusing.refuse_writes(Some(kind));
8449 let response = append(&store, region_id, "a1").await.unwrap();
8450 assert_eq!(
8451 HashMap::from([(region_id, id(1, 1))]),
8452 response.last_entry_ids
8453 );
8454 wait_until(|| failing_stores() == 1).await;
8455 (store, refusing)
8456 }
8457
8458 #[tokio::test]
8459 async fn test_store_enqueued_failure_requiring_intervention_keeps_the_backlog() {
8460 let _serialized = SERIALIZED.lock().await;
8461 let region_id = region(1);
8462 for kind in [ErrorKind::PermissionDenied, ErrorKind::ConfigInvalid] {
8464 let rejected = METRIC_OBJECT_STORE_WAL_REJECTED_APPENDS_TOTAL.get();
8465 let (store, refusing) = open_failing(kind).await;
8466 let admitted = *store.admitted_appends.borrow();
8467
8468 let error = append(&store, region_id, "a2").await.unwrap_err();
8472 assert_rejected(&error, kind);
8473 assert_eq!(admitted, *store.admitted_appends.borrow());
8474 assert_eq!(
8475 rejected + 1,
8476 METRIC_OBJECT_STORE_WAL_REJECTED_APPENDS_TOTAL.get()
8477 );
8478 let wait = {
8479 let store = store.clone();
8480 tokio::spawn(
8481 async move { store.wait_durable(&provider(region_id), id(1, 1)).await },
8482 )
8483 };
8484 timeout(WAIT, store.wait_for_durability_waits(1))
8485 .await
8486 .unwrap()
8487 .unwrap();
8488 assert!(!wait.is_finished());
8489 assert!(terminal(&store.terminal_error).is_none());
8490 assert_eq!(0, latest(&store, region_id));
8491 assert_eq!(vec![0], object_seqs(store.io.as_ref()).await);
8492
8493 refusing.refuse_writes(None);
8496 timeout(WAIT, wait).await.unwrap().unwrap().unwrap();
8497 assert_eq!(0, failing_stores());
8498 assert_eq!(
8499 expected_entries(region_id, &[(id(1, 1), "a1")]),
8500 read_entries(&store, region_id, 1).await
8501 );
8502 let response = append(&store, region_id, "a3").await.unwrap();
8503 assert_eq!(
8504 HashMap::from([(region_id, id(2, 1))]),
8505 response.last_entry_ids
8506 );
8507 timeout(WAIT, store.wait_durable(&provider(region_id), id(2, 1)))
8508 .await
8509 .unwrap()
8510 .unwrap();
8511 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
8512 store.stop().await.unwrap();
8513 }
8514 }
8515
8516 #[tokio::test]
8517 async fn test_store_enqueued_create_retries_back_off() {
8518 let _serialized = SERIALIZED.lock().await;
8519 let (store, refusing) = open_failing(ErrorKind::PermissionDenied).await;
8520 let refused = || refusing.refused_writes.lock().unwrap().clone();
8521 wait_until(|| refused().len() >= 5).await;
8522
8523 let refused = refused();
8527 for (index, delay) in [100, 200, 400, 800].into_iter().enumerate() {
8528 let gap = refused[index + 1] - refused[index];
8529 assert!(
8530 gap >= Duration::from_millis(delay),
8531 "attempt {} started {gap:?} after the one before",
8532 index + 1
8533 );
8534 }
8535 let window = refused[0] + Duration::from_millis(1600);
8536 assert!(refused.iter().filter(|at| **at < window).count() <= 5);
8537 assert_eq!(1, failing_stores());
8538
8539 refusing.refuse_writes(None);
8540 timeout(WAIT, store.wait_durable(&provider(region(1)), id(1, 1)))
8541 .await
8542 .unwrap()
8543 .unwrap();
8544 store.stop().await.unwrap();
8545 }
8546
8547 #[tokio::test]
8548 async fn test_store_enqueued_failing_store_attempts_the_lowest_batch_alone() {
8549 let _serialized = SERIALIZED.lock().await;
8550 let (store, io, mut parked) =
8551 open_parking_creates(memory_store(), &enqueued(eager())).await;
8552 let region_id = region(1);
8553 for append in spawn_appends(&store, region_id, 3).await {
8554 timeout(WAIT, append).await.unwrap().unwrap().unwrap();
8555 }
8556 let mut releases = parked_operations(&mut parked, 3).await;
8557 io.creates_refused.store(true, Ordering::SeqCst);
8558 for object_seq in [2, 1, 3] {
8559 releases.remove(&object_seq).unwrap().send(false).unwrap();
8560 }
8561
8562 for _ in 0..2 {
8563 let (object_seq, release) = next_parked_operation(&mut parked).await;
8564 assert_eq!(1, object_seq);
8565 assert_eq!(1, io.in_flight.load(Ordering::SeqCst));
8566 release.send(false).unwrap();
8567 assert!(parked.try_recv().is_err());
8568 }
8569
8570 let (object_seq, release) = next_parked_operation(&mut parked).await;
8574 assert_eq!(1, object_seq);
8575 release.send(true).unwrap();
8576 for release in parked_operations(&mut parked, 2).await.into_values() {
8577 release.send(true).unwrap();
8578 }
8579 timeout(WAIT, store.wait_durable(&provider(region_id), id(3, 1)))
8580 .await
8581 .unwrap()
8582 .unwrap();
8583 assert_eq!(0, failing_stores());
8584 assert_eq!(vec![1, 2, 3], collected_object_seqs(&store).await);
8585 assert_eq!(
8586 expected_entries(
8587 region_id,
8588 &[(id(1, 1), "a1"), (id(2, 1), "a2"), (id(3, 1), "a3")]
8589 ),
8590 read_entries(&store, region_id, 1).await
8591 );
8592 store.stop().await.unwrap();
8593 }
8594
8595 #[tokio::test]
8596 async fn test_store_enqueued_rejects_the_appends_held_back_by_a_refused_backlog() {
8597 let _serialized = SERIALIZED.lock().await;
8598 let (store, io, mut parked) =
8599 open_parking_creates(memory_store(), &enqueued(eager())).await;
8600 let region_id = region(1);
8601 let stalled = METRIC_OBJECT_STORE_WAL_STALLED_APPENDS_TOTAL.get();
8602 let stalled_waits = METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS.get_sample_count();
8603 let rejected = METRIC_OBJECT_STORE_WAL_REJECTED_APPENDS_TOTAL.get();
8604
8605 for append in spawn_appends(&store, region_id, MAX_SEALED_BATCHES - 1).await {
8608 timeout(WAIT, append).await.unwrap().unwrap().unwrap();
8609 }
8610 let held = (0..3)
8611 .map(|index| {
8612 spawn_append_batch(&store, vec![entry(&store, region_id, &format!("h{index}"))])
8613 })
8614 .collect::<Vec<_>>();
8615 let mut releases = parked_operations(&mut parked, MAX_IN_FLIGHT_CREATES).await;
8616 wait_until(|| METRIC_OBJECT_STORE_WAL_STALLED_APPENDS_TOTAL.get() == stalled + 1).await;
8617 assert!(held.iter().all(|append| !append.is_finished()));
8618
8619 io.creates_refused.store(true, Ordering::SeqCst);
8622 for release in releases.drain().map(|(_, release)| release) {
8623 release.send(false).unwrap();
8624 }
8625 for append in held {
8626 let error = timeout(WAIT, append).await.unwrap().unwrap().unwrap_err();
8627 assert_rejected(&error, ErrorKind::PermissionDenied);
8628 }
8629 assert_rejected(
8630 &append(&store, region_id, "a8").await.unwrap_err(),
8631 ErrorKind::PermissionDenied,
8632 );
8633 assert_eq!(
8634 rejected + 4,
8635 METRIC_OBJECT_STORE_WAL_REJECTED_APPENDS_TOTAL.get()
8636 );
8637 assert_eq!(
8638 stalled_waits + 1,
8639 METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS.get_sample_count()
8640 );
8641
8642 io.creates_refused.store(false, Ordering::SeqCst);
8645 let (object_seq, release) = next_parked_operation(&mut parked).await;
8646 assert_eq!(1, object_seq);
8647 release.send(true).unwrap();
8648 for _ in 2..MAX_SEALED_BATCHES {
8649 next_parked_operation(&mut parked)
8650 .await
8651 .1
8652 .send(true)
8653 .unwrap();
8654 }
8655 let last = id(MAX_SEALED_BATCHES as u64 - 1, 1);
8656 timeout(WAIT, store.wait_durable(&provider(region_id), last))
8657 .await
8658 .unwrap()
8659 .unwrap();
8660 let appended = spawn_append_batch(&store, vec![entry(&store, region_id, "a9")]);
8661 next_parked_operation(&mut parked)
8662 .await
8663 .1
8664 .send(true)
8665 .unwrap();
8666 let response = timeout(WAIT, appended).await.unwrap().unwrap().unwrap();
8667 assert_eq!(
8668 HashMap::from([(region_id, id(MAX_SEALED_BATCHES as u64, 1))]),
8669 response.last_entry_ids
8670 );
8671 store.stop().await.unwrap();
8672 }
8673
8674 #[tokio::test]
8675 async fn test_store_enqueued_stop_uploads_a_refused_backlog_at_once() {
8676 let _serialized = SERIALIZED.lock().await;
8677 let (store, io, mut parked) =
8678 open_parking_creates(memory_store(), &enqueued(eager())).await;
8679 let region_id = region(1);
8680 for append in spawn_appends(&store, region_id, 3).await {
8681 timeout(WAIT, append).await.unwrap().unwrap().unwrap();
8682 }
8683 io.creates_refused.store(true, Ordering::SeqCst);
8684 for release in parked_operations(&mut parked, 3).await.into_values() {
8685 release.send(false).unwrap();
8686 }
8687 wait_until(|| failing_stores() == 1).await;
8688
8689 let stop = begin_spawned_stop(&store).await;
8693 let releases = parked_operations(&mut parked, 3).await;
8694 for object_seq in 1..=3 {
8695 assert!(releases.contains_key(&object_seq));
8696 }
8697 for release in releases.into_values() {
8698 release.send(true).unwrap();
8699 }
8700 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
8701 assert_eq!(0, failing_stores());
8702 assert_eq!(vec![0, 1, 2, 3], object_seqs(io.as_ref()).await);
8703 assert_eq!(id(3, 1), latest(&store, region_id));
8704 }
8705
8706 #[tokio::test]
8707 async fn test_store_enqueued_conflict_poisons() {
8708 let _serialized = SERIALIZED.lock().await;
8709 for foreign_epoch in [0, 2] {
8713 let object_store = memory_store();
8714 let (io, reads) = RecordingIo::over(object_store.clone());
8715 let store = open_over(io.clone(), &enqueued(eager())).await;
8716 let region_id = region(1);
8717 put_foreign(&object_store, 1, foreign_epoch).await;
8718 let assert_poisoned = |error: Error| {
8719 assert!(
8720 matches!(unwrap_shared(&error), Error::WalObjectConflict { .. }),
8721 "unexpected error: {error:?}"
8722 );
8723 };
8724
8725 let second = entry(&store, region_id, "a2");
8727 append(&store, region_id, "a1").await.unwrap();
8728 let error = timeout(WAIT, store.wait_durable(&provider(region_id), id(1, 1)))
8729 .await
8730 .unwrap()
8731 .unwrap_err();
8732 assert_poisoned(error);
8733 assert_poisoned(store.append_batch(vec![second]).await.unwrap_err());
8734 assert!(store.latest_entry_id(&provider(region_id)).is_err());
8735 assert_poisoned(store.stop().await.unwrap_err());
8737 assert!(reads.lock().unwrap().is_empty());
8738 }
8739 }
8740
8741 #[tokio::test]
8742 async fn test_store_enqueued_stop_reports_a_backlog_lost_before_the_stop_command() {
8743 let _serialized = SERIALIZED.lock().await;
8744 let (store, io, mut parked) =
8745 open_parking_creates(memory_store(), &enqueued(manual())).await;
8746 let region_id = region(1);
8747 append(&store, region_id, "a1").await.unwrap();
8748 let seal = {
8749 let store = store.clone();
8750 tokio::spawn(async move { store.seal_open_batch().await })
8751 };
8752 let (object_seq, release) = next_parked_operation(&mut parked).await;
8753 assert_eq!(1, object_seq);
8754
8755 store.begin_stop();
8759 release.send(false).unwrap();
8760 assert_stopped(&timeout(WAIT, seal).await.unwrap().unwrap().unwrap_err());
8761 let error = timeout(WAIT, store.wait_durable(&provider(region_id), id(1, 1)))
8764 .await
8765 .unwrap()
8766 .unwrap_err();
8767 assert!(
8768 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
8769 "unexpected error: {error:?}"
8770 );
8771 timeout(WAIT, store.wait_durable(&provider(region_id), 0))
8772 .await
8773 .unwrap()
8774 .unwrap();
8775 let error = store.stop().await.unwrap_err();
8776 assert!(
8777 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
8778 "unexpected error: {error:?}"
8779 );
8780 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
8781 assert!(parked.try_recv().is_err());
8782 }
8783
8784 #[tokio::test]
8785 async fn test_store_enqueued_stop_uploads_the_backlog() {
8786 let _serialized = SERIALIZED.lock().await;
8787 let store = open(memory_store(), &enqueued(manual())).await;
8788 let region_id = region(1);
8789 append(&store, region_id, "a1").await.unwrap();
8790 append(&store, region_id, "a2").await.unwrap();
8791 assert_eq!(vec![0], object_seqs(store.io.as_ref()).await);
8792
8793 store.stop().await.unwrap();
8794 assert_eq!(vec![0, 1], object_seqs(store.io.as_ref()).await);
8795 assert_eq!(id(1, 2), latest(&store, region_id));
8796 assert_eq!(
8797 expected_entries(region_id, &[(id(1, 1), "a1"), (id(1, 2), "a2")]),
8798 read_entries(&store, region_id, 1).await
8799 );
8800
8801 for failure in ["temporary", "persistent", "permanent"] {
8805 let (io, _) = RecordingIo::over(memory_store());
8806 let store = open_over(io.clone(), &enqueued(manual())).await;
8807 append(&store, region_id, "a1").await.unwrap();
8808 match failure {
8809 "temporary" => store.fail_creates(),
8810 "persistent" => io.fail_next_put_persistently(),
8811 _ => io.fail_next_put_permanently(),
8812 }
8813 let error = store.stop().await.unwrap_err();
8814 assert!(
8815 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
8816 "unexpected error: {error:?}"
8817 );
8818 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
8819 assert!(terminal(&store.terminal_error).is_none());
8820 store.stop().await.unwrap();
8821 }
8822 }
8823
8824 #[tokio::test]
8825 async fn test_store_enqueued_issued_id_needs_no_floor_before_it_is_durable() {
8826 let _serialized = SERIALIZED.lock().await;
8827 let store = open(memory_store(), &enqueued(manual())).await;
8828 let region_id = region(1);
8829 append(&store, region_id, "a1").await.unwrap();
8830
8831 store
8836 .obsolete(&provider(region_id), region_id, id(1, 1))
8837 .await
8838 .unwrap();
8839 assert_eq!(
8840 Some(&0),
8841 store.obsolete_entry_ids.lock().unwrap().get(®ion_id)
8842 );
8843 let error = store
8844 .obsolete(&provider(region_id), region_id, id(1, 2))
8845 .await
8846 .unwrap_err();
8847 assert!(
8848 matches!(
8849 error,
8850 Error::WalObjectSequenceUnsettled { object_seq: 1, .. }
8851 ),
8852 "unexpected error: {error:?}"
8853 );
8854
8855 store.seal_open_batch().await.unwrap();
8856 assert_eq!(
8857 expected_entries(region_id, &[(id(1, 1), "a1")]),
8858 read_entries(&store, region_id, 0).await
8859 );
8860 store
8861 .obsolete(&provider(region_id), region_id, id(1, 1))
8862 .await
8863 .unwrap();
8864 assert!(read_entries(&store, region_id, 0).await.is_empty());
8865 let response = append(&store, region_id, "a2").await.unwrap();
8866 assert_eq!(
8867 HashMap::from([(region_id, id(2, 1))]),
8868 response.last_entry_ids
8869 );
8870 }
8871
8872 #[tokio::test]
8873 async fn test_store_enqueued_inherited_watermark_raises_the_floor() {
8874 let _serialized = SERIALIZED.lock().await;
8875 let object_store = memory_store();
8876 let store = open(object_store.clone(), &enqueued(manual())).await;
8877 let region_id = region(1);
8878
8879 store
8882 .obsolete(&provider(region_id), region_id, id(5, 1))
8883 .await
8884 .unwrap();
8885 assert_eq!(
8886 Some(&0),
8887 store.obsolete_entry_ids.lock().unwrap().get(®ion_id)
8888 );
8889 let response = append(&store, region_id, "r1").await.unwrap();
8890 assert_eq!(
8891 HashMap::from([(region_id, id(6, 1))]),
8892 response.last_entry_ids
8893 );
8894 store.seal_open_batch().await.unwrap();
8895 timeout(WAIT, store.wait_durable(&provider(region_id), id(6, 1)))
8896 .await
8897 .unwrap()
8898 .unwrap();
8899 assert_eq!(id(6, 1), latest(&store, region_id));
8900 store.stop().await.unwrap();
8901
8902 let store = open(object_store, &enqueued(manual())).await;
8904 assert_eq!(
8905 expected_entries(region_id, &[(id(6, 1), "r1")]),
8906 read_entries(&store, region_id, id(5, 1) + 1).await
8907 );
8908 }
8909
8910 #[tokio::test]
8911 async fn test_store_durability_wait_pending_at_stop_fails_with_stopped() {
8912 let _serialized = SERIALIZED.lock().await;
8913 let store = open(memory_store(), &manual()).await;
8914 let region_id = region(1);
8915 let append = spawn_append_batch(&store, vec![entry(&store, region_id, "a1")]);
8916 store.wait_for_admitted_appends(1).await.unwrap();
8917 let wait = {
8918 let store = store.clone();
8919 tokio::spawn(async move { store.wait_durable(&provider(region_id), id(1, 1)).await })
8920 };
8921 for _ in 0..16 {
8922 tokio::task::yield_now().await;
8923 }
8924 assert!(!wait.is_finished());
8925
8926 store.stop().await.unwrap();
8927 assert_stopped(&timeout(WAIT, append).await.unwrap().unwrap().unwrap_err());
8928 assert_stopped(&timeout(WAIT, wait).await.unwrap().unwrap().unwrap_err());
8929 }
8930
8931 async fn reclaim_boundary_of(io: &dyn WalObjectIo, object_seq: u64) -> u64 {
8933 decode_header(&io.get(object_seq).await.unwrap())
8934 .unwrap()
8935 .reclaim_boundary
8936 }
8937
8938 #[tokio::test]
8939 async fn test_store_stamps_the_reclaim_boundary_and_collects_below_it() {
8940 let _serialized = SERIALIZED.lock().await;
8941 let store = open(memory_store(), &eager()).await;
8942 let io = store.io.clone();
8943 let (one, two, three) = (region(1), region(2), region(3));
8944 let deleted = METRIC_OBJECT_STORE_WAL_DELETED_OBJECTS_TOTAL.get();
8945
8946 append(&store, one, "a1").await.unwrap();
8949 append(&store, two, "b1").await.unwrap();
8950 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
8951 assert_eq!(1, reclaim_boundary_of(io.as_ref(), 1).await);
8952 assert_eq!(1, reclaim_boundary_of(io.as_ref(), 2).await);
8953
8954 store.obsolete(&provider(one), one, id(1, 1)).await.unwrap();
8957 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
8958 append(&store, one, "a2").await.unwrap();
8959 assert_eq!(2, reclaim_boundary_of(io.as_ref(), 3).await);
8960 assert_eq!(vec![2, 3], collected_object_seqs(&store).await);
8961
8962 store.obsolete(&provider(two), two, id(2, 1)).await.unwrap();
8966 store.obsolete_all(&provider(one), one).await.unwrap();
8967 assert_eq!(4, reclaim_boundary_of(io.as_ref(), 4).await);
8968 assert_eq!(vec![4], collected_object_seqs(&store).await);
8969 append(&store, two, "b2").await.unwrap();
8970 assert_eq!(5, reclaim_boundary_of(io.as_ref(), 5).await);
8971 assert_eq!(vec![5], collected_object_seqs(&store).await);
8972
8973 append(&store, three, "c1").await.unwrap();
8975 assert_eq!(5, reclaim_boundary_of(io.as_ref(), 6).await);
8976 assert_eq!(vec![5, 6], collected_object_seqs(&store).await);
8977 assert!(METRIC_OBJECT_STORE_WAL_DELETED_OBJECTS_TOTAL.get() >= deleted + 5);
8979 assert_eq!(
8980 expected_entries(two, &[(id(5, 1), "b2")]),
8981 read_entries(&store, two, 0).await
8982 );
8983 store.stop().await.unwrap();
8984 }
8985
8986 #[tokio::test]
8987 async fn test_store_collects_failed_batches_below_a_durable_boundary() {
8988 let _serialized = SERIALIZED.lock().await;
8989 let (io, _) = RecordingIo::over(memory_store());
8990 let store = open_over(io.clone(), &eager()).await;
8991 let region_id = region(1);
8992 append(&store, region_id, "a1").await.unwrap();
8993 append(&store, region_id, "a2").await.unwrap();
8994 store
8995 .obsolete(&provider(region_id), region_id, id(2, 1))
8996 .await
8997 .unwrap();
8998
8999 io.fail_after_next_put.store(true, Ordering::Relaxed);
9003 append(&store, region_id, "a3").await.unwrap_err();
9004 assert_eq!(3, reclaim_boundary_of(io.as_ref(), 3).await);
9005 io.fail_next_put_persistently();
9006 append(&store, region_id, "a4").await.unwrap_err();
9007 assert_eq!(vec![1, 2, 3], collected_object_seqs(&store).await);
9008
9009 append(&store, region_id, "a5").await.unwrap();
9012 assert_eq!(vec![3, 5], collected_object_seqs(&store).await);
9013 store
9014 .obsolete(&provider(region_id), region_id, id(5, 1))
9015 .await
9016 .unwrap();
9017 append(&store, region_id, "a6").await.unwrap();
9018 assert_eq!(vec![6], collected_object_seqs(&store).await);
9019 assert_eq!(
9020 expected_entries(region_id, &[(id(6, 1), "a6")]),
9021 read_entries(&store, region_id, 0).await
9022 );
9023 store.stop().await.unwrap();
9024 }
9025
9026 #[tokio::test]
9027 async fn test_store_attempts_a_failed_delete_again_at_the_next_collection() {
9028 let _serialized = SERIALIZED.lock().await;
9029 let (io, mut parked) = ParkedIo::parking_creates(memory_store());
9030 let store = open_over(io.clone(), &eager()).await;
9031 io.deletes_parked.store(true, Ordering::SeqCst);
9032 let region_id = region(1);
9033 let failed = METRIC_OBJECT_STORE_WAL_FAILED_DELETES_TOTAL.get();
9034 append(&store, region_id, "a1").await.unwrap();
9035 append(&store, region_id, "a2").await.unwrap();
9036 let (object_seq, release) = next_parked_operation(&mut parked).await;
9037 assert_eq!(0, object_seq);
9038 release.send(false).unwrap();
9039
9040 timeout(WAIT, store.wait_for_garbage_collection())
9043 .await
9044 .unwrap();
9045 assert_eq!(
9046 failed + 1,
9047 METRIC_OBJECT_STORE_WAL_FAILED_DELETES_TOTAL.get()
9048 );
9049 assert!(store.catalog.read().unwrap().contains_object(0));
9050 append(&store, region_id, "a3").await.unwrap();
9051 assert!(parked.try_recv().is_err());
9052 assert_eq!(vec![0, 1, 2, 3], object_seqs(io.as_ref()).await);
9053
9054 store
9056 .obsolete(&provider(region_id), region_id, id(1, 1))
9057 .await
9058 .unwrap();
9059 let (object_seq, release) = next_parked_operation(&mut parked).await;
9060 assert_eq!(0, object_seq);
9061 release.send(true).unwrap();
9062 assert_eq!(vec![1, 2, 3], collected_object_seqs(&store).await);
9063 assert!(!store.catalog.read().unwrap().contains_object(0));
9064 store.stop().await.unwrap();
9065 }
9066
9067 #[tokio::test]
9068 async fn test_store_collection_ends_with_stop_and_poison() {
9069 let _serialized = SERIALIZED.lock().await;
9070 for poison in [false, true] {
9071 let object_store = memory_store();
9072 let (io, mut parked) = ParkedIo::parking_creates(object_store.clone());
9073 let store = open_over(io.clone(), &eager()).await;
9074 io.deletes_parked.store(true, Ordering::SeqCst);
9075 let region_id = region(1);
9076 for index in 1..=5 {
9077 append(&store, region_id, &format!("a{index}"))
9078 .await
9079 .unwrap();
9080 }
9081 store
9082 .obsolete(&provider(region_id), region_id, id(5, 1))
9083 .await
9084 .unwrap();
9085 append(&store, region_id, "a6").await.unwrap();
9086 let releases = parked_operations(&mut parked, MAX_IN_FLIGHT_DELETES).await;
9089 assert!(parked.try_recv().is_err());
9090
9091 let mut stop = None;
9092 if poison {
9093 put_foreign(&object_store, 7, 100).await;
9094 append(&store, region_id, "a7").await.unwrap_err();
9095 } else {
9096 let mut stopping = Box::pin(store.stop());
9100 assert!((&mut stopping).now_or_never().is_none());
9101 assert_stopped(
9102 &store
9103 .obsolete_all(&provider(region_id), region_id)
9104 .await
9105 .unwrap_err(),
9106 );
9107 assert!((&mut stopping).now_or_never().is_none());
9108 stop = Some(stopping);
9109 }
9110 for release in releases.into_values() {
9111 release.send(true).unwrap();
9112 }
9113 match stop {
9114 Some(stop) => timeout(WAIT, stop).await.unwrap().unwrap(),
9115 None => timeout(WAIT, store.wait_for_garbage_collection())
9116 .await
9117 .unwrap(),
9118 }
9119
9120 assert!(parked.try_recv().is_err());
9122 assert_eq!(
9123 MAX_IN_FLIGHT_DELETES,
9124 io.max_in_flight.load(Ordering::SeqCst)
9125 );
9126 let mut expected = vec![4, 5, 6];
9127 if poison {
9128 expected.push(7);
9129 }
9130 assert_eq!(expected, object_seqs(io.as_ref()).await);
9131 }
9132 }
9133
9134 #[tokio::test]
9135 async fn test_store_drops_the_holes_of_a_collected_object() {
9136 let _serialized = SERIALIZED.lock().await;
9137 let object_store = memory_store();
9138 write_corrupted_segment(&object_store).await;
9139 let store = open(object_store, &eager()).await;
9140 read_entries(&store, region(2), 0).await;
9141 assert_eq!(1, store.wal_holes(&provider(region(2))).unwrap().len());
9142
9143 store
9144 .obsolete(&provider(region(1)), region(1), 1)
9145 .await
9146 .unwrap();
9147 store
9148 .obsolete(&provider(region(2)), region(2), 10)
9149 .await
9150 .unwrap();
9151 append(&store, region(1), "a").await.unwrap();
9152 assert_eq!(vec![3], collected_object_seqs(&store).await);
9153 assert!(store.wal_holes(&provider(region(2))).unwrap().is_empty());
9154 store.stop().await.unwrap();
9155 }
9156
9157 #[tokio::test]
9158 async fn test_store_records_no_hole_for_a_collected_object() {
9159 let _serialized = SERIALIZED.lock().await;
9160 let object_store = memory_store();
9161 let (_, footer) = write_corrupted_segment(&object_store).await;
9162 let store = open(object_store, &eager()).await;
9163 let skipped = METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get();
9164
9165 store.catalog.write().unwrap().remove_object(0);
9168 let error = CorruptedWalObjectSnafu { reason: "damaged" }.build();
9169 record_skipped_segment(
9170 &store.catalog,
9171 &store.wal_holes,
9172 store.io.as_ref(),
9173 0,
9174 &footer[1],
9175 &error,
9176 );
9177 assert!(store.wal_holes(&provider(region(2))).unwrap().is_empty());
9178 assert_eq!(
9179 skipped,
9180 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
9181 );
9182 store.stop().await.unwrap();
9183 }
9184
9185 #[tokio::test]
9186 async fn test_store_collects_a_failed_batch_whose_create_completes_late() {
9187 let _serialized = SERIALIZED.lock().await;
9188 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
9189 let region_id = region(1);
9190 let appends = spawn_appends(&store, region_id, 2).await;
9192 let mut releases = parked_operations(&mut parked, 2).await;
9193 releases.remove(&1).unwrap().send(false).unwrap();
9194 for append in appends {
9195 timeout(WAIT, append).await.unwrap().unwrap().unwrap_err();
9196 }
9197
9198 for object_seq in [3, 4] {
9201 let append = spawn_append_batch(&store, vec![entry(&store, region_id, "a")]);
9202 let (parked_seq, release) = next_parked_operation(&mut parked).await;
9203 assert_eq!(object_seq, parked_seq);
9204 release.send(true).unwrap();
9205 timeout(WAIT, append).await.unwrap().unwrap().unwrap();
9206 }
9207 assert_eq!(vec![3, 4], collected_object_seqs(&store).await);
9208
9209 io.deletes_parked.store(true, Ordering::SeqCst);
9212 releases.remove(&2).unwrap().send(true).unwrap();
9213 let (object_seq, release) = next_parked_operation(&mut parked).await;
9214 assert_eq!(2, object_seq);
9215 release.send(true).unwrap();
9216 assert_eq!(vec![3, 4], collected_object_seqs(&store).await);
9217 store.stop().await.unwrap();
9218 }
9219
9220 async fn recover_listed(io: &ObjectStoreIo, listed: Vec<ListedObject>) -> Result<Recovered> {
9221 finish_recovery(fetch_footers(io, listing(listed), RECOVERY_CONCURRENCY).await?)
9222 }
9223
9224 #[tokio::test]
9225 async fn test_store_recovery_tolerates_objects_collected_after_the_listing() {
9226 let _serialized = SERIALIZED.lock().await;
9227 let io = ObjectStoreIo::new(memory_store(), PREFIX).unwrap();
9228 let bounded = |object_seq, prev, reclaim_boundary| Header {
9229 reclaim_boundary,
9230 ..chain_header(object_seq, 1, prev)
9231 };
9232 put_object_with_header(&io, chain_header(0, 1, None), &[]).await;
9233 put_object_with_header(&io, chain_header(1, 1, Some((0, 1))), &[]).await;
9234 put_object_with_header(&io, bounded(2, Some((1, 1)), 2), &[]).await;
9235 put_object_with_header(&io, bounded(3, Some((2, 1)), 2), &[]).await;
9236 let listed = io.list().await.unwrap();
9237
9238 io.delete(1).await.unwrap();
9241 let recovered = recover_listed(&io, listed.clone()).await.unwrap();
9242 assert_eq!(Some(3), recovered.tip.map(|tip| tip.object_seq));
9243 assert_eq!(2, recovered.reclaim_boundary);
9244 assert_eq!(vec![0], recovered.unindexed);
9245 assert_eq!(4, recovered.next_object_seq);
9246
9247 io.delete(2).await.unwrap();
9250 let error = recover_listed(&io, listed).await.unwrap_err();
9251 assert!(is_not_found(&error), "unexpected error: {error:?}");
9252 }
9253
9254 #[tokio::test]
9255 async fn test_store_read_skips_an_object_collected_after_it_was_listed() {
9256 let _serialized = SERIALIZED.lock().await;
9257 let store = open(memory_store(), &eager()).await;
9258 let region_id = region(1);
9259 let skipped = METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get();
9260 append(&store, region_id, "a1").await.unwrap();
9261 append(&store, region_id, "a2").await.unwrap();
9262
9263 let stream = store.read(&provider(region_id), 0).await.unwrap();
9265 store
9266 .obsolete(&provider(region_id), region_id, id(1, 1))
9267 .await
9268 .unwrap();
9269 append(&store, region_id, "a3").await.unwrap();
9270 assert_eq!(vec![2, 3], collected_object_seqs(&store).await);
9271
9272 let entries = stream
9273 .try_collect::<Vec<_>>()
9274 .await
9275 .unwrap()
9276 .into_iter()
9277 .flatten()
9278 .collect::<Vec<_>>();
9279 assert_eq!(expected_entries(region_id, &[(id(2, 1), "a2")]), entries);
9280 assert!(store.wal_holes(&provider(region_id)).unwrap().is_empty());
9281 assert_eq!(
9282 skipped,
9283 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
9284 );
9285 store.stop().await.unwrap();
9286 }
9287
9288 #[tokio::test]
9289 async fn test_store_read_waits_for_an_undecided_delete() {
9290 let _serialized = SERIALIZED.lock().await;
9291 for deleted in [true, false] {
9292 let object_store = memory_store();
9293 let (io, mut parked) = ParkedIo::parking_creates(object_store.clone());
9294 let store = open_over(io.clone(), &eager()).await;
9295 io.deletes_parked.store(true, Ordering::SeqCst);
9296 let region_id = region(1);
9297 let skipped = METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get();
9298 append(&store, region_id, "a1").await.unwrap();
9299 next_parked_operation(&mut parked)
9300 .await
9301 .1
9302 .send(true)
9303 .unwrap();
9304 append(&store, region_id, "a2").await.unwrap();
9305
9306 let stream = store.read(&provider(region_id), 0).await.unwrap();
9309 store
9310 .obsolete(&provider(region_id), region_id, id(1, 1))
9311 .await
9312 .unwrap();
9313 append(&store, region_id, "a3").await.unwrap();
9314 let (object_seq, release) = next_parked_operation(&mut parked).await;
9315 assert_eq!(1, object_seq);
9316 object_store
9317 .delete(&object_path(&object_store, 1))
9318 .await
9319 .unwrap();
9320 let read = tokio::spawn(stream.try_collect::<Vec<_>>());
9321 let waiting = || {
9322 store
9323 .deleting
9324 .lock()
9325 .get(&1)
9326 .map_or(0, |settled| settled.receiver_count())
9327 };
9328 timeout(WAIT, async {
9329 while waiting() == 0 {
9330 tokio::task::yield_now().await;
9331 }
9332 })
9333 .await
9334 .unwrap();
9335 assert!(!read.is_finished());
9336
9337 release.send(deleted).unwrap();
9340 let result = timeout(WAIT, read).await.unwrap().unwrap();
9341 if deleted {
9342 let entries = result.unwrap().into_iter().flatten().collect::<Vec<_>>();
9343 assert_eq!(expected_entries(region_id, &[(id(2, 1), "a2")]), entries);
9344 } else {
9345 let error = result.unwrap_err();
9346 assert!(is_not_found(&error), "unexpected error: {error:?}");
9347 }
9348 assert!(store.wal_holes(&provider(region_id)).unwrap().is_empty());
9349 assert_eq!(
9350 skipped,
9351 METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get()
9352 );
9353 store.stop().await.unwrap();
9354 }
9355 }
9356
9357 #[tokio::test]
9358 async fn test_store_recovers_from_the_reclaim_boundary_of_the_tip() {
9359 let _serialized = SERIALIZED.lock().await;
9360 let object_store = memory_store();
9361 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
9362 let region_id = region(1);
9363 let store = open(object_store.clone(), &eager()).await;
9364 append(&store, region_id, "a1").await.unwrap();
9365 store
9366 .obsolete(&provider(region_id), region_id, id(1, 1))
9367 .await
9368 .unwrap();
9369 append(&store, region_id, "a2").await.unwrap();
9370 assert_eq!(vec![2], collected_object_seqs(&store).await);
9371 store.stop().await.unwrap();
9372
9373 let store = open(object_store.clone(), &eager()).await;
9376 assert_eq!(2, reclaim_boundary_of(&io, 3).await);
9377 assert_eq!(
9378 expected_entries(region_id, &[(id(2, 1), "a2")]),
9379 read_entries(&store, region_id, 0).await
9380 );
9381 store
9382 .obsolete(&provider(region_id), region_id, id(2, 1))
9383 .await
9384 .unwrap();
9385 append(&store, region_id, "a3").await.unwrap();
9386 assert_eq!(vec![4], collected_object_seqs(&store).await);
9387 store.stop().await.unwrap();
9388
9389 let store = open(object_store, &eager()).await;
9390 assert_eq!(4, reclaim_boundary_of(&io, 5).await);
9391 assert_eq!(
9392 expected_entries(region_id, &[(id(4, 1), "a3")]),
9393 read_entries(&store, region_id, 0).await
9394 );
9395 assert_eq!(vec![4, 5], object_seqs(&io).await);
9396 store.stop().await.unwrap();
9397 }
9398
9399 #[tokio::test]
9400 async fn test_store_collects_a_late_object_of_an_earlier_epoch_below_the_boundary() {
9401 let _serialized = SERIALIZED.lock().await;
9402 let object_store = memory_store();
9403 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
9404 let region_id = region(1);
9405 let record = |entry_id, data: &'static str| Record {
9406 region_id,
9407 entry_id,
9408 payload: Bytes::from_static(data.as_bytes()),
9409 };
9410 let bounded = |object_seq, epoch, prev, reclaim_boundary| Header {
9411 reclaim_boundary,
9412 ..chain_header(object_seq, epoch, prev)
9413 };
9414 put_object_with_header(&io, chain_header(0, 1, None), &[]).await;
9415 put_object_with_header(
9416 &io,
9417 chain_header(1, 1, Some((0, 1))),
9418 &[record(id(1, 1), "a")],
9419 )
9420 .await;
9421 put_object_with_header(
9425 &io,
9426 bounded(2, 1, Some((1, 1)), 2),
9427 &[record(id(2, 1), "late")],
9428 )
9429 .await;
9430 put_object_with_header(&io, chain_header(3, 4, Some((1, 1))), &[]).await;
9431 put_object_with_header(
9432 &io,
9433 bounded(4, 4, Some((3, 4)), 4),
9434 &[record(id(4, 1), "b")],
9435 )
9436 .await;
9437 let recovered = recover(&io).await.unwrap();
9438 assert_eq!(Some(4), recovered.tip.map(|tip| tip.object_seq));
9439 assert_eq!(4, recovered.reclaim_boundary);
9440 assert_eq!(vec![0, 1, 2, 3], recovered.unindexed);
9441
9442 let store = open(object_store, &eager()).await;
9445 assert_eq!(
9446 expected_entries(region_id, &[(id(4, 1), "b")]),
9447 read_entries(&store, region_id, 0).await
9448 );
9449 store
9450 .obsolete(&provider(region_id), region_id, id(4, 1))
9451 .await
9452 .unwrap();
9453 assert_eq!(vec![4, 5], collected_object_seqs(&store).await);
9454 store.stop().await.unwrap();
9455 }
9456
9457 fn spawn_release(
9458 store: &Arc<ObjectStoreLogStore>,
9459 region_id: RegionId,
9460 ) -> tokio::task::JoinHandle<Result<()>> {
9461 let store = store.clone();
9462 tokio::spawn(async move { store.release_region(&provider(region_id), region_id).await })
9463 }
9464
9465 fn release(region_id: RegionId, entry_id: EntryId) -> Release {
9466 Release {
9467 region_id,
9468 entry_id,
9469 }
9470 }
9471
9472 async fn releases_of(io: &dyn WalObjectIo, object_seq: u64) -> Vec<Release> {
9474 let decoded = decode_object(&io.get(object_seq).await.unwrap()).unwrap();
9475 let record = decoded
9476 .records
9477 .iter()
9478 .find(|record| record.region_id == RELEASE_REGION_ID)
9479 .unwrap();
9480 assert_eq!(entry_id(object_seq, 1), record.entry_id);
9481 decode_releases(&record.payload).unwrap()
9482 }
9483
9484 fn watermark(store: &ObjectStoreLogStore, region_id: RegionId) -> Option<EntryId> {
9485 store
9486 .obsolete_entry_ids
9487 .lock()
9488 .unwrap()
9489 .get(®ion_id)
9490 .copied()
9491 }
9492
9493 #[tokio::test]
9494 async fn test_store_release_writes_a_record_of_the_largest_issued_id() {
9495 let _serialized = SERIALIZED.lock().await;
9496 let store = open(memory_store(), &eager()).await;
9497 let (one, two) = (region(1), region(2));
9498 store
9499 .append_batch(vec![entry(&store, one, "a1"), entry(&store, two, "b1")])
9500 .await
9501 .unwrap();
9502
9503 store.release_region(&provider(one), one).await.unwrap();
9507 assert_eq!(
9508 vec![release(one, id(1, 1))],
9509 releases_of(store.io.as_ref(), 2).await
9510 );
9511 assert_eq!(1, reclaim_boundary_of(store.io.as_ref(), 2).await);
9512 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
9513 assert!(read_entries(&store, one, 0).await.is_empty());
9514 assert_eq!(
9517 vec![provider(one), provider(two)],
9518 store.list_namespaces().await.unwrap()
9519 );
9520 store.stop().await.unwrap();
9521 }
9522
9523 #[tokio::test]
9524 async fn test_store_recovery_seeds_watermarks_from_release_records() {
9525 let _serialized = SERIALIZED.lock().await;
9526 let object_store = memory_store();
9527 let (one, two) = (region(1), region(2));
9528 let store = open(object_store.clone(), &eager()).await;
9529 store
9533 .append_batch(vec![entry(&store, one, "a1"), entry(&store, two, "b1")])
9534 .await
9535 .unwrap();
9536 append(&store, one, "a2").await.unwrap();
9537 store.release_region(&provider(one), one).await.unwrap();
9538 assert_eq!(1, reclaim_boundary_of(store.io.as_ref(), 3).await);
9539 store.stop().await.unwrap();
9540
9541 let store = open(object_store, &eager()).await;
9544 assert_eq!(vec![1, 2, 3, 4], collected_object_seqs(&store).await);
9545 assert_eq!(Some(id(2, 1)), watermark(&store, one));
9546 assert!(read_entries(&store, one, 0).await.is_empty());
9547 store.obsolete(&provider(one), one, id(1, 1)).await.unwrap();
9548 assert_eq!(Some(id(2, 1)), watermark(&store, one));
9549
9550 store.obsolete(&provider(two), two, id(1, 1)).await.unwrap();
9553 append(&store, two, "b2").await.unwrap();
9554 assert_eq!(5, reclaim_boundary_of(store.io.as_ref(), 5).await);
9555 assert_eq!(vec![5], collected_object_seqs(&store).await);
9556 assert_eq!(
9557 expected_entries(two, &[(id(5, 1), "b2")]),
9558 read_entries(&store, two, 0).await
9559 );
9560 store.stop().await.unwrap();
9561 }
9562
9563 #[tokio::test]
9564 async fn test_store_recovery_takes_release_records_from_the_footer_reads() {
9565 let _serialized = SERIALIZED.lock().await;
9566 let object_store = memory_store();
9567 let (one, two, three) = (region(1), region(2), region(3));
9568 let store = open(object_store.clone(), &eager()).await;
9569 store
9573 .append_batch(vec![entry(&store, one, "a1"), entry(&store, two, "b1")])
9574 .await
9575 .unwrap();
9576 store.release_region(&provider(one), one).await.unwrap();
9577 store.obsolete_all(&provider(three), three).await.unwrap();
9578 store.stop().await.unwrap();
9579
9580 let (io, reads) = RecordingIo::over(object_store);
9584 let store = open_over(io, &eager()).await;
9585 assert_eq!(Some(id(1, 1)), watermark(&store, one));
9586 assert_eq!(None, watermark(&store, two));
9587 assert_eq!(Some(EntryId::MAX), watermark(&store, three));
9588 assert!(reads.lock().unwrap().is_empty());
9589 store.stop().await.unwrap();
9590 }
9591
9592 #[tokio::test]
9593 async fn test_store_recovery_reads_a_release_segment_outside_the_tail_window() {
9594 let _serialized = SERIALIZED.lock().await;
9595 let object_store = memory_store();
9596 let releases = (1..=5_000)
9599 .map(|number| (region(number), id(0, 1)))
9600 .collect::<BTreeMap<_, _>>();
9601 let record = Record {
9602 region_id: RELEASE_REGION_ID,
9603 entry_id: id(0, 1),
9604 payload: encode_releases(&releases),
9605 };
9606 put_records(&object_store, 0, &[record]).await;
9607 let (io, reads) = RecordingIo::over(object_store);
9608 let object = io.list().await.unwrap().remove(0);
9609 let (_, footer) = footer_of(&io.get(0).await.unwrap());
9610 let segment = &footer[0];
9611 assert!(segment.segment_offset < object.size - RECOVERY_TAIL_WINDOW as u64);
9612
9613 let store = open_over(io, &eager()).await;
9614 for number in [1, 5_000] {
9615 assert_eq!(Some(id(0, 1)), watermark(&store, region(number)));
9616 }
9617 let mut reads = reads.lock().unwrap().clone();
9618 reads.sort_unstable();
9619 assert_eq!(
9620 vec![
9621 (0, 0, HEADER_LEN as u64),
9622 (0, segment.segment_offset, segment.segment_len),
9623 (
9624 0,
9625 object.size - RECOVERY_TAIL_WINDOW as u64,
9626 RECOVERY_TAIL_WINDOW as u64
9627 ),
9628 ],
9629 reads
9630 );
9631 store.stop().await.unwrap();
9632 }
9633
9634 #[tokio::test]
9635 async fn test_store_recovery_takes_a_release_segment_from_the_tail_window() {
9636 let _serialized = SERIALIZED.lock().await;
9637 let object_store = memory_store();
9638 let records = [
9642 Record {
9643 region_id: region(1),
9644 entry_id: id(0, 1),
9645 payload: Bytes::from(vec![0; RECOVERY_TAIL_WINDOW * 2]),
9646 },
9647 Record {
9648 region_id: RELEASE_REGION_ID,
9649 entry_id: id(0, 1),
9650 payload: encode_releases(&BTreeMap::from([(region(2), 7)])),
9651 },
9652 ];
9653 put_records(&object_store, 0, &records).await;
9654 let (io, reads) = RecordingIo::over(object_store);
9655 let object = io.list().await.unwrap().remove(0);
9656 let tail_start = object.size - RECOVERY_TAIL_WINDOW as u64;
9657 let (_, footer) = footer_of(&io.get(0).await.unwrap());
9658 let segment = footer
9659 .iter()
9660 .find(|entry| entry.region_id == RELEASE_REGION_ID)
9661 .unwrap();
9662 assert!(segment.segment_offset > tail_start);
9663
9664 let store = open_over(io, &eager()).await;
9665 assert_eq!(Some(7), watermark(&store, region(2)));
9666 let mut reads = reads.lock().unwrap().clone();
9667 reads.sort_unstable();
9668 assert_eq!(
9669 vec![
9670 (0, 0, HEADER_LEN as u64),
9671 (0, tail_start, RECOVERY_TAIL_WINDOW as u64)
9672 ],
9673 reads
9674 );
9675 store.stop().await.unwrap();
9676 }
9677
9678 #[tokio::test]
9679 async fn test_store_recovery_reads_a_release_segment_above_the_kept_size_again() {
9680 let _serialized = SERIALIZED.lock().await;
9681 let object_store = memory_store();
9682 let releases = (1..=300)
9685 .map(|number| (region(number), id(0, 1)))
9686 .collect::<BTreeMap<_, _>>();
9687 let record = Record {
9688 region_id: RELEASE_REGION_ID,
9689 entry_id: id(0, 1),
9690 payload: encode_releases(&releases),
9691 };
9692 put_records(&object_store, 0, &[record]).await;
9693 let (io, reads) = RecordingIo::over(object_store);
9694 let object = io.list().await.unwrap().remove(0);
9695 let (_, footer) = footer_of(&io.get(0).await.unwrap());
9696 let segment = &footer[0];
9697 assert!(object.size <= RECOVERY_TAIL_WINDOW as u64);
9698 assert!(segment.segment_len > MAX_KEPT_RELEASE_BYTES);
9699
9700 let store = open_over(io, &eager()).await;
9701 for number in [1, 300] {
9702 assert_eq!(Some(id(0, 1)), watermark(&store, region(number)));
9703 }
9704 assert_eq!(
9705 vec![(0, segment.segment_offset, segment.segment_len)],
9706 *reads.lock().unwrap()
9707 );
9708 store.stop().await.unwrap();
9709 }
9710
9711 #[tokio::test]
9712 async fn test_store_recovery_decodes_release_records_only_on_the_chain() {
9713 let _serialized = SERIALIZED.lock().await;
9714 let object_store = memory_store();
9715 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
9716 let release_record = |object_seq, number| Record {
9717 region_id: RELEASE_REGION_ID,
9718 entry_id: id(object_seq, 1),
9719 payload: encode_releases(&BTreeMap::from([(region(number), 1)])),
9720 };
9721 let damage_release_segment = |bytes: &mut Vec<u8>| {
9722 let (_, footer) = footer_of(bytes);
9723 let segment = footer
9724 .iter()
9725 .find(|entry| entry.region_id == RELEASE_REGION_ID)
9726 .unwrap();
9727 let last = segment.segment_offset + segment.segment_len - 1;
9728 bytes[last as usize] ^= 1;
9729 };
9730 let entry_record = Record {
9734 region_id: region(1),
9735 entry_id: id(0, 1),
9736 payload: Bytes::from_static(b"a1"),
9737 };
9738 put_records(&object_store, 0, &[entry_record, release_record(0, 2)]).await;
9739 let orphan = Header {
9740 object_seq: 1,
9741 epoch: 1,
9742 prev: Some(ChainLink {
9743 object_seq: 0,
9744 epoch: 2,
9745 }),
9746 reclaim_boundary: 0,
9747 };
9748 put_object_with_header(&io, orphan, &[release_record(1, 3)]).await;
9749
9750 corrupt_object(&object_store, &io.object_path(1), damage_release_segment).await;
9752 let store = open(object_store.clone(), &eager()).await;
9753 assert_eq!(Some(1), watermark(&store, region(2)));
9754 assert_eq!(None, watermark(&store, region(3)));
9755 store.stop().await.unwrap();
9756
9757 let path = io.object_path(0);
9759 corrupt_object(&object_store, &path, damage_release_segment).await;
9760 assert_recovery_rejects(&object_store, &path, "checksum mismatch").await;
9761 }
9762
9763 #[tokio::test]
9764 async fn test_store_carries_the_record_of_a_failed_release() {
9765 let _serialized = SERIALIZED.lock().await;
9766 let region_id = region(1);
9767
9768 let (io, _) = RecordingIo::over(memory_store());
9771 let store = open_over(io.clone(), &config(MIN_FLUSH_INTERVAL, u64::MAX)).await;
9772 io.fail_next_put_persistently();
9773 store
9774 .release_region(&provider(region_id), region_id)
9775 .await
9776 .unwrap_err();
9777 wait_until(|| store.catalog.read().unwrap().contains_object(2)).await;
9778 assert_eq!(
9779 vec![release(region_id, 0)],
9780 releases_of(io.as_ref(), 2).await
9781 );
9782 store.stop().await.unwrap();
9783
9784 for last_create in ["succeeds", "fails", "conflicts"] {
9787 let object_store = memory_store();
9788 let store = open(object_store.clone(), &eager()).await;
9789 append(&store, region_id, "a1").await.unwrap();
9790 store.fail_creates();
9791 store
9792 .release_region(&provider(region_id), region_id)
9793 .await
9794 .unwrap_err();
9795 match last_create {
9796 "succeeds" => store.allow_creates(),
9797 "conflicts" => {
9798 store.allow_creates();
9799 put_foreign(&object_store, 3, 2).await;
9800 }
9801 _ => {}
9802 }
9803 let stopped = store.stop().await;
9804 let store = open(object_store, &eager()).await;
9805 if last_create == "succeeds" {
9806 stopped.unwrap();
9807 assert_eq!(Some(id(1, 1)), watermark(&store, region_id));
9808 } else {
9809 let error = stopped.unwrap_err();
9810 let conflict = matches!(unwrap_shared(&error), Error::WalObjectConflict { .. });
9811 assert_eq!(last_create == "conflicts", conflict, "{error:?}");
9812 assert_eq!(None, watermark(&store, region_id));
9813 }
9814 store.stop().await.unwrap();
9815 }
9816 }
9817
9818 #[tokio::test]
9819 async fn test_store_releases_a_region_without_segments() {
9820 let _serialized = SERIALIZED.lock().await;
9821 let object_store = memory_store();
9822 let store = open(object_store.clone(), &eager()).await;
9823 let io = store.io.clone();
9824 let (one, two, three) = (region(1), region(2), region(3));
9825 append(&store, two, "b1").await.unwrap();
9826
9827 store.release_region(&provider(one), one).await.unwrap();
9831 store.obsolete_all(&provider(three), three).await.unwrap();
9832 assert_eq!(vec![release(one, 0)], releases_of(io.as_ref(), 2).await);
9833 assert_eq!(
9834 vec![release(three, EntryId::MAX)],
9835 releases_of(io.as_ref(), 3).await
9836 );
9837 assert_eq!(1, reclaim_boundary_of(io.as_ref(), 3).await);
9838 assert_eq!(vec![1, 2, 3], collected_object_seqs(&store).await);
9839
9840 append(&store, one, "a1").await.unwrap();
9842 store.stop().await.unwrap();
9843 let store = open(object_store, &eager()).await;
9844 assert_eq!(Some(0), watermark(&store, one));
9845 assert_eq!(Some(EntryId::MAX), watermark(&store, three));
9846 assert_eq!(
9847 expected_entries(one, &[(id(4, 1), "a1")]),
9848 read_entries(&store, one, 0).await
9849 );
9850 store.stop().await.unwrap();
9851 }
9852
9853 #[tokio::test]
9854 async fn test_store_release_record_lives_as_long_as_the_segments_it_covers() {
9855 let _serialized = SERIALIZED.lock().await;
9856 let object_store = memory_store();
9857 let (one, two) = (region(1), region(2));
9858 let store = open(object_store.clone(), &eager()).await;
9859 store
9863 .append_batch(vec![entry(&store, one, "a1"), entry(&store, two, "b1")])
9864 .await
9865 .unwrap();
9866 store.release_region(&provider(one), one).await.unwrap();
9867 append(&store, two, "b2").await.unwrap();
9868 assert_eq!(vec![1, 2, 3], collected_object_seqs(&store).await);
9869 store.stop().await.unwrap();
9870
9871 let (io, mut parked) = ParkedIo::parking_creates(object_store.clone());
9873 let store = open_over(io.clone(), &eager()).await;
9874 assert_eq!(Some(id(1, 1)), watermark(&store, one));
9875 assert!(read_entries(&store, one, 0).await.is_empty());
9876
9877 io.deletes_parked.store(true, Ordering::SeqCst);
9881 store.obsolete(&provider(two), two, id(1, 1)).await.unwrap();
9882 append(&store, two, "b3").await.unwrap();
9883 assert_eq!(3, reclaim_boundary_of(io.as_ref(), 5).await);
9884 let mut deletes = parked_operations(&mut parked, 2).await;
9885 deletes.remove(&2).unwrap().send(true).unwrap();
9886 deletes.remove(&1).unwrap().send(false).unwrap();
9887 timeout(WAIT, store.wait_for_garbage_collection())
9888 .await
9889 .unwrap();
9890 assert_eq!(vec![1, 3, 4, 5], object_seqs(io.as_ref()).await);
9891 store.stop().await.unwrap();
9892
9893 let plain = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
9897 let recovered = recover(&plain).await.unwrap();
9898 assert_eq!(3, recovered.reclaim_boundary);
9899 assert_eq!(vec![1], recovered.unindexed);
9900 assert!(recovered.released_entry_ids.is_empty());
9901 let store = open(object_store, &eager()).await;
9902 assert_eq!(3, reclaim_boundary_of(&plain, 6).await);
9903 assert_eq!(None, watermark(&store, one));
9904 store.obsolete(&provider(two), two, id(3, 1)).await.unwrap();
9905 assert_eq!(vec![3, 4, 5, 6], collected_object_seqs(&store).await);
9906 store.stop().await.unwrap();
9907 }
9908
9909 struct LostResponseIo {
9912 inner: ObjectStoreIo,
9913 lose_next: AtomicBool,
9914 }
9915
9916 #[async_trait::async_trait]
9917 impl WalObjectIo for LostResponseIo {
9918 async fn put_if_absent(&self, object_seq: u64, content: Bytes) -> Result<PutResult> {
9919 let result = self.inner.put_if_absent(object_seq, content).await?;
9920 if !self.lose_next.swap(false, Ordering::SeqCst) {
9921 return Ok(result);
9922 }
9923 Err(
9924 object_store::Error::new(object_store::ErrorKind::Unexpected, "lost response")
9925 .set_temporary(),
9926 )
9927 .context(WalObjectStoreSnafu {
9928 operation: "write",
9929 path: self.object_path(object_seq),
9930 })
9931 }
9932
9933 async fn get(&self, object_seq: u64) -> Result<Bytes> {
9934 self.inner.get(object_seq).await
9935 }
9936
9937 async fn get_range(&self, object_seq: u64, offset: u64, len: u64) -> Result<Bytes> {
9938 self.inner.get_range(object_seq, offset, len).await
9939 }
9940
9941 async fn delete(&self, object_seq: u64) -> Result<()> {
9942 self.inner.delete(object_seq).await
9943 }
9944
9945 async fn list(&self) -> Result<Vec<ListedObject>> {
9946 self.inner.list().await
9947 }
9948
9949 fn object_path(&self, object_seq: u64) -> String {
9950 self.inner.object_path(object_seq)
9951 }
9952
9953 fn storage(&self) -> String {
9954 self.inner.storage()
9955 }
9956 }
9957
9958 struct RacingIo {
9961 inner: ObjectStoreIo,
9962 late: Mutex<Option<(u64, Bytes)>>,
9963 }
9964
9965 #[async_trait::async_trait]
9966 impl WalObjectIo for RacingIo {
9967 async fn put_if_absent(&self, object_seq: u64, content: Bytes) -> Result<PutResult> {
9968 let late = self.late.lock().unwrap().take();
9969 if let Some((late_seq, late)) = late {
9970 self.inner.put_if_absent(late_seq, late).await?;
9971 }
9972 self.inner.put_if_absent(object_seq, content).await
9973 }
9974
9975 async fn get(&self, object_seq: u64) -> Result<Bytes> {
9976 self.inner.get(object_seq).await
9977 }
9978
9979 async fn get_range(&self, object_seq: u64, offset: u64, len: u64) -> Result<Bytes> {
9980 self.inner.get_range(object_seq, offset, len).await
9981 }
9982
9983 async fn delete(&self, object_seq: u64) -> Result<()> {
9984 self.inner.delete(object_seq).await
9985 }
9986
9987 async fn list(&self) -> Result<Vec<ListedObject>> {
9988 self.inner.list().await
9989 }
9990
9991 fn object_path(&self, object_seq: u64) -> String {
9992 self.inner.object_path(object_seq)
9993 }
9994
9995 fn storage(&self) -> String {
9996 self.inner.storage()
9997 }
9998 }
9999
10000 struct ParkedIo {
10006 inner: ObjectStoreIo,
10007 parked: mpsc::UnboundedSender<(u64, oneshot::Sender<bool>)>,
10008 park_creates: bool,
10009 creates_refused: AtomicBool,
10010 creates_parked: AtomicBool,
10012 deletes_parked: AtomicBool,
10014 range_reads_parked: AtomicBool,
10015 in_flight: AtomicUsize,
10016 max_in_flight: AtomicUsize,
10017 }
10018
10019 impl ParkedIo {
10020 fn over(
10021 object_store: ObjectStore,
10022 ) -> (
10023 Arc<Self>,
10024 mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
10025 ) {
10026 Self::new(object_store, false)
10027 }
10028
10029 fn parking_creates(
10030 object_store: ObjectStore,
10031 ) -> (
10032 Arc<Self>,
10033 mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
10034 ) {
10035 Self::new(object_store, true)
10036 }
10037
10038 fn new(
10039 object_store: ObjectStore,
10040 park_creates: bool,
10041 ) -> (
10042 Arc<Self>,
10043 mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
10044 ) {
10045 let (parked, parked_rx) = mpsc::unbounded_channel();
10046 (
10047 Arc::new(Self {
10048 inner: ObjectStoreIo::new(object_store, PREFIX).unwrap(),
10049 parked,
10050 park_creates,
10051 creates_refused: AtomicBool::new(false),
10052 creates_parked: AtomicBool::new(false),
10053 deletes_parked: AtomicBool::new(false),
10054 range_reads_parked: AtomicBool::new(false),
10055 in_flight: AtomicUsize::new(0),
10056 max_in_flight: AtomicUsize::new(0),
10057 }),
10058 parked_rx,
10059 )
10060 }
10061
10062 async fn park(&self, object_seq: u64) -> bool {
10065 let in_flight = self.in_flight.fetch_add(1, Ordering::SeqCst) + 1;
10066 self.max_in_flight.fetch_max(in_flight, Ordering::SeqCst);
10067 let (release, released) = oneshot::channel();
10068 self.parked.send((object_seq, release)).unwrap();
10069 let proceed = released.await.unwrap();
10070 self.in_flight.fetch_sub(1, Ordering::SeqCst);
10071 proceed
10072 }
10073 }
10074
10075 #[async_trait::async_trait]
10076 impl WalObjectIo for ParkedIo {
10077 async fn put_if_absent(&self, object_seq: u64, content: Bytes) -> Result<PutResult> {
10078 if self.park_creates
10079 && self.creates_parked.load(Ordering::SeqCst)
10080 && !self.park(object_seq).await
10081 {
10082 if self.creates_refused.load(Ordering::SeqCst) {
10083 return Err(object_store::Error::new(
10084 ErrorKind::PermissionDenied,
10085 "access denied",
10086 )
10087 .set_persistent())
10088 .context(WalObjectStoreSnafu {
10089 operation: "write",
10090 path: self.object_path(object_seq),
10091 });
10092 }
10093 return injected_failure("write", self.object_path(object_seq));
10094 }
10095 self.inner.put_if_absent(object_seq, content).await
10096 }
10097
10098 async fn get(&self, object_seq: u64) -> Result<Bytes> {
10099 if !self.park_creates && !self.park(object_seq).await {
10100 return injected_failure("read", self.object_path(object_seq));
10101 }
10102 self.inner.get(object_seq).await
10103 }
10104
10105 async fn get_range(&self, object_seq: u64, offset: u64, len: u64) -> Result<Bytes> {
10106 if self.range_reads_parked.load(Ordering::SeqCst) && !self.park(object_seq).await {
10107 return injected_failure("read", self.object_path(object_seq));
10108 }
10109 self.inner.get_range(object_seq, offset, len).await
10110 }
10111
10112 async fn delete(&self, object_seq: u64) -> Result<()> {
10113 if self.deletes_parked.load(Ordering::SeqCst) && !self.park(object_seq).await {
10114 return injected_failure("delete", self.object_path(object_seq));
10115 }
10116 self.inner.delete(object_seq).await
10117 }
10118
10119 async fn list(&self) -> Result<Vec<ListedObject>> {
10120 self.inner.list().await
10121 }
10122
10123 fn object_path(&self, object_seq: u64) -> String {
10124 self.inner.object_path(object_seq)
10125 }
10126
10127 fn storage(&self) -> String {
10128 self.inner.storage()
10129 }
10130 }
10131
10132 type RangeReads = Arc<Mutex<Vec<(u64, u64, u64)>>>;
10133
10134 struct RecordingIo {
10143 inner: ObjectStoreIo,
10144 reads: RangeReads,
10145 fail_after_next_put: AtomicBool,
10146 fail_next_put: Mutex<Option<object_store::Error>>,
10147 damage_next_range_read: AtomicBool,
10148 duplicate_next_put: AtomicBool,
10149 fail_next_list: AtomicBool,
10150 fail_range_read_at: Mutex<Option<(u64, u64)>>,
10151 panic_range_read_of: Mutex<Option<u64>>,
10152 block_range_read_of: Mutex<Option<BlockedRangeRead>>,
10153 }
10154
10155 type BlockedRangeRead = (
10158 u64,
10159 std::sync::mpsc::Sender<()>,
10160 std::sync::mpsc::Receiver<()>,
10161 );
10162
10163 impl RecordingIo {
10164 fn over(object_store: ObjectStore) -> (Arc<Self>, RangeReads) {
10165 let reads = RangeReads::default();
10166 let io = Self {
10167 inner: ObjectStoreIo::new(object_store, PREFIX).unwrap(),
10168 reads: reads.clone(),
10169 fail_after_next_put: AtomicBool::new(false),
10170 fail_next_put: Mutex::new(None),
10171 damage_next_range_read: AtomicBool::new(false),
10172 duplicate_next_put: AtomicBool::new(false),
10173 fail_next_list: AtomicBool::new(false),
10174 fail_range_read_at: Mutex::new(None),
10175 panic_range_read_of: Mutex::new(None),
10176 block_range_read_of: Mutex::new(None),
10177 };
10178 (Arc::new(io), reads)
10179 }
10180
10181 fn fail_next_put_permanently(&self) {
10182 let error = object_store::Error::new(
10183 object_store::ErrorKind::PermissionDenied,
10184 "injected failure",
10185 );
10186 *self.fail_next_put.lock().unwrap() = Some(error);
10187 }
10188
10189 fn fail_next_put_persistently(&self) {
10192 let error = object_store::Error::new(object_store::ErrorKind::Unexpected, "injected")
10193 .set_temporary()
10194 .set_persistent();
10195 *self.fail_next_put.lock().unwrap() = Some(error);
10196 }
10197 }
10198
10199 #[async_trait::async_trait]
10200 impl WalObjectIo for RecordingIo {
10201 async fn put_if_absent(&self, object_seq: u64, content: Bytes) -> Result<PutResult> {
10202 if let Some(error) = self.fail_next_put.lock().unwrap().take() {
10203 return Err(error).context(WalObjectStoreSnafu {
10204 operation: "write",
10205 path: self.object_path(object_seq),
10206 });
10207 }
10208 if self.duplicate_next_put.swap(false, Ordering::Relaxed) {
10209 self.inner
10210 .put_if_absent(object_seq, content.clone())
10211 .await?;
10212 }
10213 let result = self.inner.put_if_absent(object_seq, content).await?;
10214 if self.fail_after_next_put.swap(false, Ordering::Relaxed) {
10215 return injected_failure("write", self.object_path(object_seq));
10216 }
10217 Ok(result)
10218 }
10219
10220 async fn get(&self, object_seq: u64) -> Result<Bytes> {
10221 self.inner.get(object_seq).await
10222 }
10223
10224 async fn get_range(&self, object_seq: u64, offset: u64, len: u64) -> Result<Bytes> {
10225 self.reads.lock().unwrap().push((object_seq, offset, len));
10226 if self
10227 .fail_range_read_at
10228 .lock()
10229 .unwrap()
10230 .take_if(|at| *at == (object_seq, offset))
10231 .is_some()
10232 {
10233 return injected_failure("read", self.object_path(object_seq));
10234 }
10235 let panics = self
10236 .panic_range_read_of
10237 .lock()
10238 .unwrap()
10239 .take_if(|of| *of == object_seq)
10240 .is_some();
10241 assert!(!panics, "injected panic");
10242 let blocked = self
10243 .block_range_read_of
10244 .lock()
10245 .unwrap()
10246 .take_if(|(of, ..)| *of == object_seq);
10247 if let Some((_, began, release)) = blocked {
10248 began.send(()).unwrap();
10249 let _ = release.recv();
10251 }
10252 let bytes = self.inner.get_range(object_seq, offset, len).await?;
10253 if self.damage_next_range_read.swap(false, Ordering::Relaxed) {
10254 let mut damaged = bytes.to_vec();
10255 if let Some(last) = damaged.last_mut() {
10256 *last ^= 1;
10257 }
10258 return Ok(Bytes::from(damaged));
10259 }
10260 Ok(bytes)
10261 }
10262
10263 async fn delete(&self, object_seq: u64) -> Result<()> {
10264 self.inner.delete(object_seq).await
10265 }
10266
10267 async fn list(&self) -> Result<Vec<ListedObject>> {
10268 if self.fail_next_list.swap(false, Ordering::Relaxed) {
10269 return injected_failure("list", PREFIX.to_string());
10270 }
10271 self.inner.list().await
10272 }
10273
10274 fn object_path(&self, object_seq: u64) -> String {
10275 self.inner.object_path(object_seq)
10276 }
10277
10278 fn storage(&self) -> String {
10279 self.inner.storage()
10280 }
10281 }
10282
10283 #[allow(clippy::print_stdout)]
10288 mod measurements {
10289 use super::*;
10290
10291 #[tokio::test]
10297 #[ignore]
10298 async fn measure_watermark_updates_over_one_shared_object() {
10299 let _serialized = SERIALIZED.lock().await;
10300 for regions in [1_000, 5_000, 10_000, 20_000] {
10301 let store = open_with_one_shared_object(regions).await;
10302 let mut pauses = Vec::with_capacity(regions as usize);
10303 for number in 1..=regions {
10304 let region_id = region(number);
10305 let started_at = Instant::now();
10306 store
10307 .obsolete(&provider(region_id), region_id, id(1, 1))
10308 .await
10309 .unwrap();
10310 pauses.push(started_at.elapsed());
10311 }
10312 append(&store, region(regions + 1), "b").await.unwrap();
10315 assert_eq!(vec![2], collected_object_seqs(&store).await);
10316 print_pauses("watermark updates", regions, pauses);
10317 store.stop().await.unwrap();
10318 }
10319 }
10320
10321 #[tokio::test]
10326 #[ignore]
10327 async fn measure_actor_watermark_steps_over_one_shared_object() {
10328 let _serialized = SERIALIZED.lock().await;
10329 for regions in [1_000, 5_000, 10_000, 20_000] {
10330 let store = open_with_one_shared_object(regions).await;
10331 store.stop().await.unwrap();
10332 let mut boundary = 0;
10333 let mut pauses = Vec::with_capacity(regions as usize);
10334 for number in 1..=regions {
10335 let started_at = Instant::now();
10336 record_obsolete(
10337 &store.obsolete_entry_ids,
10338 &store.catalog,
10339 region(number),
10340 id(1, 1),
10341 );
10342 boundary = store.catalog.read().unwrap().reclaim_boundary(boundary);
10343 pauses.push(started_at.elapsed());
10344 }
10345 assert_eq!(2, boundary);
10346 print_pauses("actor watermark steps", regions, pauses);
10347 }
10348 }
10349
10350 async fn open_with_one_shared_object(regions: u32) -> Arc<ObjectStoreLogStore> {
10353 let store = open(memory_store(), &eager()).await;
10354 let entries = (1..=regions)
10355 .map(|number| entry(&store, region(number), "a"))
10356 .collect();
10357 store.append_batch(entries).await.unwrap();
10358 store
10359 }
10360
10361 fn print_pauses(label: &str, regions: u32, mut pauses: Vec<Duration>) {
10362 let total = pauses.iter().sum::<Duration>();
10363 pauses.sort_unstable();
10364 let micros = |pause: Duration| pause.as_secs_f64() * 1e6;
10365 println!(
10366 "{label}: regions={regions} total_ms={:.3} mean_us={:.3} p50_us={:.3} p99_us={:.3} max_us={:.3}",
10367 total.as_secs_f64() * 1e3,
10368 micros(total) / pauses.len() as f64,
10369 micros(pauses[pauses.len() / 2]),
10370 micros(pauses[pauses.len() * 99 / 100]),
10371 micros(pauses[pauses.len() - 1]),
10372 );
10373 }
10374
10375 #[tokio::test]
10379 #[ignore]
10380 async fn measure_tail_reads_over_retained_objects() {
10381 let _serialized = SERIALIZED.lock().await;
10382 let region_id = region(1);
10383 for objects in [10_000, 100_000, 1_000_000] {
10384 let object_store = memory_store();
10385 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
10386 futures::stream::iter(0..objects)
10387 .for_each_concurrent(RECOVERY_CONCURRENCY, |object_seq| {
10388 let io = &io;
10389 async move {
10390 let header = Header {
10391 object_seq,
10392 epoch: 1,
10393 prev: object_seq.checked_sub(1).map(|prev| ChainLink {
10394 object_seq: prev,
10395 epoch: 1,
10396 }),
10397 reclaim_boundary: 0,
10398 };
10399 let record = Record {
10400 region_id,
10401 entry_id: id(object_seq, 1),
10402 payload: Bytes::from_static(b"a"),
10403 };
10404 put_object_with_header(io, header, &[record]).await;
10405 }
10406 })
10407 .await;
10408 let store = open(object_store, &eager()).await;
10409 let start = id(objects - 10, 1);
10410
10411 let started_at = Instant::now();
10412 for _ in 0..100 {
10413 assert_eq!(10, read_entries(&store, region_id, start).await.len());
10414 }
10415 let reads = started_at.elapsed();
10416 let started_at = Instant::now();
10417 for _ in 0..100 {
10418 let catalog = store.catalog.read().unwrap();
10419 let found = catalog
10420 .objects_for_entry_range(region_id, start, EntryId::MAX)
10421 .unwrap();
10422 assert_eq!(10, found.len());
10423 }
10424 let lookups = started_at.elapsed();
10425 println!(
10426 "tail reads: objects={objects} read_100_ms={:.3} lookup_100_ms={:.3}",
10427 reads.as_secs_f64() * 1e3,
10428 lookups.as_secs_f64() * 1e3,
10429 );
10430 store.stop().await.unwrap();
10431 }
10432 }
10433
10434 struct CountingIo {
10437 inner: ObjectStoreIo,
10438 gets: AtomicUsize,
10439 range_gets: AtomicUsize,
10440 }
10441
10442 #[async_trait::async_trait]
10443 impl WalObjectIo for CountingIo {
10444 async fn put_if_absent(&self, object_seq: u64, content: Bytes) -> Result<PutResult> {
10445 self.inner.put_if_absent(object_seq, content).await
10446 }
10447
10448 async fn get(&self, object_seq: u64) -> Result<Bytes> {
10449 self.gets.fetch_add(1, Ordering::Relaxed);
10450 self.inner.get(object_seq).await
10451 }
10452
10453 async fn get_range(&self, object_seq: u64, offset: u64, len: u64) -> Result<Bytes> {
10454 self.range_gets.fetch_add(1, Ordering::Relaxed);
10455 self.inner.get_range(object_seq, offset, len).await
10456 }
10457
10458 async fn delete(&self, object_seq: u64) -> Result<()> {
10459 self.inner.delete(object_seq).await
10460 }
10461
10462 async fn list(&self) -> Result<Vec<ListedObject>> {
10463 self.inner.list().await
10464 }
10465
10466 fn list_ahead(&self) -> BoxStream<'_, Result<ListedObject>> {
10467 self.inner.list_ahead()
10468 }
10469
10470 fn object_path(&self, object_seq: u64) -> String {
10471 self.inner.object_path(object_seq)
10472 }
10473
10474 fn storage(&self) -> String {
10475 self.inner.storage()
10476 }
10477 }
10478
10479 fn measured_object_store() -> (ObjectStore, &'static str) {
10483 let Ok(bucket) = std::env::var("GT_S3_BUCKET") else {
10484 return (memory_store(), "memory");
10485 };
10486 let variable = |name: &str| std::env::var(name).unwrap();
10487 let mut builder = object_store::services::S3::default()
10488 .root(&format!("measurements/{}", uuid::Uuid::new_v4()))
10489 .bucket(&bucket)
10490 .region(&variable("GT_S3_REGION"))
10491 .access_key_id(&variable("GT_S3_ACCESS_KEY_ID"))
10492 .secret_access_key(&variable("GT_S3_ACCESS_KEY"));
10493 if let Ok(endpoint) = std::env::var("GT_S3_ENDPOINT_URL") {
10494 builder = builder.endpoint(&endpoint);
10495 }
10496 (ObjectStore::new(builder).unwrap(), "s3")
10497 }
10498
10499 #[tokio::test]
10504 #[ignore]
10505 async fn measure_recovery_of_dropped_regions() {
10506 let _serialized = SERIALIZED.lock().await;
10507 for regions in [1_000, 4_000] {
10508 let (object_store, storage) = measured_object_store();
10509 let store = open(object_store.clone(), &eager()).await;
10510 let entries = (1..=regions + 1)
10511 .map(|number| entry(&store, region(number), "a"))
10512 .collect();
10513 store.append_batch(entries).await.unwrap();
10514 for number in 1..=regions {
10515 let region_id = region(number);
10516 store
10517 .obsolete_all(&provider(region_id), region_id)
10518 .await
10519 .unwrap();
10520 }
10521 store.stop().await.unwrap();
10522
10523 let io = Arc::new(CountingIo {
10524 inner: ObjectStoreIo::new(object_store, PREFIX).unwrap(),
10525 gets: AtomicUsize::new(0),
10526 range_gets: AtomicUsize::new(0),
10527 });
10528 let objects = io.list().await.unwrap().len();
10529 let started_at = Instant::now();
10530 let store = open_over(io.clone(), &eager()).await;
10531 let elapsed = started_at.elapsed();
10532 for number in 1..=regions {
10533 assert_eq!(Some(EntryId::MAX), watermark(&store, region(number)));
10534 }
10535 assert_eq!(None, watermark(&store, region(regions + 1)));
10536 println!(
10537 "recovery of dropped regions: storage={storage} regions={regions} objects={objects} gets={} range_gets={} recovery_ms={:.3}",
10538 io.gets.load(Ordering::Relaxed),
10539 io.range_gets.load(Ordering::Relaxed),
10540 elapsed.as_secs_f64() * 1e3,
10541 );
10542 store.stop().await.unwrap();
10543 }
10544 }
10545 }
10546
10547 mod fault_matrix {
10551 use object_store::layers::mock::MockLayerBuilder;
10552 use prometheus::Histogram;
10553
10554 use super::*;
10555
10556 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
10558 enum Row {
10559 Created,
10561 TransientDurable,
10563 EarlierEpochDurable,
10565 TransientEnqueued,
10567 InterventionEnqueued,
10569 ErrorEnqueuedAfterStop,
10571 Conflict,
10574 CatalogError,
10576 LastSequence,
10578 StartMovesPast,
10580 StartFails,
10583 }
10584
10585 const ROWS: [Row; 11] = [
10587 Row::Created,
10588 Row::TransientDurable,
10589 Row::EarlierEpochDurable,
10590 Row::TransientEnqueued,
10591 Row::InterventionEnqueued,
10592 Row::ErrorEnqueuedAfterStop,
10593 Row::Conflict,
10594 Row::CatalogError,
10595 Row::LastSequence,
10596 Row::StartMovesPast,
10597 Row::StartFails,
10598 ];
10599
10600 struct Case {
10603 fault: &'static str,
10604 row: Option<Row>,
10605 run: fn() -> BoxFuture<'static, ()>,
10606 }
10607
10608 macro_rules! counters {
10610 ($($(#[$doc:meta])* $field:ident: $sample:expr,)*) => {
10611 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
10613 struct Counters {
10614 $($(#[$doc])* $field: u64,)*
10615 }
10616
10617 impl Counters {
10618 fn sample() -> Self {
10619 Self { $($field: $sample,)* }
10620 }
10621
10622 fn since(before: Self) -> Self {
10624 let now = Self::sample();
10625 Self { $($field: now.$field - before.$field,)* }
10626 }
10627 }
10628 };
10629 }
10630
10631 counters! {
10632 created_objects: METRIC_OBJECT_STORE_WAL_CREATED_OBJECTS_TOTAL.get(),
10633 seal_to_durable: METRIC_OBJECT_STORE_WAL_SEAL_TO_DURABLE_SECONDS.get_sample_count(),
10635 create_failures: METRIC_OBJECT_STORE_WAL_CREATE_FAILURES_TOTAL.get(),
10636 create_conflicts: METRIC_OBJECT_STORE_WAL_CREATE_CONFLICTS_TOTAL.get(),
10637 poisoned: METRIC_OBJECT_STORE_WAL_POISONED_TOTAL.get(),
10638 sealed_objects: sealed_objects(),
10640 object_bytes: sum_of(&METRIC_OBJECT_STORE_WAL_OBJECT_BYTES),
10642 object_entries: sum_of(&METRIC_OBJECT_STORE_WAL_OBJECT_ENTRIES),
10643 acknowledged_appends: METRIC_OBJECT_STORE_WAL_APPEND_ACK_SECONDS.get_sample_count(),
10645 recovered_objects: METRIC_OBJECT_STORE_WAL_RECOVERED_OBJECTS_TOTAL.get(),
10647 recoveries: METRIC_OBJECT_STORE_WAL_RECOVERY_SECONDS.get_sample_count(),
10648 reads: METRIC_OBJECT_STORE_WAL_READ_SECONDS.get_sample_count(),
10650 deleted_objects: METRIC_OBJECT_STORE_WAL_DELETED_OBJECTS_TOTAL.get(),
10651 failed_deletes: METRIC_OBJECT_STORE_WAL_FAILED_DELETES_TOTAL.get(),
10652 release_records: METRIC_OBJECT_STORE_WAL_RELEASE_RECORDS_TOTAL.get(),
10653 skipped_segments: METRIC_OBJECT_STORE_WAL_SKIPPED_SEGMENTS_TOTAL.get(),
10655 stalled_appends: METRIC_OBJECT_STORE_WAL_STALLED_APPENDS_TOTAL.get(),
10656 stalled_waits: METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS.get_sample_count(),
10657 rejected_appends: METRIC_OBJECT_STORE_WAL_REJECTED_APPENDS_TOTAL.get(),
10658 }
10659
10660 fn sealed_objects() -> u64 {
10663 let bytes = METRIC_OBJECT_STORE_WAL_OBJECT_BYTES.get_sample_count();
10664 let entries = METRIC_OBJECT_STORE_WAL_OBJECT_ENTRIES.get_sample_count();
10665 assert_eq!(
10666 bytes, entries,
10667 "an object was sized but its entries were not counted"
10668 );
10669 bytes
10670 }
10671
10672 fn sum_of(histogram: &Histogram) -> u64 {
10675 let sum = histogram.get_sample_sum();
10676 assert_eq!(
10677 sum,
10678 sum.round(),
10679 "a histogram of whole numbers recorded {sum}"
10680 );
10681 sum as u64
10682 }
10683
10684 fn object_bytes_of(entries: &[(RegionId, &str)]) -> u64 {
10687 let mut positions = HashMap::new();
10688 let records = entries
10689 .iter()
10690 .map(|(region_id, data)| {
10691 let position = positions.entry(*region_id).or_insert(0);
10692 *position += 1;
10693 Record {
10694 region_id: *region_id,
10695 entry_id: id(1, *position),
10696 payload: Bytes::from(data.as_bytes().to_vec()),
10697 }
10698 })
10699 .collect::<Vec<_>>();
10700 encode_object(chain_header(1, 1, None), &records)
10701 .unwrap()
10702 .bytes
10703 .len() as u64
10704 }
10705
10706 fn one_entry_object() -> u64 {
10708 object_bytes_of(&[(region(1), "a1")])
10709 }
10710
10711 fn release_object() -> u64 {
10713 let releases = BTreeMap::from([(region(1), 0)]);
10714 encode_batch(chain_header(1, 1, None), Vec::new(), &releases)
10715 .unwrap()
10716 .bytes
10717 .len() as u64
10718 }
10719
10720 fn timer_before(histogram: &Histogram) -> (f64, u64) {
10722 (histogram.get_sample_sum(), histogram.get_sample_count())
10723 }
10724
10725 #[derive(Debug, Clone, Copy)]
10728 struct Timed {
10729 recorded: f64,
10730 held: Duration,
10732 elapsed: Duration,
10733 }
10734
10735 impl Timed {
10736 fn since(
10737 histogram: &Histogram,
10738 before: (f64, u64),
10739 held: Duration,
10740 elapsed: Duration,
10741 ) -> Self {
10742 assert_eq!(
10743 before.1 + 1,
10744 histogram.get_sample_count(),
10745 "expected the timer to take exactly one sample"
10746 );
10747 Self {
10748 recorded: histogram.get_sample_sum() - before.0,
10749 held,
10750 elapsed,
10751 }
10752 }
10753
10754 fn assert_pair(name: &str, short: Self, long: Self) {
10759 assert!(
10760 long.held > short.elapsed,
10761 "the {name} timer was observed around a long wait of {:?} that did not outlast the short observation's {:?}",
10762 long.held,
10763 short.elapsed
10764 );
10765 for observed in [short, long] {
10766 assert!(
10767 observed.recorded >= observed.held.as_secs_f64()
10768 && observed.recorded <= observed.elapsed.as_secs_f64(),
10769 "the {name} timer recorded {}s around a wait of {:?}, expected at most {:?}",
10770 observed.recorded,
10771 observed.held,
10772 observed.elapsed
10773 );
10774 }
10775 }
10776 }
10777
10778 fn indexed_gauges() -> (i64, i64) {
10779 (
10780 METRIC_OBJECT_STORE_WAL_INDEXED_OBJECTS.get(),
10781 METRIC_OBJECT_STORE_WAL_INDEXED_BYTES.get(),
10782 )
10783 }
10784
10785 async fn assert_indexed(io: &dyn WalObjectIo, indexed: &[u64]) {
10789 let bytes = io
10790 .list()
10791 .await
10792 .unwrap()
10793 .into_iter()
10794 .filter(|object| indexed.contains(&object.object_seq))
10795 .map(|object| object.size)
10796 .sum::<u64>();
10797 assert_eq!(
10798 (indexed.len() as i64, bytes as i64),
10799 indexed_gauges(),
10800 "expected the gauges to hold the objects {indexed:?}"
10801 );
10802 }
10803
10804 async fn assert_appends_failed(
10806 appends: Vec<tokio::task::JoinHandle<Result<AppendBatchResponse>>>,
10807 recognise: impl Fn(&Error) -> bool,
10808 ) {
10809 for append in appends {
10810 let error = timeout(WAIT, append).await.unwrap().unwrap().unwrap_err();
10811 assert!(
10812 recognise(unwrap_shared(&error)),
10813 "unexpected error: {error:?}"
10814 );
10815 }
10816 }
10817
10818 async fn assert_read_is_terminal(store: &ObjectStoreLogStore, region_id: RegionId) {
10820 let error = store
10821 .read(&provider(region_id), 1)
10822 .await
10823 .err()
10824 .expect("a read of a poisoned store must fail");
10825 let terminal = terminal(&store.terminal_error).expect("the store is not poisoned");
10826 assert!(
10827 matches!(&error, Error::ObjectStoreWal { source, .. } if Arc::ptr_eq(source, &terminal)),
10828 "unexpected error: {error:?}"
10829 );
10830 }
10831
10832 #[tokio::test]
10833 async fn test_store_fault_matrix() {
10834 let _serialized = SERIALIZED.lock().await;
10835 wait_until(|| LIVE_ACTORS.load(Ordering::SeqCst) == 0).await;
10836 let cases = [
10837 Case {
10838 fault: "a create that succeeds",
10839 row: Some(Row::Created),
10840 run: || Box::pin(create_succeeds()),
10841 },
10842 Case {
10843 fault: "a create that finds an object with identical content",
10844 row: Some(Row::Created),
10845 run: || Box::pin(create_finds_an_identical_object()),
10846 },
10847 Case {
10848 fault: "a create whose response is lost",
10849 row: Some(Row::Created),
10850 run: || Box::pin(create_response_is_lost()),
10851 },
10852 Case {
10853 fault: "a create that fails transiently with later objects created and in flight, `durable` mode",
10854 row: Some(Row::TransientDurable),
10855 run: || Box::pin(create_fails_transiently_durable()),
10856 },
10857 Case {
10858 fault: "a create that fails transiently, `enqueued` mode",
10859 row: Some(Row::TransientEnqueued),
10860 run: || Box::pin(create_fails_transiently_enqueued()),
10861 },
10862 Case {
10863 fault: "creates the object store refuses with an error that requires intervention, `enqueued` mode",
10864 row: Some(Row::InterventionEnqueued),
10865 run: || Box::pin(creates_refused_enqueued()),
10866 },
10867 Case {
10868 fault: "a create that fails transiently after `stop` began, `enqueued` mode",
10869 row: Some(Row::ErrorEnqueuedAfterStop),
10870 run: || Box::pin(create_fails_transiently_after_stop_began()),
10871 },
10872 Case {
10873 fault: "a stop while the object store refuses the backlog, `enqueued` mode",
10874 row: Some(Row::ErrorEnqueuedAfterStop),
10875 run: || Box::pin(stop_while_the_backlog_is_refused()),
10876 },
10877 Case {
10878 fault: "a create that finds an object of an earlier epoch, `durable` mode",
10879 row: Some(Row::EarlierEpochDurable),
10880 run: || Box::pin(create_finds_an_earlier_epoch()),
10881 },
10882 Case {
10883 fault: "a create that finds an object of an earlier epoch, `enqueued` mode",
10884 row: Some(Row::Conflict),
10885 run: || Box::pin(create_finds_an_earlier_epoch_enqueued()),
10886 },
10887 Case {
10888 fault: "a create that finds an object of the same or a later epoch",
10889 row: Some(Row::Conflict),
10890 run: || Box::pin(create_finds_another_writer()),
10891 },
10892 Case {
10893 fault: "a create that finds an object whose header cannot be read",
10894 row: Some(Row::Conflict),
10895 run: || Box::pin(create_finds_an_unreadable_object()),
10896 },
10897 Case {
10898 fault: "a create that completes after a conflict poisoned the store",
10899 row: Some(Row::Conflict),
10900 run: || Box::pin(create_completes_after_the_store_was_poisoned()),
10901 },
10902 Case {
10903 fault: "a created object the catalog rejects",
10904 row: Some(Row::CatalogError),
10905 run: || Box::pin(catalog_rejects_the_created_object()),
10906 },
10907 Case {
10908 fault: "a create at the last representable object sequence",
10909 row: Some(Row::LastSequence),
10910 run: || Box::pin(create_takes_the_last_object_sequence()),
10911 },
10912 Case {
10913 fault: "a release whose create succeeds",
10914 row: Some(Row::Created),
10915 run: || Box::pin(release_create_succeeds()),
10916 },
10917 Case {
10918 fault: "a release whose create fails transiently, `durable` mode",
10919 row: Some(Row::TransientDurable),
10920 run: || Box::pin(release_create_fails_transiently_durable()),
10921 },
10922 Case {
10923 fault: "a release whose create fails transiently, `enqueued` mode",
10924 row: Some(Row::TransientEnqueued),
10925 run: || Box::pin(release_create_fails_transiently_enqueued()),
10926 },
10927 Case {
10928 fault: "a release whose create the object store refuses with an error that requires intervention, `enqueued` mode",
10929 row: Some(Row::InterventionEnqueued),
10930 run: || Box::pin(release_create_refused_enqueued()),
10931 },
10932 Case {
10933 fault: "a release while the object store refuses the backlog, `enqueued` mode",
10934 row: None,
10935 run: || Box::pin(release_while_the_backlog_is_refused()),
10936 },
10937 Case {
10938 fault: "a release whose create finds an object of the same or a later epoch",
10939 row: Some(Row::Conflict),
10940 run: || Box::pin(release_create_finds_another_writer()),
10941 },
10942 Case {
10943 fault: "a start object that meets an object of an earlier epoch",
10944 row: Some(Row::StartMovesPast),
10945 run: || Box::pin(start_object_meets_an_earlier_epoch()),
10946 },
10947 Case {
10948 fault: "a start object that meets an object of the same or a later epoch",
10949 row: Some(Row::StartFails),
10950 run: || Box::pin(start_object_meets_another_writer()),
10951 },
10952 Case {
10953 fault: "a start object that meets an object with identical content",
10954 row: Some(Row::StartFails),
10955 run: || Box::pin(start_object_meets_an_identical_object()),
10956 },
10957 Case {
10958 fault: "a start object whose create has an unknown outcome",
10959 row: Some(Row::StartFails),
10960 run: || Box::pin(start_object_create_has_an_unknown_outcome()),
10961 },
10962 Case {
10963 fault: "a listing that fails at recovery",
10964 row: None,
10965 run: || Box::pin(listing_fails_at_recovery()),
10966 },
10967 Case {
10968 fault: "a trailer fetch that fails at recovery",
10969 row: None,
10970 run: || Box::pin(trailer_fetch_fails_at_recovery()),
10971 },
10972 Case {
10973 fault: "a footer fetch that fails at recovery",
10974 row: None,
10975 run: || Box::pin(footer_fetch_fails_at_recovery()),
10976 },
10977 Case {
10978 fault: "a recovery the catalog rejects after every footer was read",
10979 row: None,
10980 run: || Box::pin(catalog_rejects_the_recovery()),
10981 },
10982 Case {
10983 fault: "an object gone by the time recovery fetches it",
10984 row: None,
10985 run: || Box::pin(object_vanishes_before_its_fetch()),
10986 },
10987 Case {
10988 fault: "a segment fetch that fails at a read",
10989 row: None,
10990 run: || Box::pin(segment_fetch_fails_at_a_read()),
10991 },
10992 Case {
10993 fault: "a delete that fails at a collection",
10994 row: None,
10995 run: || Box::pin(delete_fails_at_a_collection()),
10996 },
10997 Case {
10998 fault: "a stop with a delete in flight",
10999 row: None,
11000 run: || Box::pin(stop_waits_for_a_delete_in_flight()),
11001 },
11002 Case {
11003 fault: "an append past the entry id range of its region",
11004 row: None,
11005 run: || Box::pin(region_exhausts_its_entry_ids()),
11006 },
11007 Case {
11008 fault: "creates that succeed after `stop` began, `durable` mode",
11009 row: None,
11010 run: || Box::pin(creates_succeed_after_stop_began()),
11011 },
11012 Case {
11013 fault: "a create that fails transiently after `stop` began, `durable` mode",
11014 row: None,
11015 run: || Box::pin(create_fails_transiently_after_stop_began_durable()),
11016 },
11017 Case {
11018 fault: "a create that conflicts after `stop` began",
11019 row: None,
11020 run: || Box::pin(create_conflicts_after_stop_began()),
11021 },
11022 Case {
11023 fault: "an append held back by full sealed batches, `durable` mode",
11024 row: None,
11025 run: || Box::pin(append_held_back_by_full_sealed_batches()),
11026 },
11027 Case {
11028 fault: "a stalled append released by admission, then one released by a stop",
11029 row: None,
11030 run: || Box::pin(stalled_appends_released_by_admission_and_by_a_stop()),
11031 },
11032 Case {
11033 fault: "a stalled append released by a terminal error",
11034 row: None,
11035 run: || Box::pin(stalled_append_released_by_a_terminal_error()),
11036 },
11037 Case {
11038 fault: "a stalled append released by the teardown of the actor",
11039 row: None,
11040 run: || Box::pin(stalled_append_released_by_the_teardown()),
11041 },
11042 Case {
11043 fault: "an object store that answers slowly",
11044 row: None,
11045 run: || Box::pin(object_store_answers_slowly()),
11046 },
11047 ];
11048
11049 common_telemetry::init_default_ut_logging();
11050 for case in &cases {
11051 common_telemetry::info!("Fault matrix case: {}", case.fault);
11052 (case.run)().await;
11053 }
11054 for row in ROWS {
11055 assert!(
11056 cases.iter().any(|case| case.row == Some(row)),
11057 "no case covers the failure matrix row {row:?}"
11058 );
11059 }
11060 }
11061
11062 async fn create_succeeds() {
11066 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
11067 let before = Counters::sample();
11068 let (region_id, other) = (region(1), region(2));
11069 let mut seals = Vec::new();
11070 let mut acknowledgements = Vec::new();
11071 let mut hold = Duration::from_millis(20);
11072 for batch in [
11075 vec![(region_id, "a1")],
11076 vec![(region_id, "a2"), (region_id, "a3"), (other, "b1")],
11077 ] {
11078 let seal = timer_before(&METRIC_OBJECT_STORE_WAL_SEAL_TO_DURABLE_SECONDS);
11079 let acknowledgement = timer_before(&METRIC_OBJECT_STORE_WAL_APPEND_ACK_SECONDS);
11080 let started_at = Instant::now();
11081 let entries = batch
11082 .iter()
11083 .map(|(region_id, data)| entry(&store, *region_id, data))
11084 .collect();
11085 let appended = spawn_append_batch(&store, entries);
11086 let (_, release) = next_parked_operation(&mut parked).await;
11087 let parked_at = Instant::now();
11088 tokio::time::sleep(hold).await;
11089 let held = parked_at.elapsed();
11090 release.send(true).unwrap();
11091 timeout(WAIT, appended).await.unwrap().unwrap().unwrap();
11092 let elapsed = started_at.elapsed();
11093 let timed = |timer, before| Timed::since(timer, before, held, elapsed);
11094 seals.push(timed(
11095 &METRIC_OBJECT_STORE_WAL_SEAL_TO_DURABLE_SECONDS,
11096 seal,
11097 ));
11098 acknowledgements.push(timed(
11099 &METRIC_OBJECT_STORE_WAL_APPEND_ACK_SECONDS,
11100 acknowledgement,
11101 ));
11102 hold = elapsed + Duration::from_millis(20);
11103 }
11104 Timed::assert_pair("seal to durable", seals[0], seals[1]);
11105 Timed::assert_pair(
11106 "append acknowledgement",
11107 acknowledgements[0],
11108 acknowledgements[1],
11109 );
11110
11111 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
11113 assert_eq!(
11114 expected_entries(
11115 region_id,
11116 &[(id(1, 1), "a1"), (id(2, 1), "a2"), (id(2, 2), "a3")]
11117 ),
11118 read_entries(&store, region_id, 1).await
11119 );
11120 assert_indexed(io.as_ref(), &[1, 2]).await;
11121 assert_eq!(
11122 Counters {
11123 created_objects: 2,
11124 seal_to_durable: 2,
11125 sealed_objects: 2,
11126 object_bytes: one_entry_object()
11127 + object_bytes_of(&[(region_id, "a2"), (region_id, "a3"), (other, "b1")]),
11128 object_entries: 4,
11129 acknowledged_appends: 2,
11130 reads: 1,
11131 deleted_objects: 1,
11132 ..Counters::default()
11133 },
11134 Counters::since(before)
11135 );
11136 store.stop().await.unwrap();
11137 }
11138
11139 async fn release_create_succeeds() {
11142 for config in [eager(), enqueued(eager())] {
11143 let (store, io, mut parked) = open_parking_creates(memory_store(), &config).await;
11144 let before = Counters::sample();
11145 let region_id = region(1);
11146 let released = spawn_release(&store, region_id);
11147 let (object_seq, create) = next_parked_operation(&mut parked).await;
11148 assert_eq!(1, object_seq);
11149 assert!(!released.is_finished());
11150 assert_indexed(io.as_ref(), &[0]).await;
11151 create.send(true).unwrap();
11152 timeout(WAIT, released).await.unwrap().unwrap().unwrap();
11153 assert_eq!(vec![1], collected_object_seqs(&store).await);
11155 assert_eq!(
11156 vec![release(region_id, 0)],
11157 releases_of(io.as_ref(), 1).await
11158 );
11159 assert!(read_entries(&store, region_id, 1).await.is_empty());
11160 assert_indexed(io.as_ref(), &[1]).await;
11161 assert_eq!(
11162 Counters {
11163 created_objects: 1,
11164 seal_to_durable: 1,
11165 sealed_objects: 1,
11166 object_bytes: release_object(),
11167 reads: 1,
11168 deleted_objects: 1,
11169 release_records: 1,
11170 ..Counters::default()
11171 },
11172 Counters::since(before)
11173 );
11174 store.stop().await.unwrap();
11175 }
11176 }
11177
11178 async fn release_create_fails_transiently_durable() {
11181 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
11182 let before = Counters::sample();
11183 let region_id = region(1);
11184 let released = spawn_release(&store, region_id);
11185 let (object_seq, create) = next_parked_operation(&mut parked).await;
11186 assert_eq!(1, object_seq);
11187 create.send(false).unwrap();
11188 let error = timeout(WAIT, released).await.unwrap().unwrap().unwrap_err();
11189 assert!(
11190 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
11191 "unexpected error: {error:?}"
11192 );
11193 assert!(terminal(&store.terminal_error).is_none());
11194 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
11195 assert_indexed(io.as_ref(), &[0]).await;
11196 assert_eq!(
11197 Counters {
11198 create_failures: 1,
11199 sealed_objects: 1,
11200 object_bytes: release_object(),
11201 ..Counters::default()
11202 },
11203 Counters::since(before)
11204 );
11205
11206 let released = spawn_release(&store, region_id);
11207 let (object_seq, create) = next_parked_operation(&mut parked).await;
11208 assert_eq!(2, object_seq);
11209 create.send(true).unwrap();
11210 timeout(WAIT, released).await.unwrap().unwrap().unwrap();
11211 assert_eq!(vec![2], collected_object_seqs(&store).await);
11212 assert_indexed(io.as_ref(), &[2]).await;
11213 assert_eq!(
11214 Counters {
11215 created_objects: 1,
11216 seal_to_durable: 1,
11217 create_failures: 1,
11218 sealed_objects: 2,
11219 object_bytes: 2 * release_object(),
11220 deleted_objects: 1,
11221 release_records: 1,
11222 ..Counters::default()
11223 },
11224 Counters::since(before)
11225 );
11226 store.stop().await.unwrap();
11227 }
11228
11229 async fn release_create_fails_transiently_enqueued() {
11231 let (store, io, mut parked) =
11232 open_parking_creates(memory_store(), &enqueued(eager())).await;
11233 let before = Counters::sample();
11234 let region_id = region(1);
11235 let released = spawn_release(&store, region_id);
11236 let (object_seq, create) = next_parked_operation(&mut parked).await;
11237 assert_eq!(1, object_seq);
11238 create.send(false).unwrap();
11239 let (object_seq, repeat) = next_parked_operation(&mut parked).await;
11240 assert_eq!(1, object_seq);
11241 assert!(!released.is_finished());
11242 assert_eq!(0, failing_stores());
11243 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
11244 assert_indexed(io.as_ref(), &[0]).await;
11245 assert_eq!(
11246 Counters {
11247 create_failures: 1,
11248 sealed_objects: 1,
11249 object_bytes: release_object(),
11250 ..Counters::default()
11251 },
11252 Counters::since(before)
11253 );
11254
11255 repeat.send(true).unwrap();
11256 timeout(WAIT, released).await.unwrap().unwrap().unwrap();
11257 assert_eq!(vec![1], collected_object_seqs(&store).await);
11258 assert_indexed(io.as_ref(), &[1]).await;
11259 assert_eq!(
11260 Counters {
11261 created_objects: 1,
11262 seal_to_durable: 1,
11263 create_failures: 1,
11264 sealed_objects: 1,
11265 object_bytes: release_object(),
11266 deleted_objects: 1,
11267 release_records: 1,
11268 ..Counters::default()
11269 },
11270 Counters::since(before)
11271 );
11272 store.stop().await.unwrap();
11273 }
11274
11275 async fn release_create_refused_enqueued() {
11280 let (store, io, mut parked) =
11281 open_parking_creates(memory_store(), &enqueued(eager())).await;
11282 let before = Counters::sample();
11283 let region_id = region(1);
11284 let released = spawn_release(&store, region_id);
11285 let (object_seq, create) = next_parked_operation(&mut parked).await;
11286 assert_eq!(1, object_seq);
11287 io.creates_refused.store(true, Ordering::SeqCst);
11288 create.send(false).unwrap();
11289 let error = timeout(WAIT, released).await.unwrap().unwrap().unwrap_err();
11290 assert_rejected(&error, ErrorKind::PermissionDenied);
11291 assert_eq!(1, failing_stores());
11292 assert!(terminal(&store.terminal_error).is_none());
11293 assert_indexed(io.as_ref(), &[0]).await;
11294 assert_eq!(
11295 Counters {
11296 create_failures: 1,
11297 sealed_objects: 1,
11298 object_bytes: release_object(),
11299 rejected_appends: 1,
11300 ..Counters::default()
11301 },
11302 Counters::since(before)
11303 );
11304
11305 let (object_seq, create) = next_parked_operation(&mut parked).await;
11306 assert_eq!(1, object_seq);
11307 create.send(true).unwrap();
11308 wait_until(|| failing_stores() == 0).await;
11309 assert_eq!(vec![1], collected_object_seqs(&store).await);
11310 assert_eq!(
11311 vec![release(region_id, 0)],
11312 releases_of(io.as_ref(), 1).await
11313 );
11314 assert_indexed(io.as_ref(), &[1]).await;
11315 assert_eq!(
11316 Counters {
11317 created_objects: 1,
11318 seal_to_durable: 1,
11319 create_failures: 1,
11320 sealed_objects: 1,
11321 object_bytes: release_object(),
11322 deleted_objects: 1,
11323 release_records: 1,
11324 rejected_appends: 1,
11325 ..Counters::default()
11326 },
11327 Counters::since(before)
11328 );
11329 store.stop().await.unwrap();
11330 }
11331
11332 async fn release_while_the_backlog_is_refused() {
11337 let object_store = memory_store();
11338 let (store, io, mut parked) = open_parking_creates(
11339 object_store.clone(),
11340 &enqueued(config(MIN_FLUSH_INTERVAL, 1)),
11341 )
11342 .await;
11343 let before = Counters::sample();
11344 let region_id = region(1);
11345 append(&store, region_id, "a1").await.unwrap();
11346 io.creates_refused.store(true, Ordering::SeqCst);
11347 next_parked_operation(&mut parked)
11348 .await
11349 .1
11350 .send(false)
11351 .unwrap();
11352 wait_until(|| failing_stores() == 1).await;
11353 for _ in 0..3 {
11354 let error = store
11355 .release_region(&provider(region_id), region_id)
11356 .await
11357 .unwrap_err();
11358 assert_rejected(&error, ErrorKind::PermissionDenied);
11359 }
11360 let (object_seq, probe) = next_parked_operation(&mut parked).await;
11362 assert_eq!(1, object_seq);
11363 assert!(parked.try_recv().is_err());
11364 probe.send(false).unwrap();
11365 wait_until(|| Counters::since(before).create_failures == 2).await;
11366 assert_eq!(1, Counters::since(before).sealed_objects);
11367
11368 let stop = {
11369 let store = store.clone();
11370 tokio::spawn(async move { store.stop().await })
11371 };
11372 for create in parked_operations(&mut parked, 2).await.into_values() {
11373 create.send(true).unwrap();
11374 }
11375 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
11376 assert_eq!(0, failing_stores());
11377 assert_eq!(
11378 vec![release(region_id, id(1, 1))],
11379 releases_of(io.as_ref(), 2).await
11380 );
11381 assert_eq!(
11382 Counters {
11383 created_objects: 2,
11384 seal_to_durable: 2,
11385 create_failures: 2,
11386 sealed_objects: 2,
11387 object_bytes: one_entry_object() + release_object(),
11388 object_entries: 1,
11389 release_records: 1,
11390 rejected_appends: 3,
11391 ..Counters::default()
11392 },
11393 Counters::since(before)
11394 );
11395 let store = open(object_store, &eager()).await;
11396 assert_eq!(Some(id(1, 1)), watermark(&store, region_id));
11397 store.stop().await.unwrap();
11398 }
11399
11400 async fn release_create_finds_another_writer() {
11403 for config in [eager(), enqueued(eager())] {
11404 let object_store = memory_store();
11405 let (store, io, mut parked) =
11406 open_parking_creates(object_store.clone(), &config).await;
11407 put_foreign(&object_store, 1, 2).await;
11408 let before = Counters::sample();
11409 let region_id = region(1);
11410 let released = spawn_release(&store, region_id);
11411 let (_, create) = next_parked_operation(&mut parked).await;
11412 create.send(true).unwrap();
11413 let error = timeout(WAIT, released).await.unwrap().unwrap().unwrap_err();
11414 let path = io.object_path(1);
11415 assert!(
11416 matches!(unwrap_shared(&error), Error::WalObjectConflict { path: actual, .. } if *actual == path),
11417 "unexpected error: {error:?}"
11418 );
11419 assert_read_is_terminal(&store, region_id).await;
11420 assert_indexed(io.as_ref(), &[0]).await;
11421 assert_eq!(
11422 Counters {
11423 create_conflicts: 1,
11424 poisoned: 1,
11425 sealed_objects: 1,
11426 object_bytes: release_object(),
11427 ..Counters::default()
11428 },
11429 Counters::since(before)
11430 );
11431 let stopped = store.stop().await;
11432 if config.ack_mode == AckMode::Durable {
11433 stopped.unwrap();
11434 } else {
11435 assert!(matches!(
11436 unwrap_shared(&stopped.unwrap_err()),
11437 Error::WalObjectConflict { .. }
11438 ));
11439 }
11440 }
11441 }
11442
11443 async fn assert_identical_retry(io: Arc<dyn WalObjectIo>, arm: impl FnOnce()) {
11446 let store = open_over(io.clone(), &eager()).await;
11447 let before = Counters::sample();
11448 let region_id = region(1);
11449 arm();
11450 let response = append(&store, region_id, "a1").await.unwrap();
11451 assert_eq!(Some(&id(1, 1)), response.last_entry_ids.get(®ion_id));
11452 let response = append(&store, region_id, "a2").await.unwrap();
11453 assert_eq!(Some(&id(2, 1)), response.last_entry_ids.get(®ion_id));
11454 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
11455 assert_eq!(
11456 expected_entries(region_id, &[(id(1, 1), "a1"), (id(2, 1), "a2")]),
11457 read_entries(&store, region_id, 1).await
11458 );
11459 assert_indexed(io.as_ref(), &[1, 2]).await;
11460 assert_eq!(
11461 Counters {
11462 created_objects: 2,
11463 seal_to_durable: 2,
11464 sealed_objects: 2,
11465 object_bytes: 2 * one_entry_object(),
11466 object_entries: 2,
11467 acknowledged_appends: 2,
11468 reads: 1,
11469 deleted_objects: 1,
11470 ..Counters::default()
11471 },
11472 Counters::since(before)
11473 );
11474 store.stop().await.unwrap();
11475 }
11476
11477 async fn create_finds_an_identical_object() {
11478 let (io, _) = RecordingIo::over(memory_store());
11479 let armed = io.clone();
11480 assert_identical_retry(io.clone(), move || {
11481 armed.duplicate_next_put.store(true, Ordering::Relaxed)
11482 })
11483 .await;
11484 assert!(!io.duplicate_next_put.load(Ordering::Relaxed));
11485 }
11486
11487 async fn create_response_is_lost() {
11490 let lose_next = Arc::new(AtomicBool::new(false));
11491 let losing = lose_next.clone();
11492 let object_store = memory_store().layer(
11493 MockLayerBuilder::default()
11494 .writer_factory(Arc::new(move |_, _, inner| {
11495 if losing.swap(false, Ordering::SeqCst) {
11496 Box::new(LosingWriter(inner))
11497 } else {
11498 inner
11499 }
11500 }))
11501 .build()
11502 .unwrap(),
11503 );
11504 let io = Arc::new(ObjectStoreIo::new(object_store, PREFIX).unwrap());
11505 let armed = lose_next.clone();
11506 assert_identical_retry(io, move || armed.store(true, Ordering::SeqCst)).await;
11507 assert!(!lose_next.load(Ordering::SeqCst));
11508 }
11509
11510 async fn create_fails_transiently_durable() {
11512 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
11513 let before = Counters::sample();
11514 let region_id = region(1);
11515 let appends = spawn_appends(&store, region_id, 3).await;
11516 let mut releases = parked_operations(&mut parked, 3).await;
11517 releases.remove(&2).unwrap().send(true).unwrap();
11518 wait_until(|| Counters::since(before).created_objects == 1).await;
11519 releases.remove(&1).unwrap().send(false).unwrap();
11520 assert_appends_failed(appends, |error| {
11521 matches!(error, Error::WalObjectStore { .. })
11522 })
11523 .await;
11524 assert_indexed(io.as_ref(), &[0]).await;
11525 assert!(read_entries(&store, region_id, 1).await.is_empty());
11526 assert_eq!(
11527 Counters {
11528 created_objects: 1,
11529 seal_to_durable: 1,
11530 create_failures: 1,
11531 sealed_objects: 3,
11532 object_bytes: 3 * one_entry_object(),
11533 object_entries: 3,
11534 reads: 1,
11535 ..Counters::default()
11536 },
11537 Counters::since(before)
11538 );
11539
11540 releases.remove(&3).unwrap().send(true).unwrap();
11541 wait_until(|| Counters::since(before).created_objects == 2).await;
11542 let appended = spawn_append_batch(&store, vec![entry(&store, region_id, "a4")]);
11543 let (object_seq, release) = next_parked_operation(&mut parked).await;
11544 assert_eq!(4, object_seq);
11545 release.send(true).unwrap();
11546 timeout(WAIT, appended).await.unwrap().unwrap().unwrap();
11547 assert_eq!(vec![2, 3, 4], collected_object_seqs(&store).await);
11548 assert_eq!(
11549 expected_entries(region_id, &[(id(4, 1), "a4")]),
11550 read_entries(&store, region_id, 1).await
11551 );
11552 assert_indexed(io.as_ref(), &[4]).await;
11553 assert_eq!(
11554 Counters {
11555 created_objects: 3,
11556 seal_to_durable: 3,
11557 create_failures: 1,
11558 sealed_objects: 4,
11559 object_bytes: 4 * one_entry_object(),
11560 object_entries: 4,
11561 acknowledged_appends: 1,
11562 reads: 2,
11563 deleted_objects: 1,
11564 ..Counters::default()
11565 },
11566 Counters::since(before)
11567 );
11568 store.stop().await.unwrap();
11569 }
11570
11571 async fn create_fails_transiently_enqueued() {
11572 let (store, io, mut parked) =
11573 open_parking_creates(memory_store(), &enqueued(eager())).await;
11574 let before = Counters::sample();
11575 let region_id = region(1);
11576 let response = append(&store, region_id, "a1").await.unwrap();
11577 assert_eq!(Some(&id(1, 1)), response.last_entry_ids.get(®ion_id));
11578 let (object_seq, release) = next_parked_operation(&mut parked).await;
11579 assert_eq!(1, object_seq);
11580 release.send(false).unwrap();
11581
11582 let (object_seq, repeat) = next_parked_operation(&mut parked).await;
11584 assert_eq!(1, object_seq);
11585 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
11586 assert_eq!(0, store.durable_entry_id(&provider(region_id)).unwrap());
11587 assert!(read_entries(&store, region_id, 1).await.is_empty());
11588 assert_indexed(io.as_ref(), &[0]).await;
11589 assert_eq!(
11590 Counters {
11591 create_failures: 1,
11592 sealed_objects: 1,
11593 object_bytes: one_entry_object(),
11594 object_entries: 1,
11595 reads: 1,
11596 ..Counters::default()
11597 },
11598 Counters::since(before)
11599 );
11600
11601 repeat.send(true).unwrap();
11602 timeout(WAIT, store.wait_durable(&provider(region_id), id(1, 1)))
11603 .await
11604 .unwrap()
11605 .unwrap();
11606 assert_eq!(vec![1], collected_object_seqs(&store).await);
11607 assert_eq!(
11608 expected_entries(region_id, &[(id(1, 1), "a1")]),
11609 read_entries(&store, region_id, 1).await
11610 );
11611 assert_indexed(io.as_ref(), &[1]).await;
11612 assert_eq!(
11613 Counters {
11614 created_objects: 1,
11615 seal_to_durable: 1,
11616 create_failures: 1,
11617 sealed_objects: 1,
11618 object_bytes: one_entry_object(),
11619 object_entries: 1,
11620 reads: 2,
11621 deleted_objects: 1,
11622 ..Counters::default()
11623 },
11624 Counters::since(before)
11625 );
11626 store.stop().await.unwrap();
11627 }
11628
11629 async fn create_fails_transiently_after_stop_began() {
11630 let (store, io, mut parked) =
11631 open_parking_creates(memory_store(), &enqueued(manual())).await;
11632 let before = Counters::sample();
11633 let region_id = region(1);
11634 append(&store, region_id, "a1").await.unwrap();
11635 let seal = {
11636 let store = store.clone();
11637 tokio::spawn(async move { store.seal_open_batch().await })
11638 };
11639 let (_, release) = next_parked_operation(&mut parked).await;
11640 store.begin_stop();
11641 release.send(false).unwrap();
11642 timeout(WAIT, seal).await.unwrap().unwrap().unwrap_err();
11643 let error = store.stop().await.unwrap_err();
11644 assert!(
11645 matches!(unwrap_shared(&error), Error::WalObjectStore { .. }),
11646 "unexpected error: {error:?}"
11647 );
11648 assert!(terminal(&store.terminal_error).is_none());
11649 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
11650 assert!(read_entries(&store, region_id, 1).await.is_empty());
11651 assert_indexed(io.as_ref(), &[0]).await;
11652 assert_eq!(
11653 Counters {
11654 create_failures: 1,
11655 sealed_objects: 1,
11656 object_bytes: one_entry_object(),
11657 object_entries: 1,
11658 reads: 1,
11659 ..Counters::default()
11660 },
11661 Counters::since(before)
11662 );
11663 }
11664
11665 async fn creates_refused_enqueued() {
11670 let (store, io, mut parked) =
11671 open_parking_creates(memory_store(), &enqueued(eager())).await;
11672 let before = Counters::sample();
11673 let region_id = region(1);
11674 for append in spawn_appends(&store, region_id, 3).await {
11675 timeout(WAIT, append).await.unwrap().unwrap().unwrap();
11676 }
11677 let mut releases = parked_operations(&mut parked, 3).await;
11678 io.creates_refused.store(true, Ordering::SeqCst);
11679 for object_seq in 1..=3 {
11680 releases.remove(&object_seq).unwrap().send(false).unwrap();
11681 }
11682 wait_until(|| Counters::since(before).create_failures == 3).await;
11683 assert_eq!(1, failing_stores());
11684 let error = append(&store, region_id, "a4").await.unwrap_err();
11685 assert_rejected(&error, ErrorKind::PermissionDenied);
11686 assert_eq!(0, store.durable_entry_id(&provider(region_id)).unwrap());
11687 assert!(read_entries(&store, region_id, 1).await.is_empty());
11688 assert!(terminal(&store.terminal_error).is_none());
11689 assert_indexed(io.as_ref(), &[0]).await;
11690 assert_eq!(
11691 Counters {
11692 create_failures: 3,
11693 sealed_objects: 3,
11694 object_bytes: 3 * one_entry_object(),
11695 object_entries: 3,
11696 reads: 1,
11697 rejected_appends: 1,
11698 ..Counters::default()
11699 },
11700 Counters::since(before)
11701 );
11702
11703 let (object_seq, release) = next_parked_operation(&mut parked).await;
11704 assert_eq!(1, object_seq);
11705 assert!(parked.try_recv().is_err());
11706 release.send(true).unwrap();
11707 for release in parked_operations(&mut parked, 2).await.into_values() {
11708 release.send(true).unwrap();
11709 }
11710 timeout(WAIT, store.wait_durable(&provider(region_id), id(3, 1)))
11711 .await
11712 .unwrap()
11713 .unwrap();
11714 assert_eq!(0, failing_stores());
11715 let response = append(&store, region_id, "a5").await.unwrap();
11716 assert_eq!(Some(&id(4, 1)), response.last_entry_ids.get(®ion_id));
11717 next_parked_operation(&mut parked)
11718 .await
11719 .1
11720 .send(true)
11721 .unwrap();
11722 timeout(WAIT, store.wait_durable(&provider(region_id), id(4, 1)))
11723 .await
11724 .unwrap()
11725 .unwrap();
11726 assert_eq!(vec![1, 2, 3, 4], collected_object_seqs(&store).await);
11727 assert_eq!(
11728 expected_entries(
11729 region_id,
11730 &[
11731 (id(1, 1), "a1"),
11732 (id(2, 1), "a2"),
11733 (id(3, 1), "a3"),
11734 (id(4, 1), "a5")
11735 ]
11736 ),
11737 read_entries(&store, region_id, 1).await
11738 );
11739 assert_indexed(io.as_ref(), &[1, 2, 3, 4]).await;
11740 assert_eq!(
11741 Counters {
11742 created_objects: 4,
11743 seal_to_durable: 4,
11744 create_failures: 3,
11745 sealed_objects: 4,
11746 object_bytes: 4 * one_entry_object(),
11747 object_entries: 4,
11748 reads: 2,
11749 deleted_objects: 1,
11750 rejected_appends: 1,
11751 ..Counters::default()
11752 },
11753 Counters::since(before)
11754 );
11755 store.stop().await.unwrap();
11756 }
11757
11758 async fn stop_while_the_backlog_is_refused() {
11762 let (store, io, mut parked) =
11763 open_parking_creates(memory_store(), &enqueued(eager())).await;
11764 let before = Counters::sample();
11765 let region_id = region(1);
11766 for append in spawn_appends(&store, region_id, 2).await {
11767 timeout(WAIT, append).await.unwrap().unwrap().unwrap();
11768 }
11769 io.creates_refused.store(true, Ordering::SeqCst);
11770 for release in parked_operations(&mut parked, 2).await.into_values() {
11771 release.send(false).unwrap();
11772 }
11773 wait_until(|| Counters::since(before).create_failures == 2).await;
11774 assert_eq!(1, failing_stores());
11775 let wait = {
11776 let store = store.clone();
11777 tokio::spawn(
11778 async move { store.wait_durable(&provider(region_id), id(1, 1)).await },
11779 )
11780 };
11781 timeout(WAIT, store.wait_for_durability_waits(1))
11782 .await
11783 .unwrap()
11784 .unwrap();
11785
11786 let stop = {
11787 let store = store.clone();
11788 tokio::spawn(async move { store.stop().await })
11789 };
11790 for release in parked_operations(&mut parked, 2).await.into_values() {
11791 release.send(false).unwrap();
11792 }
11793 let error = timeout(WAIT, stop).await.unwrap().unwrap().unwrap_err();
11794 assert!(
11795 matches!(unwrap_shared(&error), Error::WalObjectStore { error, .. }
11796 if error.kind() == ErrorKind::PermissionDenied),
11797 "unexpected error: {error:?}"
11798 );
11799 assert_stopped(&timeout(WAIT, wait).await.unwrap().unwrap().unwrap_err());
11800 assert!(terminal(&store.terminal_error).is_none());
11801 assert_eq!(0, failing_stores());
11802 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
11803 assert_indexed(io.as_ref(), &[0]).await;
11804 assert_eq!(
11805 Counters {
11806 create_failures: 4,
11807 sealed_objects: 2,
11808 object_bytes: 2 * one_entry_object(),
11809 object_entries: 2,
11810 ..Counters::default()
11811 },
11812 Counters::since(before)
11813 );
11814 }
11815
11816 async fn create_finds_an_earlier_epoch() {
11817 let object_store = memory_store();
11818 let store = open(object_store.clone(), &eager()).await;
11819 put_foreign(&object_store, 1, 0).await;
11820 let before = Counters::sample();
11821 let region_id = region(1);
11822 let error = append(&store, region_id, "a1").await.unwrap_err();
11823 assert!(
11824 matches!(unwrap_shared(&error), Error::StaleWalObject { .. }),
11825 "unexpected error: {error:?}"
11826 );
11827 assert_eq!(RetryHint::Retryable, error.retry_hint());
11828 assert_indexed(store.io.as_ref(), &[0]).await;
11829 assert!(read_entries(&store, region_id, 1).await.is_empty());
11830 assert_eq!(
11831 Counters {
11832 create_conflicts: 1,
11833 sealed_objects: 1,
11834 object_bytes: one_entry_object(),
11835 object_entries: 1,
11836 reads: 1,
11837 ..Counters::default()
11838 },
11839 Counters::since(before)
11840 );
11841
11842 let response = append(&store, region_id, "a2").await.unwrap();
11843 assert_eq!(Some(&id(2, 1)), response.last_entry_ids.get(®ion_id));
11844 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
11845 assert_indexed(store.io.as_ref(), &[2]).await;
11846 assert_eq!(
11847 Counters {
11848 created_objects: 1,
11849 seal_to_durable: 1,
11850 create_conflicts: 1,
11851 sealed_objects: 2,
11852 object_bytes: 2 * one_entry_object(),
11853 object_entries: 2,
11854 acknowledged_appends: 1,
11855 reads: 1,
11856 deleted_objects: 1,
11857 ..Counters::default()
11858 },
11859 Counters::since(before)
11860 );
11861 store.stop().await.unwrap();
11862 }
11863
11864 async fn create_finds_an_earlier_epoch_enqueued() {
11865 let object_store = memory_store();
11866 let store = open(object_store.clone(), &enqueued(eager())).await;
11867 put_foreign(&object_store, 1, 0).await;
11868 let before = Counters::sample();
11869 let region_id = region(1);
11870 let response = append(&store, region_id, "a1").await.unwrap();
11871 assert_eq!(Some(&id(1, 1)), response.last_entry_ids.get(®ion_id));
11872 let error = timeout(WAIT, store.wait_durable(&provider(region_id), id(1, 1)))
11873 .await
11874 .unwrap()
11875 .unwrap_err();
11876 assert!(
11877 matches!(unwrap_shared(&error), Error::WalObjectConflict { .. }),
11878 "unexpected error: {error:?}"
11879 );
11880 assert!(store.latest_entry_id(&provider(region_id)).is_err());
11881 assert_read_is_terminal(&store, region_id).await;
11882 assert_indexed(store.io.as_ref(), &[0]).await;
11883 let error = store.stop().await.unwrap_err();
11884 assert!(
11885 matches!(unwrap_shared(&error), Error::WalObjectConflict { .. }),
11886 "unexpected error: {error:?}"
11887 );
11888 assert_eq!(
11889 Counters {
11890 create_conflicts: 1,
11891 poisoned: 1,
11892 sealed_objects: 1,
11893 object_bytes: one_entry_object(),
11894 object_entries: 1,
11895 ..Counters::default()
11896 },
11897 Counters::since(before)
11898 );
11899 }
11900
11901 async fn create_finds_another_writer() {
11902 for epoch in [1, 2] {
11903 let object_store = memory_store();
11904 let (store, io, mut parked) =
11905 open_parking_creates(object_store.clone(), &eager()).await;
11906 put_foreign(&object_store, 1, epoch).await;
11907 let before = Counters::sample();
11908 let region_id = region(1);
11909 let appends = spawn_appends(&store, region_id, 2).await;
11910 let mut releases = parked_operations(&mut parked, 2).await;
11911 releases.remove(&2).unwrap().send(true).unwrap();
11912 wait_until(|| Counters::since(before).created_objects == 1).await;
11913 releases.remove(&1).unwrap().send(true).unwrap();
11914 let path = io.object_path(1);
11915 assert_appends_failed(appends, |error| {
11916 matches!(error, Error::WalObjectConflict { path: actual, .. } if *actual == path)
11917 })
11918 .await;
11919 assert_eq!(vec![0, 1, 2], object_seqs(io.as_ref()).await);
11920 assert!(store.latest_entry_id(&provider(region_id)).is_err());
11921 assert_read_is_terminal(&store, region_id).await;
11922 assert_indexed(io.as_ref(), &[0]).await;
11923 assert_eq!(
11924 Counters {
11925 created_objects: 1,
11926 seal_to_durable: 1,
11927 create_conflicts: 1,
11928 poisoned: 1,
11929 sealed_objects: 2,
11930 object_bytes: 2 * one_entry_object(),
11931 object_entries: 2,
11932 ..Counters::default()
11933 },
11934 Counters::since(before)
11935 );
11936 store.stop().await.unwrap();
11937 }
11938 }
11939
11940 async fn create_finds_an_unreadable_object() {
11943 let object_store = memory_store();
11944 let (io, _) = RecordingIo::over(object_store.clone());
11945 let store = open_over(io.clone(), &eager()).await;
11946 put_foreign(&object_store, 1, 1).await;
11947 let before = Counters::sample();
11948 let region_id = region(1);
11949 io.damage_next_range_read.store(true, Ordering::Relaxed);
11950 let error = append(&store, region_id, "a1").await.unwrap_err();
11951 assert!(
11952 matches!(unwrap_shared(&error), Error::InvalidWalObject { .. }),
11953 "unexpected error: {error:?}"
11954 );
11955 assert_read_is_terminal(&store, region_id).await;
11956 assert_indexed(io.as_ref(), &[0]).await;
11957 assert_eq!(
11958 Counters {
11959 create_conflicts: 1,
11960 poisoned: 1,
11961 sealed_objects: 1,
11962 object_bytes: one_entry_object(),
11963 object_entries: 1,
11964 ..Counters::default()
11965 },
11966 Counters::since(before)
11967 );
11968 store.stop().await.unwrap();
11969 }
11970
11971 async fn create_completes_after_the_store_was_poisoned() {
11974 let object_store = memory_store();
11975 let (store, io, mut parked) =
11976 open_parking_creates(object_store.clone(), &eager()).await;
11977 put_foreign(&object_store, 1, 1).await;
11978 let before = Counters::sample();
11979 let region_id = region(1);
11980 let appends = spawn_appends(&store, region_id, 2).await;
11981 let mut releases = parked_operations(&mut parked, 2).await;
11982 releases.remove(&1).unwrap().send(true).unwrap();
11983 assert_appends_failed(appends, |error| {
11984 matches!(error, Error::WalObjectConflict { .. })
11985 })
11986 .await;
11987 assert_indexed(io.as_ref(), &[0]).await;
11988 assert_eq!(
11989 Counters {
11990 create_conflicts: 1,
11991 poisoned: 1,
11992 sealed_objects: 2,
11993 object_bytes: 2 * one_entry_object(),
11994 object_entries: 2,
11995 ..Counters::default()
11996 },
11997 Counters::since(before)
11998 );
11999
12000 releases.remove(&2).unwrap().send(true).unwrap();
12001 wait_until(|| Counters::since(before).created_objects == 1).await;
12002 assert_eq!(vec![0, 1, 2], object_seqs(io.as_ref()).await);
12003 assert_read_is_terminal(&store, region_id).await;
12004 assert_indexed(io.as_ref(), &[0]).await;
12005 assert_eq!(
12006 Counters {
12007 created_objects: 1,
12008 seal_to_durable: 1,
12009 create_conflicts: 1,
12010 poisoned: 1,
12011 sealed_objects: 2,
12012 object_bytes: 2 * one_entry_object(),
12013 object_entries: 2,
12014 ..Counters::default()
12015 },
12016 Counters::since(before)
12017 );
12018 store.stop().await.unwrap();
12019 }
12020
12021 async fn catalog_rejects_the_created_object() {
12022 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
12023 let before = Counters::sample();
12024 let region_id = region(1);
12025 let appends = spawn_appends(&store, region_id, 2).await;
12026 let mut releases = parked_operations(&mut parked, 2).await;
12027 releases.remove(&2).unwrap().send(true).unwrap();
12028 wait_until(|| Counters::since(before).created_objects == 1).await;
12029 let occupying = FooterEntry {
12030 region_id: region(9),
12031 min_entry_id: id(1, 1),
12032 max_entry_id: id(1, 1),
12033 entry_count: 1,
12034 segment_offset: HEADER_LEN as u64,
12035 segment_len: 1,
12036 segment_crc32: 0,
12037 };
12038 store
12039 .catalog
12040 .write()
12041 .unwrap()
12042 .insert_object(1, vec![occupying])
12043 .unwrap();
12044 releases.remove(&1).unwrap().send(true).unwrap();
12045 assert_appends_failed(appends, |error| {
12046 matches!(error, Error::CorruptedWalObject { .. })
12047 })
12048 .await;
12049 assert_eq!(vec![0, 1, 2], object_seqs(io.as_ref()).await);
12050 assert!(store.latest_entry_id(&provider(region_id)).is_err());
12051 assert_read_is_terminal(&store, region_id).await;
12052 assert_indexed(io.as_ref(), &[0]).await;
12055 assert_eq!(
12056 Counters {
12057 created_objects: 2,
12058 seal_to_durable: 2,
12059 poisoned: 1,
12060 sealed_objects: 2,
12061 object_bytes: 2 * one_entry_object(),
12062 object_entries: 2,
12063 ..Counters::default()
12064 },
12065 Counters::since(before)
12066 );
12067 store.stop().await.unwrap();
12068 }
12069
12070 async fn create_takes_the_last_object_sequence() {
12071 let object_store = memory_store();
12072 let last_object_seq = OBJECT_SEQ_LIMIT - 1;
12073 put_records(&object_store, last_object_seq - 2, &[]).await;
12074 let store = open(object_store, &eager()).await;
12075 let region_id = region(1);
12076 let second = entry(&store, region_id, "a2");
12078 let before = Counters::sample();
12079 let response = append(&store, region_id, "a1").await.unwrap();
12080 assert_eq!(
12081 Some(&id(last_object_seq, 1)),
12082 response.last_entry_ids.get(®ion_id)
12083 );
12084 let error = store.append_batch(vec![second]).await.unwrap_err();
12085 assert!(
12086 matches!(unwrap_shared(&error), Error::WalObjectSequenceExhausted { last_object_seq: actual, .. }
12087 if *actual == last_object_seq),
12088 "unexpected error: {error:?}"
12089 );
12090 assert!(store.latest_entry_id(&provider(region_id)).is_err());
12091 assert_read_is_terminal(&store, region_id).await;
12092 assert_indexed(
12093 store.io.as_ref(),
12094 &[last_object_seq - 2, last_object_seq - 1, last_object_seq],
12095 )
12096 .await;
12097 assert_eq!(
12098 Counters {
12099 created_objects: 1,
12100 seal_to_durable: 1,
12101 poisoned: 1,
12102 sealed_objects: 1,
12103 object_bytes: one_entry_object(),
12104 object_entries: 1,
12105 acknowledged_appends: 1,
12106 ..Counters::default()
12107 },
12108 Counters::since(before)
12109 );
12110 store.stop().await.unwrap();
12111 }
12112
12113 async fn put_chain_start(object_store: &ObjectStore) -> ObjectStoreIo {
12115 let fixtures = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
12116 let record = Record {
12117 region_id: region(1),
12118 entry_id: id(0, 1),
12119 payload: Bytes::from_static(b"a0"),
12120 };
12121 put_fixture(&fixtures, 0, &[record]).await;
12122 fixtures
12123 }
12124
12125 async fn open_meeting(
12127 object_store: &ObjectStore,
12128 late: Bytes,
12129 ) -> Result<Arc<ObjectStoreLogStore>> {
12130 let io = Arc::new(RacingIo {
12131 inner: ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap(),
12132 late: Mutex::new(Some((1, late))),
12133 });
12134 ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string()).await
12135 }
12136
12137 async fn start_object_meets_an_earlier_epoch() {
12138 let object_store = memory_store();
12139 let fixtures = put_chain_start(&object_store).await;
12140 let late = encode_object(chain_header(1, 1, None), &[]).unwrap().bytes;
12141 let before = Counters::sample();
12142 let store = open_meeting(&object_store, late).await.unwrap();
12143 assert_eq!(vec![0, 1, 2], object_seqs(&fixtures).await);
12144 assert_eq!(
12145 3,
12146 decode_header(&fixtures.get(2).await.unwrap())
12147 .unwrap()
12148 .epoch
12149 );
12150 assert_indexed(&fixtures, &[0, 2]).await;
12151 assert_eq!(
12152 expected_entries(region(1), &[(id(0, 1), "a0")]),
12153 read_entries(&store, region(1), 0).await
12154 );
12155 assert_eq!(
12156 Counters {
12157 created_objects: 1,
12158 create_conflicts: 1,
12159 recovered_objects: 1,
12160 recoveries: 1,
12161 reads: 1,
12162 ..Counters::default()
12163 },
12164 Counters::since(before)
12165 );
12166 store.stop().await.unwrap();
12167 }
12168
12169 async fn assert_start_fails(
12172 fixtures: &ObjectStoreIo,
12173 before: Counters,
12174 create: Counters,
12175 error: Error,
12176 recognise: impl Fn(&Error) -> bool,
12177 retry_hint: RetryHint,
12178 ) {
12179 assert!(recognise(&error), "unexpected error: {error:?}");
12180 assert_eq!(retry_hint, error.retry_hint());
12181 assert_eq!(vec![0, 1], object_seqs(fixtures).await);
12182 assert_indexed(fixtures, &[0]).await;
12183 assert_eq!(
12184 Counters {
12185 recovered_objects: 1,
12186 recoveries: 1,
12187 ..create
12188 },
12189 Counters::since(before)
12190 );
12191 }
12192
12193 async fn start_object_meets_another_writer() {
12194 for epoch in [2, 3] {
12195 let object_store = memory_store();
12196 let fixtures = put_chain_start(&object_store).await;
12197 let late = encode_object(chain_header(1, epoch, None), &[])
12198 .unwrap()
12199 .bytes;
12200 let before = Counters::sample();
12201 let error = open_meeting(&object_store, late).await.unwrap_err();
12202 let path = fixtures.object_path(1);
12203 assert_start_fails(
12204 &fixtures,
12205 before,
12206 Counters {
12207 create_conflicts: 1,
12208 ..Counters::default()
12209 },
12210 error,
12211 |error| matches!(error, Error::WalObjectConflict { path: actual, .. } if *actual == path),
12212 RetryHint::NonRetryable,
12213 )
12214 .await;
12215 }
12216 }
12217
12218 async fn start_object_meets_an_identical_object() {
12219 let object_store = memory_store();
12220 let fixtures = put_chain_start(&object_store).await;
12221 let same = encode_object(chain_header(1, 2, Some((0, 1))), &[])
12222 .unwrap()
12223 .bytes;
12224 let before = Counters::sample();
12225 let error = open_meeting(&object_store, same).await.unwrap_err();
12226 assert_start_fails(
12227 &fixtures,
12228 before,
12229 Counters {
12230 created_objects: 1,
12231 ..Counters::default()
12232 },
12233 error,
12234 |error| matches!(error, Error::UnconfirmedWalEpochStart { epoch: 2, .. }),
12235 RetryHint::Retryable,
12236 )
12237 .await;
12238 }
12239
12240 async fn start_object_create_has_an_unknown_outcome() {
12241 let object_store = memory_store();
12242 let fixtures = put_chain_start(&object_store).await;
12243 let io = Arc::new(LostResponseIo {
12244 inner: ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap(),
12245 lose_next: AtomicBool::new(true),
12246 });
12247 let before = Counters::sample();
12248 let error = ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string())
12249 .await
12250 .unwrap_err();
12251 assert_start_fails(
12252 &fixtures,
12253 before,
12254 Counters {
12255 create_failures: 1,
12256 ..Counters::default()
12257 },
12258 error,
12259 |error| matches!(error, Error::WalObjectStore { .. }),
12260 RetryHint::Retryable,
12261 )
12262 .await;
12263 }
12264
12265 async fn assert_appends_stopped(
12266 appends: Vec<tokio::task::JoinHandle<Result<AppendBatchResponse>>>,
12267 ) {
12268 for append in appends {
12269 assert_stopped(&timeout(WAIT, append).await.unwrap().unwrap().unwrap_err());
12270 }
12271 }
12272
12273 async fn initialize_indexed_gauges() -> (i64, i64) {
12279 let store = open(memory_store(), &eager()).await;
12280 store.stop().await.unwrap();
12281 let gauges = indexed_gauges();
12282 assert_eq!((1, object_len(&[]) as i64), gauges);
12283 gauges
12284 }
12285
12286 async fn assert_recovery_abandoned(
12291 io: Arc<dyn WalObjectIo>,
12292 present: &[u64],
12293 recognise: impl Fn(&Error) -> bool,
12294 retry_hint: RetryHint,
12295 ) {
12296 let gauges = initialize_indexed_gauges().await;
12297 let before = Counters::sample();
12298 let error = ObjectStoreLogStore::open(io.clone(), &eager(), PREFIX.to_string())
12299 .await
12300 .unwrap_err();
12301 assert!(recognise(&error), "unexpected error: {error:?}");
12302 assert_eq!(retry_hint, error.retry_hint());
12303 assert_eq!(present, object_seqs(io.as_ref()).await.as_slice());
12304 assert_eq!(gauges, indexed_gauges());
12305 assert_eq!(Counters::default(), Counters::since(before));
12306 }
12307
12308 async fn put_entry(object_store: &ObjectStore, object_seq: u64, data: &str) {
12311 let record = Record {
12312 region_id: region(1),
12313 entry_id: id(object_seq, 1),
12314 payload: Bytes::from(data.as_bytes().to_vec()),
12315 };
12316 put_records(object_store, object_seq, &[record]).await;
12317 }
12318
12319 async fn listing_fails_at_recovery() {
12320 let object_store = memory_store();
12321 put_entry(&object_store, 0, "a0").await;
12322 put_entry(&object_store, 1, "a1").await;
12323 let (io, _) = RecordingIo::over(object_store);
12324 io.fail_next_list.store(true, Ordering::Relaxed);
12325 assert_recovery_abandoned(
12326 io.clone(),
12327 &[0, 1],
12328 |error| {
12329 matches!(
12330 error,
12331 Error::WalObjectStore {
12332 operation: "list",
12333 ..
12334 }
12335 )
12336 },
12337 RetryHint::Retryable,
12338 )
12339 .await;
12340 assert!(!io.fail_next_list.load(Ordering::Relaxed));
12341
12342 let before = Counters::sample();
12343 let store = open_over(io.clone(), &eager()).await;
12344 assert_indexed(io.as_ref(), &[0, 1, 2]).await;
12345 assert_eq!(
12346 expected_entries(region(1), &[(id(0, 1), "a0"), (id(1, 1), "a1")]),
12347 read_entries(&store, region(1), 1).await
12348 );
12349 assert_eq!(
12350 Counters {
12351 created_objects: 1,
12352 recovered_objects: 2,
12353 recoveries: 1,
12354 reads: 1,
12355 ..Counters::default()
12356 },
12357 Counters::since(before)
12358 );
12359 store.stop().await.unwrap();
12360 }
12361
12362 async fn trailer_fetch_fails_at_recovery() {
12363 fetch_fails_at_recovery(false).await;
12364 }
12365
12366 async fn footer_fetch_fails_at_recovery() {
12367 fetch_fails_at_recovery(true).await;
12368 }
12369
12370 async fn fetch_fails_at_recovery(footer: bool) {
12373 let object_store = memory_store();
12374 let (tail_start, footer_offset) = put_wide_object(&object_store).await;
12375 let (io, _) = RecordingIo::over(object_store);
12376 let offset = if footer { footer_offset } else { tail_start };
12377 *io.fail_range_read_at.lock().unwrap() = Some((0, offset));
12378 let path = io.object_path(0);
12379 assert_recovery_abandoned(
12380 io.clone(),
12381 &[0],
12382 |error| {
12383 matches!(error, Error::WalObjectStore { operation: "read", path: actual, .. } if *actual == path)
12384 },
12385 RetryHint::Retryable,
12386 )
12387 .await;
12388 assert!(io.fail_range_read_at.lock().unwrap().is_none());
12389
12390 let before = Counters::sample();
12391 let store = open_over(io.clone(), &eager()).await;
12392 assert_indexed(io.as_ref(), &[0, 1]).await;
12393 assert_eq!(
12394 expected_entries(region(1), &[(1, "wide")]),
12395 read_entries(&store, region(1), 1).await
12396 );
12397 assert_eq!(
12398 Counters {
12399 created_objects: 1,
12400 recovered_objects: 1,
12401 recoveries: 1,
12402 reads: 1,
12403 ..Counters::default()
12404 },
12405 Counters::since(before)
12406 );
12407 store.stop().await.unwrap();
12408 }
12409
12410 async fn catalog_rejects_the_recovery() {
12413 let object_store = memory_store();
12414 put_object(&object_store, 0, region(1), &[id(3, 1), id(3, 2)]).await;
12415 put_object(&object_store, 1, region(1), &[id(2, 1)]).await;
12416 let io = Arc::new(ObjectStoreIo::new(object_store, PREFIX).unwrap());
12417 let path = io.object_path(1);
12418 assert_recovery_abandoned(
12419 io.clone(),
12420 &[0, 1],
12421 |error| {
12422 matches!(error, Error::InvalidWalObject { path: actual, source, .. }
12423 if *actual == path && matches!(**source, Error::CorruptedWalObject { .. }))
12424 },
12425 RetryHint::NonRetryable,
12426 )
12427 .await;
12428 }
12429
12430 async fn put_bounded_chain(object_store: &ObjectStore, wide: bool) {
12435 let io = ObjectStoreIo::new(object_store.clone(), PREFIX).unwrap();
12436 for object_seq in 0..=3 {
12437 let header = Header {
12438 reclaim_boundary: if object_seq >= 2 { 2 } else { 0 },
12439 ..chain_header(object_seq, 1, (object_seq > 0).then(|| (object_seq - 1, 1)))
12440 };
12441 let mut records = vec![Record {
12442 region_id: region(1),
12443 entry_id: id(object_seq, 1),
12444 payload: Bytes::from(format!("a{object_seq}")),
12445 }];
12446 if wide {
12447 records.extend((2..=WIDE_REGIONS).map(|number| Record {
12448 region_id: region(number),
12449 entry_id: id(object_seq, 1),
12450 payload: Bytes::from_static(b"wide"),
12451 }));
12452 }
12453 put_object_with_header(&io, header, &records).await;
12454 }
12455 }
12456
12457 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12459 enum RecoveryFetchStage {
12460 WholeObject,
12462 Header,
12464 Footer,
12466 }
12467
12468 async fn parked_releases(
12470 parked: &mut mpsc::UnboundedReceiver<(u64, oneshot::Sender<bool>)>,
12471 count: usize,
12472 ) -> Vec<oneshot::Sender<bool>> {
12473 let mut releases = Vec::with_capacity(count);
12474 for _ in 0..count {
12475 releases.push(next_parked_operation(parked).await.1);
12476 }
12477 releases
12478 }
12479
12480 async fn object_vanishes_before_its_fetch() {
12484 for (vanished, fails) in [(1, false), (2, true)] {
12485 for stage in [
12486 RecoveryFetchStage::WholeObject,
12487 RecoveryFetchStage::Header,
12488 RecoveryFetchStage::Footer,
12489 ] {
12490 object_vanishes_at(vanished, fails, stage).await;
12491 }
12492 }
12493 }
12494
12495 async fn object_vanishes_at(vanished: u64, fails: bool, stage: RecoveryFetchStage) {
12496 let object_store = memory_store();
12497 put_bounded_chain(&object_store, stage != RecoveryFetchStage::WholeObject).await;
12498 let (io, mut parked) = ParkedIo::over(object_store.clone());
12499 io.range_reads_parked.store(true, Ordering::SeqCst);
12500 let gauges = initialize_indexed_gauges().await;
12501 let before = Counters::sample();
12502 let opening = {
12503 let io = io.clone();
12504 tokio::spawn(async move {
12505 ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string()).await
12506 })
12507 };
12508 let path = object_path(&object_store, vanished);
12509 let delete = || object_store.delete(&path);
12510 let release_all = |releases: Vec<oneshot::Sender<bool>>| {
12511 for release in releases {
12512 release.send(true).unwrap();
12513 }
12514 };
12515 let whole = stage == RecoveryFetchStage::WholeObject;
12518 let first = parked_releases(&mut parked, if whole { 4 } else { 8 }).await;
12519 if stage != RecoveryFetchStage::Footer {
12520 delete().await.unwrap();
12521 }
12522 release_all(first);
12523 match stage {
12524 RecoveryFetchStage::WholeObject => {}
12525 RecoveryFetchStage::Header => release_all(parked_releases(&mut parked, 3).await),
12526 RecoveryFetchStage::Footer => {
12527 let footers = parked_releases(&mut parked, 4).await;
12528 delete().await.unwrap();
12529 release_all(footers);
12530 }
12531 }
12532 let opened = timeout(WAIT, opening).await.unwrap().unwrap();
12533 io.range_reads_parked.store(false, Ordering::SeqCst);
12534 if fails {
12535 let error = opened.unwrap_err();
12536 assert!(
12537 is_not_found(&error)
12538 && matches!(&error, Error::WalObjectStore { path, .. } if *path == io.object_path(2)),
12539 "unexpected error: {error:?}"
12540 );
12541 assert_eq!(vec![0, 1, 3], object_seqs(io.as_ref()).await);
12542 assert_eq!(gauges, indexed_gauges());
12543 assert_eq!(Counters::default(), Counters::since(before));
12544 return;
12545 }
12546 let store = opened.unwrap();
12547 assert_eq!(vec![0, 2, 3, 4], object_seqs(io.as_ref()).await);
12548 assert_indexed(io.as_ref(), &[2, 3, 4]).await;
12549 assert_eq!(
12550 expected_entries(region(1), &[(id(2, 1), "a2"), (id(3, 1), "a3")]),
12551 read_entries(&store, region(1), 1).await
12552 );
12553 assert_eq!(
12554 Counters {
12555 created_objects: 1,
12556 recovered_objects: 3,
12557 recoveries: 1,
12558 reads: 1,
12559 ..Counters::default()
12560 },
12561 Counters::since(before)
12562 );
12563 store.stop().await.unwrap();
12564 }
12565
12566 async fn segment_fetch_fails_at_a_read() {
12567 let (io, _) = RecordingIo::over(memory_store());
12568 let store = open_over(io.clone(), &eager()).await;
12569 let before = Counters::sample();
12570 let region_id = region(1);
12571 append(&store, region_id, "a1").await.unwrap();
12572 append(&store, region_id, "a2").await.unwrap();
12573 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
12574 let (path, segment) = store
12575 .segment_location(&provider(region_id), 1)
12576 .unwrap()
12577 .unwrap();
12578 *io.fail_range_read_at.lock().unwrap() = Some((1, segment.start));
12579 let error = store
12580 .read(&provider(region_id), 1)
12581 .await
12582 .unwrap()
12583 .try_collect::<Vec<_>>()
12584 .await
12585 .unwrap_err();
12586 assert!(
12587 matches!(&error, Error::WalObjectStore { operation: "read", path: actual, .. } if *actual == path),
12588 "unexpected error: {error:?}"
12589 );
12590 assert_eq!(RetryHint::Retryable, error.retry_hint());
12591 assert!(io.fail_range_read_at.lock().unwrap().is_none());
12592 assert!(store.wal_holes(&provider(region_id)).unwrap().is_empty());
12593 assert!(terminal(&store.terminal_error).is_none());
12594 assert_indexed(io.as_ref(), &[1, 2]).await;
12595 let written = Counters {
12596 created_objects: 2,
12597 seal_to_durable: 2,
12598 sealed_objects: 2,
12599 object_bytes: 2 * one_entry_object(),
12600 object_entries: 2,
12601 acknowledged_appends: 2,
12602 deleted_objects: 1,
12603 ..Counters::default()
12604 };
12605 assert_eq!(written, Counters::since(before));
12606
12607 assert_eq!(
12608 expected_entries(region_id, &[(id(1, 1), "a1"), (id(2, 1), "a2")]),
12609 read_entries(&store, region_id, 1).await
12610 );
12611 assert_eq!(
12612 Counters {
12613 reads: 1,
12614 ..written
12615 },
12616 Counters::since(before)
12617 );
12618 store.stop().await.unwrap();
12619 }
12620
12621 async fn delete_fails_at_a_collection() {
12625 let (io, mut parked) = ParkedIo::parking_creates(memory_store());
12626 let store = open_over(io.clone(), &eager()).await;
12627 io.deletes_parked.store(true, Ordering::SeqCst);
12628 let before = Counters::sample();
12629 let region_id = region(1);
12630 append(&store, region_id, "a1").await.unwrap();
12631 let (object_seq, release) = next_parked_operation(&mut parked).await;
12632 assert_eq!(0, object_seq);
12633 release.send(false).unwrap();
12634 timeout(WAIT, store.wait_for_garbage_collection())
12635 .await
12636 .unwrap();
12637 assert!(store.catalog.read().unwrap().contains_object(0));
12638 assert!(terminal(&store.terminal_error).is_none());
12639 assert_eq!(vec![0, 1], object_seqs(io.as_ref()).await);
12640 assert_indexed(io.as_ref(), &[0, 1]).await;
12641 assert_eq!(
12642 expected_entries(region_id, &[(id(1, 1), "a1")]),
12643 read_entries(&store, region_id, 1).await
12644 );
12645 let written = Counters {
12646 created_objects: 1,
12647 seal_to_durable: 1,
12648 sealed_objects: 1,
12649 object_bytes: one_entry_object(),
12650 object_entries: 1,
12651 acknowledged_appends: 1,
12652 reads: 1,
12653 failed_deletes: 1,
12654 ..Counters::default()
12655 };
12656 assert_eq!(written, Counters::since(before));
12657
12658 store
12659 .obsolete(&provider(region_id), region_id, 0)
12660 .await
12661 .unwrap();
12662 let (object_seq, release) = next_parked_operation(&mut parked).await;
12663 assert_eq!(0, object_seq);
12664 release.send(true).unwrap();
12665 assert_eq!(vec![1], collected_object_seqs(&store).await);
12666 assert!(!store.catalog.read().unwrap().contains_object(0));
12667 assert_indexed(io.as_ref(), &[1]).await;
12668 assert_eq!(
12669 expected_entries(region_id, &[(id(1, 1), "a1")]),
12670 read_entries(&store, region_id, 1).await
12671 );
12672 assert_eq!(
12673 Counters {
12674 reads: 2,
12675 deleted_objects: 1,
12676 ..written
12677 },
12678 Counters::since(before)
12679 );
12680 store.stop().await.unwrap();
12681 }
12682
12683 async fn stop_waits_for_a_delete_in_flight() {
12687 for deleted in [true, false] {
12688 let (io, mut parked) = ParkedIo::parking_creates(memory_store());
12689 let store = open_over(io.clone(), &eager()).await;
12690 io.deletes_parked.store(true, Ordering::SeqCst);
12691 let before = Counters::sample();
12692 let region_id = region(1);
12693 append(&store, region_id, "a1").await.unwrap();
12694 let (object_seq, release) = next_parked_operation(&mut parked).await;
12695 assert_eq!(0, object_seq);
12696 let mut stop = Box::pin(store.stop());
12700 assert!((&mut stop).now_or_never().is_none());
12701 round_trip_stopping_actor(&store).await;
12702 assert!((&mut stop).now_or_never().is_none());
12703 release.send(deleted).unwrap();
12704 timeout(WAIT, stop).await.unwrap().unwrap();
12705
12706 let present = if deleted { vec![1] } else { vec![0, 1] };
12707 assert_eq!(present, object_seqs(io.as_ref()).await);
12708 let indexed = store
12709 .catalog
12710 .read()
12711 .unwrap()
12712 .objects_in_order()
12713 .map(|(object_seq, _)| object_seq)
12714 .collect::<Vec<_>>();
12715 assert_eq!(present, indexed);
12716 assert_indexed(io.as_ref(), &present).await;
12717 assert!(terminal(&store.terminal_error).is_none());
12718 assert_eq!(
12719 expected_entries(region_id, &[(id(1, 1), "a1")]),
12720 read_entries(&store, region_id, 1).await
12721 );
12722 assert_eq!(
12723 Counters {
12724 created_objects: 1,
12725 seal_to_durable: 1,
12726 sealed_objects: 1,
12727 object_bytes: one_entry_object(),
12728 object_entries: 1,
12729 acknowledged_appends: 1,
12730 reads: 1,
12731 deleted_objects: deleted as u64,
12732 failed_deletes: (!deleted) as u64,
12733 ..Counters::default()
12734 },
12735 Counters::since(before)
12736 );
12737 }
12738 }
12739
12740 async fn region_exhausts_its_entry_ids() {
12744 let store = open(memory_store(), &manual()).await;
12745 let before = Counters::sample();
12746 let (region_id, other) = (region(1), region(2));
12747 let shared = spawn_append_batch(
12748 &store,
12749 vec![entry(&store, other, "b1"), entry(&store, region_id, "a1")],
12750 );
12751 store.wait_for_admitted_appends(1).await.unwrap();
12752 let exhausting = (0..POSITION_LIMIT)
12753 .map(|_| entry(&store, region_id, ""))
12754 .collect::<Vec<_>>();
12755 let error = store.append_batch(exhausting).await.unwrap_err();
12756 assert!(
12757 matches!(error, Error::WalEntryPositionExhausted { region_id: actual, .. } if actual == region_id),
12758 "unexpected error: {error:?}"
12759 );
12760 let response = timeout(WAIT, shared).await.unwrap().unwrap().unwrap();
12761 assert_eq!(
12762 HashMap::from([(other, id(1, 1)), (region_id, id(1, 1))]),
12763 response.last_entry_ids
12764 );
12765 assert!(terminal(&store.terminal_error).is_none());
12766
12767 let next = spawn_append_batch(&store, vec![entry(&store, region_id, "a2")]);
12768 store.wait_for_admitted_appends(2).await.unwrap();
12769 store.seal_open_batch().await.unwrap();
12770 let response = timeout(WAIT, next).await.unwrap().unwrap().unwrap();
12771 assert_eq!(Some(&id(2, 1)), response.last_entry_ids.get(®ion_id));
12772 assert_eq!(vec![1, 2], collected_object_seqs(&store).await);
12773 assert_indexed(store.io.as_ref(), &[1, 2]).await;
12774 assert_eq!(
12775 expected_entries(region_id, &[(id(1, 1), "a1"), (id(2, 1), "a2")]),
12776 read_entries(&store, region_id, 1).await
12777 );
12778 assert_eq!(
12779 expected_entries(other, &[(id(1, 1), "b1")]),
12780 read_entries(&store, other, 1).await
12781 );
12782 assert_eq!(
12783 Counters {
12784 created_objects: 2,
12785 seal_to_durable: 2,
12786 sealed_objects: 2,
12787 object_bytes: object_bytes_of(&[(other, "b1"), (region_id, "a1")])
12788 + one_entry_object(),
12789 object_entries: 3,
12790 acknowledged_appends: 2,
12791 reads: 2,
12792 deleted_objects: 1,
12793 ..Counters::default()
12794 },
12795 Counters::since(before)
12796 );
12797 store.stop().await.unwrap();
12798 }
12799
12800 async fn round_trip_stopping_actor(store: &ObjectStoreLogStore) {
12805 let region_id = region(9);
12806 assert_stopped(
12807 &store
12808 .obsolete_all(&provider(region_id), region_id)
12809 .await
12810 .unwrap_err(),
12811 );
12812 }
12813
12814 async fn creates_succeed_after_stop_began() {
12819 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
12820 let before = Counters::sample();
12821 let region_id = region(1);
12822 let mut appends = spawn_appends(&store, region_id, MAX_IN_FLIGHT_CREATES + 1).await;
12823 let mut releases = parked_operations(&mut parked, MAX_IN_FLIGHT_CREATES).await;
12824 let dropped = appends.pop().unwrap();
12825 let stop = {
12826 let store = store.clone();
12827 tokio::spawn(async move { store.stop().await })
12828 };
12829 assert_stopped(&timeout(WAIT, dropped).await.unwrap().unwrap().unwrap_err());
12830 for object_seq in (2..=MAX_IN_FLIGHT_CREATES as u64).rev() {
12831 releases.remove(&object_seq).unwrap().send(true).unwrap();
12832 }
12833 wait_until(|| {
12834 Counters::since(before).created_objects == MAX_IN_FLIGHT_CREATES as u64 - 1
12835 })
12836 .await;
12837 round_trip_stopping_actor(&store).await;
12838 assert!(appends.iter().all(|append| !append.is_finished()));
12839 assert_eq!(0, latest(&store, region_id));
12840 assert_indexed(io.as_ref(), &[0]).await;
12841
12842 releases.remove(&1).unwrap().send(true).unwrap();
12843 for (index, append) in appends.into_iter().enumerate() {
12844 let response = timeout(WAIT, append).await.unwrap().unwrap().unwrap();
12845 assert_eq!(
12846 Some(&id(index as u64 + 1, 1)),
12847 response.last_entry_ids.get(®ion_id)
12848 );
12849 }
12850 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
12851
12852 assert_eq!(vec![0, 1, 2, 3, 4], object_seqs(io.as_ref()).await);
12854 assert_indexed(io.as_ref(), &[0, 1, 2, 3, 4]).await;
12855 assert!(terminal(&store.terminal_error).is_none());
12856 assert_eq!(
12857 expected_entries(
12858 region_id,
12859 &[
12860 (id(1, 1), "a1"),
12861 (id(2, 1), "a2"),
12862 (id(3, 1), "a3"),
12863 (id(4, 1), "a4")
12864 ]
12865 ),
12866 read_entries(&store, region_id, 1).await
12867 );
12868 assert_eq!(
12869 Counters {
12870 created_objects: 4,
12871 seal_to_durable: 4,
12872 sealed_objects: 5,
12873 object_bytes: 5 * one_entry_object(),
12874 object_entries: 5,
12875 acknowledged_appends: 4,
12876 reads: 1,
12877 ..Counters::default()
12878 },
12879 Counters::since(before)
12880 );
12881 }
12882
12883 async fn create_fails_transiently_after_stop_began_durable() {
12887 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
12888 let before = Counters::sample();
12889 let region_id = region(1);
12890 let appends = spawn_appends(&store, region_id, 3).await;
12891 let mut releases = parked_operations(&mut parked, 3).await;
12892 let stop = {
12893 let store = store.clone();
12894 tokio::spawn(async move { store.stop().await })
12895 };
12896 wait_until(|| store.stopped.load(Ordering::Acquire)).await;
12897 releases.remove(&2).unwrap().send(true).unwrap();
12898 wait_until(|| Counters::since(before).created_objects == 1).await;
12899 releases.remove(&1).unwrap().send(false).unwrap();
12900 assert_appends_stopped(appends).await;
12901 assert!(!stop.is_finished());
12902 assert_indexed(io.as_ref(), &[0]).await;
12903 assert_eq!(
12904 Counters {
12905 created_objects: 1,
12906 seal_to_durable: 1,
12907 create_failures: 1,
12908 sealed_objects: 3,
12909 object_bytes: 3 * one_entry_object(),
12910 object_entries: 3,
12911 ..Counters::default()
12912 },
12913 Counters::since(before)
12914 );
12915
12916 releases.remove(&3).unwrap().send(true).unwrap();
12917 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
12918 assert_eq!(vec![0, 2, 3], object_seqs(io.as_ref()).await);
12919 assert!(terminal(&store.terminal_error).is_none());
12920 assert!(read_entries(&store, region_id, 1).await.is_empty());
12921 assert_indexed(io.as_ref(), &[0]).await;
12922 assert_eq!(
12923 Counters {
12924 created_objects: 2,
12925 seal_to_durable: 2,
12926 create_failures: 1,
12927 sealed_objects: 3,
12928 object_bytes: 3 * one_entry_object(),
12929 object_entries: 3,
12930 reads: 1,
12931 ..Counters::default()
12932 },
12933 Counters::since(before)
12934 );
12935 }
12936
12937 async fn create_conflicts_after_stop_began() {
12941 let object_store = memory_store();
12942 let (store, io, mut parked) =
12943 open_parking_creates(object_store.clone(), &eager()).await;
12944 put_foreign(&object_store, 1, 1).await;
12945 let before = Counters::sample();
12946 let region_id = region(1);
12947 let appends = spawn_appends(&store, region_id, 3).await;
12948 let mut releases = parked_operations(&mut parked, 3).await;
12949 let stop = {
12950 let store = store.clone();
12951 tokio::spawn(async move { store.stop().await })
12952 };
12953 wait_until(|| store.stopped.load(Ordering::Acquire)).await;
12954 releases.remove(&2).unwrap().send(true).unwrap();
12955 wait_until(|| Counters::since(before).created_objects == 1).await;
12956 releases.remove(&1).unwrap().send(true).unwrap();
12957 assert_appends_stopped(appends).await;
12958 assert!(matches!(
12959 terminal(&store.terminal_error).as_deref(),
12960 Some(Error::WalObjectConflict { .. })
12961 ));
12962 assert_read_is_terminal(&store, region_id).await;
12963 assert!(!stop.is_finished());
12964 assert_indexed(io.as_ref(), &[0]).await;
12965
12966 releases.remove(&3).unwrap().send(true).unwrap();
12967 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
12968 assert_eq!(vec![0, 1, 2, 3], object_seqs(io.as_ref()).await);
12969 assert_indexed(io.as_ref(), &[0]).await;
12970 assert_eq!(
12971 Counters {
12972 created_objects: 2,
12973 seal_to_durable: 2,
12974 create_conflicts: 1,
12975 poisoned: 1,
12976 sealed_objects: 3,
12977 object_bytes: 3 * one_entry_object(),
12978 object_entries: 3,
12979 ..Counters::default()
12980 },
12981 Counters::since(before)
12982 );
12983 }
12984
12985 async fn append_held_back_by_full_sealed_batches() {
12989 let (store, io, mut parked) = open_parking_creates(memory_store(), &eager()).await;
12990 let before = Counters::sample();
12991 let region_id = region(1);
12992 let mut appends = spawn_appends(&store, region_id, MAX_SEALED_BATCHES - 1).await;
12993 appends.push(spawn_append_batch(
12994 &store,
12995 vec![entry(&store, region_id, "h1")],
12996 ));
12997 let releases = parked_operations(&mut parked, MAX_IN_FLIGHT_CREATES).await;
12998 wait_until(|| Counters::since(before).stalled_appends == 1).await;
12999 assert_eq!(0, Counters::since(before).stalled_waits);
13000
13001 for release in releases.into_values() {
13002 release.send(true).unwrap();
13003 }
13004 for _ in MAX_IN_FLIGHT_CREATES..MAX_SEALED_BATCHES {
13005 next_parked_operation(&mut parked)
13006 .await
13007 .1
13008 .send(true)
13009 .unwrap();
13010 }
13011 for (index, append) in appends.into_iter().enumerate() {
13012 let response = timeout(WAIT, append).await.unwrap().unwrap().unwrap();
13013 assert_eq!(
13014 Some(&id(index as u64 + 1, 1)),
13015 response.last_entry_ids.get(®ion_id)
13016 );
13017 }
13018 let sealed = MAX_SEALED_BATCHES as u64;
13019 assert_eq!(
13020 (1..=sealed).collect::<Vec<_>>(),
13021 collected_object_seqs(&store).await
13022 );
13023 assert_indexed(io.as_ref(), &(1..=sealed).collect::<Vec<_>>()).await;
13024 assert_eq!(
13025 Counters {
13026 created_objects: sealed,
13027 seal_to_durable: sealed,
13028 sealed_objects: sealed,
13029 object_bytes: sealed * one_entry_object(),
13030 object_entries: sealed,
13031 acknowledged_appends: sealed,
13032 deleted_objects: 1,
13033 stalled_appends: 1,
13034 stalled_waits: 1,
13035 ..Counters::default()
13036 },
13037 Counters::since(before)
13038 );
13039 store.stop().await.unwrap();
13040 }
13041
13042 fn stalling() -> ObjectStoreWalConfig {
13045 ObjectStoreWalConfig {
13046 max_unpersisted_bytes: ReadableSize(1),
13047 ..enqueued(manual())
13048 }
13049 }
13050
13051 fn stalled_once() -> Counters {
13053 Counters {
13054 created_objects: 1,
13055 sealed_objects: 1,
13056 object_bytes: one_entry_object(),
13057 object_entries: 1,
13058 recoveries: 1,
13059 stalled_appends: 1,
13060 ..Counters::default()
13061 }
13062 }
13063
13064 async fn stalled_appends_released_by_admission_and_by_a_stop() {
13069 const SHORT: Duration = Duration::from_millis(20);
13070 let region_id = region(1);
13071 let before = Counters::sample();
13072 let stalled_at = Instant::now();
13073 let (store, io, mut parked, stalled, release) = stall_second_append(stalling()).await;
13074 assert_eq!(stalled_once(), Counters::since(before));
13075
13076 let timer = timer_before(&METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS);
13077 let holding_since = Instant::now();
13078 tokio::time::sleep(SHORT).await;
13079 let held = holding_since.elapsed();
13080 release.send(true).unwrap();
13081 let response = timeout(WAIT, stalled).await.unwrap().unwrap().unwrap();
13082 assert_eq!(Some(&id(2, 1)), response.last_entry_ids.get(®ion_id));
13083 let admitted = Timed::since(
13084 &METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS,
13085 timer,
13086 held,
13087 stalled_at.elapsed(),
13088 );
13089 assert_eq!(vec![1], collected_object_seqs(&store).await);
13090
13091 let stalled_at = Instant::now();
13092 let refused = spawn_append_batch(&store, vec![entry(&store, region_id, "a3")]);
13093 let (object_seq, release) = next_parked_operation(&mut parked).await;
13094 assert_eq!(2, object_seq);
13095 let timer = timer_before(&METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS);
13096 let holding_since = Instant::now();
13097 tokio::time::sleep(admitted.elapsed + SHORT).await;
13098 let held = holding_since.elapsed();
13099 let stop = {
13100 let store = store.clone();
13101 tokio::spawn(async move { store.stop().await })
13102 };
13103 assert_stopped(&timeout(WAIT, refused).await.unwrap().unwrap().unwrap_err());
13104 let refused = Timed::since(
13105 &METRIC_OBJECT_STORE_WAL_STALLED_APPEND_SECONDS,
13106 timer,
13107 held,
13108 stalled_at.elapsed(),
13109 );
13110 Timed::assert_pair("stalled append", admitted, refused);
13111
13112 release.send(true).unwrap();
13113 timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
13114 assert_eq!(vec![1, 2], object_seqs(io.as_ref()).await);
13115 assert_indexed(io.as_ref(), &[1, 2]).await;
13116 assert!(terminal(&store.terminal_error).is_none());
13117 assert_eq!(
13118 expected_entries(region_id, &[(id(1, 1), "a1"), (id(2, 1), "a2")]),
13119 read_entries(&store, region_id, 1).await
13120 );
13121 assert_eq!(
13122 Counters {
13123 created_objects: 3,
13124 seal_to_durable: 2,
13125 sealed_objects: 2,
13126 object_bytes: 2 * one_entry_object(),
13127 object_entries: 2,
13128 recoveries: 1,
13129 reads: 1,
13130 deleted_objects: 1,
13131 stalled_appends: 2,
13132 stalled_waits: 2,
13133 ..Counters::default()
13134 },
13135 Counters::since(before)
13136 );
13137 }
13138
13139 async fn stalled_append_released_by_a_terminal_error() {
13143 let region_id = region(1);
13144 let before = Counters::sample();
13145 let (store, io, _parked, stalled, release) = stall_second_append(stalling()).await;
13146 assert_eq!(stalled_once(), Counters::since(before));
13147 let foreign = Header {
13148 object_seq: 1,
13149 epoch: 1,
13150 prev: None,
13151 reclaim_boundary: 0,
13152 };
13153 put_object_with_header(&io.inner, foreign, &[]).await;
13154 release.send(true).unwrap();
13155 let error = timeout(WAIT, stalled).await.unwrap().unwrap().unwrap_err();
13156 assert!(
13157 matches!(unwrap_shared(&error), Error::WalObjectConflict { .. }),
13158 "unexpected error: {error:?}"
13159 );
13160 assert_read_is_terminal(&store, region_id).await;
13161 let error = store.stop().await.unwrap_err();
13162 assert!(
13163 matches!(unwrap_shared(&error), Error::WalObjectConflict { .. }),
13164 "unexpected error: {error:?}"
13165 );
13166 assert_eq!(vec![0, 1], object_seqs(io.as_ref()).await);
13167 assert_indexed(io.as_ref(), &[0]).await;
13168 assert_eq!(
13169 Counters {
13170 create_conflicts: 1,
13171 poisoned: 1,
13172 stalled_waits: 1,
13173 ..stalled_once()
13174 },
13175 Counters::since(before)
13176 );
13177 }
13178
13179 async fn stalled_append_released_by_the_teardown() {
13183 let before = Counters::sample();
13184 let (store, io, _parked, stalled, _release) = stall_second_append(stalling()).await;
13185 assert_eq!(stalled_once(), Counters::since(before));
13186 assert_indexed(io.as_ref(), &[0]).await;
13187
13188 stalled.abort();
13189 drop(store);
13190 wait_until(|| LIVE_ACTORS.load(Ordering::SeqCst) == 0).await;
13191 assert_eq!(vec![0], object_seqs(io.as_ref()).await);
13192 assert_indexed(io.as_ref(), &[0]).await;
13193 assert_eq!(
13194 Counters {
13195 stalled_waits: 1,
13196 ..stalled_once()
13197 },
13198 Counters::since(before)
13199 );
13200 }
13201
13202 async fn object_store_answers_slowly() {
13207 const SHORT: Duration = Duration::from_millis(20);
13208 let object_store = memory_store();
13209 put_entry(&object_store, 0, "a0").await;
13210 let (io, mut parked) = ParkedIo::over(object_store);
13211 let before = Counters::sample();
13212 let region_id = region(1);
13213 let mut recoveries = Vec::new();
13214 let mut reads = Vec::new();
13215 let mut hold = SHORT;
13216 for objects in 1..=2 {
13217 let timer = timer_before(&METRIC_OBJECT_STORE_WAL_RECOVERY_SECONDS);
13218 let started_at = Instant::now();
13219 let opening = {
13220 let io = io.clone();
13221 tokio::spawn(async move {
13222 ObjectStoreLogStore::open(io, &eager(), PREFIX.to_string()).await
13223 })
13224 };
13225 let releases = parked_operations(&mut parked, objects).await;
13228 let parked_at = Instant::now();
13229 tokio::time::sleep(hold).await;
13230 let held = parked_at.elapsed();
13231 for release in releases.into_values() {
13232 release.send(true).unwrap();
13233 }
13234 let store = timeout(WAIT, opening).await.unwrap().unwrap().unwrap();
13235 recoveries.push(Timed::since(
13236 &METRIC_OBJECT_STORE_WAL_RECOVERY_SECONDS,
13237 timer,
13238 held,
13239 started_at.elapsed(),
13240 ));
13241
13242 io.range_reads_parked.store(true, Ordering::SeqCst);
13243 let timer = timer_before(&METRIC_OBJECT_STORE_WAL_READ_SECONDS);
13244 let started_at = Instant::now();
13245 let reading = {
13246 let store = store.clone();
13247 tokio::spawn(async move { read_entries(&store, region_id, 1).await })
13248 };
13249 let (object_seq, release) = next_parked_operation(&mut parked).await;
13250 assert_eq!(0, object_seq);
13251 let parked_at = Instant::now();
13252 tokio::time::sleep(hold).await;
13253 let held = parked_at.elapsed();
13254 release.send(true).unwrap();
13255 let entries = timeout(WAIT, reading).await.unwrap().unwrap();
13256 reads.push(Timed::since(
13257 &METRIC_OBJECT_STORE_WAL_READ_SECONDS,
13258 timer,
13259 held,
13260 started_at.elapsed(),
13261 ));
13262 io.range_reads_parked.store(false, Ordering::SeqCst);
13263 assert_eq!(expected_entries(region_id, &[(id(0, 1), "a0")]), entries);
13264 store.stop().await.unwrap();
13265 hold = recoveries[0].elapsed.max(reads[0].elapsed) + SHORT;
13266 }
13267 Timed::assert_pair("recovery", recoveries[0], recoveries[1]);
13268 Timed::assert_pair("read", reads[0], reads[1]);
13269 assert_indexed(io.as_ref(), &[0, 1, 2]).await;
13270 assert_eq!(
13271 Counters {
13272 created_objects: 2,
13273 recovered_objects: 3,
13274 recoveries: 2,
13275 reads: 2,
13276 ..Counters::default()
13277 },
13278 Counters::since(before)
13279 );
13280 }
13281 }
13282}