Skip to main content

log_store/object_store_wal/
store.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Object store WAL construction, recovery, writes and region reads.
16
17use 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;
75/// Appends queued for the actor; a caller beyond them waits to send.
76const 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;
80/// Delay before a create that failed transiently is attempted again in the
81/// `enqueued` acknowledgement mode, where no caller is left to retry it.
82const CREATE_RETRY_DELAY: Duration = Duration::from_millis(100);
83/// Longest delay from a failed attempt of a backlog that the object store
84/// refuses with an error that requires intervention, see [`Failing`], to the
85/// next attempt.
86/// The delay doubles from [`CREATE_RETRY_DELAY`] after every attempt. At the
87/// cap a dead bucket costs four requests a minute, a conditional create and
88/// the read that reconciles it every 30 seconds; a longer cap would keep
89/// appends rejected longer after the cause is fixed.
90const MAX_FAILING_RETRY_DELAY: Duration = Duration::from_secs(30);
91/// How often a store logs that the object store still refuses its backlog.
92const FAILING_LOG_INTERVAL: Duration = Duration::from_secs(60);
93/// Number of sealed batches that may wait to be created or indexed. One
94/// admission can seal two batches, the open batch before an append that would
95/// exhaust its positions and the append's own batch, so the actor admits an
96/// append only while two more fit and holds it back otherwise: a stalled
97/// object store holds callers back instead of growing the backlog.
98const MAX_SEALED_BATCHES: usize = 2 * MAX_IN_FLIGHT_CREATES;
99/// Number of objects whose footers recovery fetches at a time. On S3 an
100/// object costs two reads, its header and its tail, tens of milliseconds per
101/// lane however many vCPUs the node has, and recovery time scales with objects
102/// divided by concurrency up to 128 lanes without throttling: at 64 about
103/// 10,000 objects recover in about 10 s instead of about 100 s at 8, while the
104/// reads use about a third of S3's request rate per prefix, leaving room for
105/// the data file reads of start-up. Each lane holds one
106/// [`RECOVERY_TAIL_WINDOW`], 4 MiB in all.
107const RECOVERY_CONCURRENCY: usize = 64;
108/// Number of segments a read fetches ahead of the one whose entries it
109/// yields. On S3 a replay that fetched each segment after the previous one
110/// spent about 30 ms per object; with eight fetches in flight a segment
111/// arrives about every 4 ms. The segments fetched ahead also take their bytes
112/// from [`READ_PREFETCH_BYTES`].
113const READ_PREFETCH_DEPTH: usize = 8;
114/// Bytes of decoded segments that all reads of a store together fetch ahead; a
115/// segment counts as its length, which bounds its payloads, plus a [`Record`]
116/// per entry. A read always fetches the segment it yields next, as a read that
117/// fetched one segment at a time did, and a later segment only while it fits
118/// in what is left, so regions replayed in parallel hold one segment each, up
119/// to about `max_batch_bytes` or more for one oversized append, plus at most
120/// these bytes, however many regions open at once. While a fetch copies the
121/// payloads out of the downloaded segment it briefly holds the segment's
122/// length once more.
123const READ_PREFETCH_BYTES: usize = 64 * 1024 * 1024;
124/// Bytes recovery reads from the end of an object in one request. The window
125/// holds the trailer and the footer of an object with up to 1364 regions of
126/// 48 bytes each, so a second request for the footer is rare, and usually the
127/// segment of release records, which is the last segment.
128const RECOVERY_TAIL_WINDOW: usize = 64 * 1024;
129/// Largest segment of release records that recovery keeps from the bytes it
130/// read for a footer. It keeps them before it knows which objects are on the
131/// chain, so the bound caps what objects off the chain hold until recovery
132/// ends; a larger segment is read again if its object is on the chain. A
133/// release seals its record at once, so a segment usually holds the records of
134/// one or a few regions, 16 bytes each.
135const MAX_KEPT_RELEASE_BYTES: u64 = 4 * 1024;
136
137/// A log store over the immutable WAL objects under one prefix.
138///
139/// Appends are admitted into an open batch. A background actor seals the batch
140/// when it reaches the size limit or the flush interval elapses and creates
141/// the object under the next sequence while it keeps admitting entries into
142/// the next batch; up to [`MAX_IN_FLIGHT_CREATES`] creates run at a time.
143/// Objects are indexed in the catalog in sequence order. In the `durable`
144/// acknowledgement mode an append returns once its object is durable and
145/// indexed, so an acknowledged entry never has a missing predecessor; in the
146/// `enqueued` mode it returns on admission and the object is created in the
147/// background. While [`MAX_SEALED_BATCHES`] batches
148/// wait to become durable no append is admitted, and in the `enqueued` mode
149/// appends are rejected while the object store refuses the backlog with an
150/// error that requires intervention. A segment that a read cannot
151/// decode is handled as `on_corrupted_segment` says. Objects below the reclaim
152/// boundary of the last indexed object are deleted in the background. A
153/// release of a region seals an object with a release record of the region at
154/// once and returns, in both modes, once that object is durable and indexed.
155pub struct ObjectStoreLogStore {
156    prefix: String,
157    /// The storage the prefix is read from, as configured, for messages.
158    storage: String,
159    ack_mode: AckMode,
160    on_corrupted_segment: CorruptedSegmentAction,
161    accept_missing_history: bool,
162    /// Whether the prefix held no object when the store opened.
163    found_empty: bool,
164    /// The sequence above the newest object holding entries that recovery
165    /// indexed, zero when it indexed none.
166    history_end: u64,
167    started_regions: StartedRegions,
168    io: Arc<dyn WalObjectIo>,
169    catalog: Arc<RwLock<ObjectCatalog>>,
170    obsolete_entry_ids: ObsoleteEntryIds,
171    /// Segments that reads skipped, per region and by object sequence.
172    wal_holes: WalHoles,
173    /// Objects with a delete in flight, which a read consults.
174    deleting: Arc<DeletingObjects>,
175    /// Bytes the reads may fetch ahead, see [`READ_PREFETCH_BYTES`].
176    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
197/// Regions whose history starts in the store: those created on it and those
198/// it opened by accepting a missing history. The store registers a region and
199/// the actor decides a withdrawal under this lock, so neither misses the
200/// other.
201type StartedRegions = Arc<Mutex<HashSet<RegionId>>>;
202
203type WalHoles = Arc<Mutex<HashMap<RegionId, BTreeMap<u64, WalHole>>>>;
204
205/// A segment a read skipped because it did not decode: the entries of one
206/// region in one object are missing from every read of that region.
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct WalHole {
209    /// Key of the object that holds the segment.
210    pub path: String,
211    pub object_seq: u64,
212    /// Entry id range the footer records for the segment.
213    pub min_entry_id: EntryId,
214    pub max_entry_id: EntryId,
215}
216
217type TerminalError = Arc<Mutex<Option<Arc<Error>>>>;
218
219/// The delete attempts in flight, one per object.
220///
221/// The actor registers an attempt before its delete starts, unindexes the
222/// object if the delete succeeded, and only then settles the attempt and takes
223/// it out, so an object without an attempt was either never collected or is
224/// already unindexed. [`is_collected`] depends on that order.
225#[derive(Debug, Default)]
226struct DeletingObjects {
227    /// The settlement flag of the attempt in flight per object sequence.
228    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    /// Settles the attempt of `object_seq` and takes it out, releasing the
245    /// reads that wait for it.
246    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    /// Settles every attempt the actor will never complete because it exited:
253    /// their objects stay indexed, so waiting reads report their error.
254    fn abandon_all(&self) {
255        for (_, settled) in std::mem::take(&mut *self.lock()) {
256            settled.send_replace(true);
257        }
258    }
259
260    /// Returns the settlement of the attempt in flight for `object_seq`, or
261    /// `None` when no delete of it is in flight.
262    fn attempt(&self, object_seq: u64) -> Option<watch::Receiver<bool>> {
263        self.lock().get(&object_seq).map(watch::Sender::subscribe)
264    }
265
266    /// Waits until no delete is in flight.
267    #[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    /// Builds the store under the node and generation prefix derived from `config`,
297    /// recovering the catalog from the objects that already exist and writing
298    /// the object that starts the epoch of this instance. Before it recovers,
299    /// it fails unless the object store refuses to overwrite an object on a
300    /// conditional create. Recovery fails on the first corrupted or
301    /// conflicting object.
302    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        // Every epoch is one above the sequence of the start object its
353        // instance created, and every object is at or above its start object,
354        // so an epoch above the next sequence names no instance that ran.
355        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        // Only a recovery that succeeded is reported, so that the objects
370        // counted and the time measured are those of the same recoveries.
371        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        // A chain without any segment holds no history, so the start object
376        // reclaims every object below it. A start object that another
377        // instance withdraws after this recovery listed it is then below the
378        // boundary, and its delete leaves this chain complete.
379        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        // The epoch identifies this instance in every object it writes.
388        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        // The release records are the only watermarks recorded yet.
400        for (&region_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    /// Returns the largest entry id of the provider's region whose object is
520    /// durable and indexed, or zero for a region without such entries. In the
521    /// `enqueued` acknowledgement mode an entry id that an append returned
522    /// stays above this value until the object holding it is created.
523    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    /// Returns the segments of the provider's region that reads of this store
535    /// skipped because they did not decode, ordered by object sequence. Holes
536    /// are kept in memory only: a store opened later on the same prefix
537    /// records them again when its reads meet the segments.
538    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(&region_id)
545            .map(|holes| holes.values().cloned().collect())
546            .unwrap_or_default())
547    }
548
549    /// Returns the region of `provider`, which must select this store's prefix.
550    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    /// Releases the region: its watermark moves up to the release in memory
589    /// and a release record of it goes into an object sealed at once, and the
590    /// call returns once that object is durable and indexed, in both
591    /// acknowledgement modes, or with the error that failed it. A `full`
592    /// release reaches [`EntryId::MAX`]; any other the largest id the store
593    /// handed out for the region, which every entry of the region it holds is
594    /// at or below. When the object fails, the next sealed batch carries the
595    /// record again. Recovery seeds the watermark from the record, so a store
596    /// opened later collects the objects of a region that is never opened
597    /// again.
598    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    /// Returns why the prefix cannot hold the WAL history of `region_id`
613    /// through the watermark `entry_id`, or `None` when it can.
614    ///
615    /// Two invariants of a prefix decide it. First, a prefix that any store
616    /// opened is never empty afterwards, unless that store withdrew its start
617    /// object, see [`withdraw_start`](Self::withdraw_start): every open writes
618    /// its start object before it admits an append, and collection deletes
619    /// only objects below the reclaim boundary of the last indexed object,
620    /// which carries a boundary at or below its own sequence (a larger one
621    /// fails its header check), so the newest indexed object always stays.
622    /// Second, a watermark a region persisted names an object at or below
623    /// the newest object holding entries that any later recovery of the
624    /// prefix indexes. The entry it names was durable and indexed when the
625    /// watermark was persisted, since an append of the `durable` mode returns
626    /// only then and Mito waits for durability before it publishes a
627    /// watermark in the `enqueued` mode. No durable boundary passes the
628    /// newest object holding entries: a batch carries at most the sequence
629    /// after the last indexed object, which is below the batch, and a start
630    /// object carries the boundary recovery found, or its own sequence on a
631    /// chain without segments. So that object stays, and
632    /// recovery indexes it on the chain of the tip. The segment of release
633    /// records counts as entries: it never holds the reclaim boundary, so an
634    /// object holding only releases can be the one that collection keeps
635    /// while it deletes every older object. Objects without segments, such as
636    /// the start objects of opens this check refused, do not move it.
637    ///
638    /// So the history is missing when this store found the prefix empty, or
639    /// when the watermark names an object above the newest one holding
640    /// entries that it found, unless the region's history starts in this
641    /// store or the catalog holds the region's entries through the
642    /// watermark: those of a region written since the store opened.
643    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(&region_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    /// Deletes the start object of a store that found its prefix empty and
678    /// whose open of an existing region is refused, so that the next open
679    /// finds the prefix empty again and refuses the region as well, instead
680    /// of passing a region that never flushed. The actor gives the object up
681    /// only while the store has admitted nothing, has no batch, holds no
682    /// other object and no region's history started in it, and from then on
683    /// the store accepts nothing, since a later object would link to the
684    /// deleted one. The actor owns the delete like any collection, so `stop`
685    /// waits for it and a cancelled caller does not abandon it, and every
686    /// refusal returns only once it settled. A failed delete, or a crash
687    /// before the delete completes, leaves the start object behind, and the
688    /// next open passes a region that never flushed.
689    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/// The transient object store error of a create that a testing hook fails.
705#[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
718/// Records the obsolete watermark of `region_id`, which never moves down, for
719/// reads and in the catalog for the reclaim boundary.
720fn 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    /// Waits until the actor has admitted at least `expected` append calls
750    /// since the store was built.
751    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    /// Seals the open batch regardless of its size and age and returns once
762    /// its object is durable and indexed, or with the error that failed it.
763    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    /// Parks every conditional create that starts from now on until
780    /// [`release_creates`](Self::release_creates), so a test can observe
781    /// entries that are admitted but not durable. A create that is parked when
782    /// the store is dropped never runs.
783    pub fn hold_creates(&self) {
784        self.creates_held.send_replace(true);
785    }
786
787    /// Lets the creates parked by [`hold_creates`](Self::hold_creates) run.
788    pub fn release_creates(&self) {
789        self.creates_held.send_replace(false);
790    }
791
792    /// Makes every create that runs from now on fail with a transient object
793    /// store error instead of writing.
794    pub fn fail_creates(&self) {
795        self.creates_fail.store(true, Ordering::Release);
796    }
797
798    /// Lets the creates that run from now on write again after
799    /// [`fail_creates`](Self::fail_creates).
800    pub fn allow_creates(&self) {
801        self.creates_fail.store(false, Ordering::Release);
802    }
803
804    /// Makes the next create that writes its object report a transient object
805    /// store error afterwards, so the object exists although its create
806    /// failed.
807    pub fn fail_next_create_after_write(&self) {
808        self.next_create_fails_after_write
809            .store(true, Ordering::Release);
810    }
811
812    /// Waits until at least `expected` creates have been parked by
813    /// [`hold_creates`](Self::hold_creates) since the store was built.
814    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    /// Waits until at least `expected` calls of
825    /// [`wait_durable`](LogStore::wait_durable) have had to wait for an entry
826    /// that is not durable since the store was built.
827    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    /// Ends the actor the way a crash of the process would: nothing more is
838    /// written, creates that have not completed are dropped, and every caller
839    /// still waiting for the store fails. Returns once the actor has exited.
840    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    /// Waits until no object delete is in flight. The deletes a collection
856    /// starts are in flight by the time the `obsolete`, `obsolete_all`,
857    /// `release_region`, [`seal_open_batch`](Self::seal_open_batch) or
858    /// `durable` append that started it returns.
859    pub async fn wait_for_garbage_collection(&self) {
860        self.deleting.wait_until_empty().await;
861    }
862
863    /// Sets the stopped flag without sending the stop command, which is the
864    /// state a store is in between the two steps of [`stop`](LogStore::stop).
865    pub fn begin_stop(&self) {
866        self.stopped.store(true, Ordering::Release);
867    }
868
869    /// Returns the key of the object `object_seq` and the byte range the
870    /// segment of the provider's region occupies in it, so a test can damage
871    /// that segment alone, or `None` when the object holds no segment of the
872    /// region.
873    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    /// Stops the store. Creates in flight run to completion and acknowledge
899    /// their entries if they succeed; in the `enqueued` mode the remaining
900    /// backlog is uploaded first, and a failure of that upload is returned.
901    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        // A closed channel means the actor already exited.
911        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        // Only an acknowledgement is timed; an append that failed never got one.
947        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    /// Reads the provider's region from `entry_id`, hiding obsolete entries.
955    /// The catalog locates each segment, so a caller-supplied index is unnecessary.
956    /// An object collected after the read listed it is skipped. Otherwise a
957    /// segment that does not decode fails the read or is skipped and recorded
958    /// as a hole, as `on_corrupted_segment` says; a failed fetch always fails
959    /// the read. Up to [`READ_PREFETCH_DEPTH`] segments are fetched ahead
960    /// while [`READ_PREFETCH_BYTES`] allows, and each outcome is handled in
961    /// sequence order.
962    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(&region_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                // The next segment is always fetched, a later one only with
1006                // its bytes reserved from the budget.
1007                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                // The permit is held until the entries of the segment are yielded.
1030                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    /// Moves the obsolete watermark of the region up to `entry_id`. In the
1094    /// `enqueued` acknowledgement mode the watermark never passes the durable
1095    /// entry id: an entry that is not durable yet is replayed after a crash,
1096    /// and hiding it would skip it.
1097    ///
1098    /// Every id the region is assigned from now on is greater than
1099    /// `entry_id`, whatever watermark is recorded: the sequence of the next
1100    /// object is raised above the object `entry_id` names unless it is there
1101    /// already, so a watermark assigned under another prefix, or one whose
1102    /// object this prefix no longer holds, is never passed by a new id. Zero
1103    /// names no object and needs no floor. Before Mito opens a region that
1104    /// writes this WAL from its persisted state, it calls
1105    /// [`check_region_history`](LogStore::check_region_history), which
1106    /// refuses a watermark naming an object at or above the next sequence
1107    /// the store found unless `accept_missing_history` is set, so the floor
1108    /// moves for such a region only once that loss was accepted.
1109    ///
1110    /// The watermark and the floor are applied together by the actor: a call
1111    /// cancelled before its command is queued publishes neither, while a
1112    /// command already queued still applies both. While the open batch holds
1113    /// ids under the next sequence the floor cannot move, and the call fails
1114    /// with [`Error::WalObjectSequenceUnsettled`] and records neither. The
1115    /// watermark is kept in memory; the caller re-establishes it after a
1116    /// restart unless it released the region, see
1117    /// [`release_region`](LogStore::release_region). In the `enqueued` mode
1118    /// an `entry_id` this store handed out
1119    /// needs no floor: it is never handed out again while the store runs.
1120    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            // The actor exited, before the command was queued or with it
1145            // still queued behind the stop: nothing is assigned an id any
1146            // more, so the watermark alone is consistent.
1147            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    /// Hides every entry of the region and lets collection delete its
1160    /// objects, by a release in full that is durable once this returns.
1161    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    /// Releases the region through the largest id handed out for it, durably
1167    /// once this returns. Should the region id be used again, its new entries
1168    /// have greater ids and stay readable.
1169    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    /// Waits until every entry of the provider's region with an id at or
1195    /// below `entry_id` is durable and indexed. Returns at once in the
1196    /// `durable` acknowledgement mode, where a caller only holds durable ids.
1197    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    /// Fails with [`Error::MissingWalHistory`] when the prefix cannot hold
1217    /// the history of the provider's region through `entry_id`, unless
1218    /// `accept_missing_history` is set: then the region's writes that were
1219    /// not flushed are lost, which is logged and counted.
1220    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    /// Records the provider's region as one whose history starts in this
1247    /// store, which is kept in memory: a later store on the prefix judges
1248    /// the region by its watermark and the objects it finds.
1249    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        // A withdrawal records its terminal error under the same lock.
1256        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    /// Answered once the region is durable through `entry_id`.
1268    WaitDurable {
1269        region_id: RegionId,
1270        entry_id: EntryId,
1271        response: oneshot::Sender<Result<()>>,
1272    },
1273    /// Answered once the watermark is recorded and no id of the region at
1274    /// or below `entry_id` can be assigned, or with the reason neither was
1275    /// done.
1276    Obsolete {
1277        region_id: RegionId,
1278        entry_id: EntryId,
1279        watermark: EntryId,
1280        response: oneshot::Sender<Result<()>>,
1281    },
1282    /// Answered once the object holding the release record of the region is
1283    /// durable and indexed, or with the reason it is not. A `full` release
1284    /// reaches [`EntryId::MAX`], any other the largest id handed out for the
1285    /// region.
1286    Release {
1287        region_id: RegionId,
1288        full: bool,
1289        response: oneshot::Sender<Result<()>>,
1290    },
1291    /// Answered once the store keeps its start object, or once the delete
1292    /// of the start object it gave up settled.
1293    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
1305/// An append waiting for the object that holds its entries.
1306struct PendingAppend {
1307    last_entry_ids: HashMap<RegionId, EntryId>,
1308    response: AppendResponse,
1309}
1310
1311/// A caller waiting until a region is durable through an entry id.
1312struct DurableWaiter {
1313    region_id: RegionId,
1314    entry_id: EntryId,
1315    response: oneshot::Sender<Result<()>>,
1316}
1317
1318/// Where the conditional create of a sealed batch stands.
1319enum CreateState {
1320    /// The create has not started: no slot was free, or in the `enqueued`
1321    /// mode an earlier attempt failed with an object store error and is
1322    /// repeated.
1323    Pending,
1324    InFlight,
1325    Created,
1326}
1327
1328/// A sealed and encoded batch that holds its object sequence and waits to be
1329/// created, indexed and acknowledged.
1330struct 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    /// Number of creates that were attempted for the batch.
1339    attempts: u32,
1340    waiters: Vec<PendingAppend>,
1341    /// Callers of a release whose record the batch carries, answered once the
1342    /// object is durable and indexed.
1343    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
1370/// Whether the store gave up its start object, see
1371/// [`Actor::handle_withdraw`].
1372enum Withdrawal {
1373    Kept,
1374    /// The delete of the start object `object_seq` is in flight; `waiters`
1375    /// are answered once it settled.
1376    Deleting {
1377        object_seq: u64,
1378        waiters: Vec<oneshot::Sender<()>>,
1379    },
1380    Settled,
1381}
1382
1383/// The state of an `enqueued` store whose backlog the object store refuses
1384/// with an error that requires intervention, see [`requires_intervention`],
1385/// such as denied
1386/// access or a missing bucket.
1387///
1388/// Poisoning would drop the acknowledged backlog and need a restart, while
1389/// such a cause can be fixed in place. So the backlog keeps its sequences and
1390/// bytes, and its lowest pending batch is attempted alone, once nothing is in
1391/// flight, after a delay that doubles from [`CREATE_RETRY_DELAY`] up to
1392/// [`MAX_FAILING_RETRY_DELAY`]. Appends are rejected at once with a retryable
1393/// error that carries the last error: they take no entry id and never wait
1394/// behind the backlog. Releases fail with it too, and their records wait for
1395/// a create. The first create that succeeds ends the state.
1396struct Failing {
1397    /// The last error a create met, which rejected appends carry.
1398    error: Arc<Error>,
1399    since: Instant,
1400    /// The delay before the next attempt is due once nothing is in flight.
1401    delay: Duration,
1402    /// When the next attempt starts, `None` until it is scheduled.
1403    probe_at: Option<Instant>,
1404    failed_creates: u64,
1405    rejected_appends: u64,
1406    logged_at: Instant,
1407}
1408
1409impl Failing {
1410    /// Schedules the next attempt after the current delay and doubles the
1411    /// delay for the one after it.
1412    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    /// Counts an admission the store refuses and returns the error it
1418    /// fails with.
1419    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
1426/// The actor that owns the open batch and the sealed batches until they are
1427/// durable.
1428///
1429/// Sealed batches form a pipeline in sequence order. A batch is created under
1430/// its sequence as soon as one of [`MAX_IN_FLIGHT_CREATES`] slots is free, but
1431/// it is indexed and acknowledged only once every earlier batch is, so the
1432/// acknowledged history of a region never has a missing predecessor. Every
1433/// batch links to its pipeline predecessor, the batch sealed before it, or to
1434/// the last indexed object when no earlier batch waits, so recovery replays a
1435/// batch only on the chain of a later object. The outcomes of a create are:
1436///
1437/// | Situation | Sequence | Waiters | Store |
1438/// | --- | --- | --- | --- |
1439/// | created, or identical retry | advances | acknowledged in order | healthy |
1440/// | transient error, `durable` mode | never reused: a create that reported an error may have written its object | every batch that is not indexed fails, created ones included; retries are assigned new ids | healthy: the next batch links to the last indexed object, so no chain reaches a failed batch |
1441/// | transient error, `enqueued` mode | unchanged | already acknowledged, except a release, which waits for the repeat | healthy: the create is repeated with the same bytes after [`CREATE_RETRY_DELAY`], which an identical retry accepts |
1442/// | error that requires intervention, `enqueued` mode | unchanged | already acknowledged, except a release, which fails while its record stays in the backlog; later appends and releases are rejected with a retryable error | failing, see [`Failing`]: the lowest pending create is repeated alone with backoff, and the first create that succeeds ends it |
1443/// | object store error, `enqueued` mode after `stop` began | never reused | already acknowledged, except a release, which fails with the stopped error | the backlog is dropped and `stop` reports the error |
1444/// | an object of an earlier epoch holds the sequence, `durable` mode | never reused | as for a transient error | healthy: that object can never be on the chain |
1445/// | an object of the same or a later epoch holds the sequence, any conflicting object in the `enqueued` mode, encoding or catalog error | unchanged | every batch that is not indexed fails | poisoned |
1446/// | created at the last representable sequence | cannot advance | acknowledged | poisoned: no later batch can be allocated a sequence |
1447///
1448/// A create still in flight when its batch failed runs to completion and its
1449/// outcome is ignored: whatever it stored is off the chain. After `stop` began
1450/// nothing is admitted and no create starts, except that the `enqueued` mode
1451/// uploads its backlog and the `durable` mode creates one object with the
1452/// release records of failed batches; creates in flight run to completion and
1453/// acknowledge if they succeed.
1454///
1455/// Appends arrive on their own channel. The actor holds one back while the
1456/// sealed batches leave no room for the two an admission can seal, or at a
1457/// backlog threshold of the `enqueued` mode, and does not read the channel
1458/// meanwhile, so commands such as `stop` are handled while admission is held
1459/// back. A failing store reads the channel to reject every append.
1460///
1461/// The actor also collects garbage. Every batch carries the reclaim boundary
1462/// current when it was sealed, see [`ObjectCatalog::reclaim_boundary`], which
1463/// is recomputed after every `obsolete`, every release and every indexed
1464/// object and never moves down. The boundary of the last indexed object is
1465/// durable: every present object below it, indexed or not, is deleted in the
1466/// background, up to [`MAX_IN_FLIGHT_DELETES`] at a time, after an
1467/// `obsolete`, a release or an indexing that moved it. An indexed object is
1468/// unindexed once its delete succeeded; a failed delete is attempted again at
1469/// the next collection. Nothing is
1470/// collected once stop began or the store is poisoned, and deletes in flight
1471/// complete before `stop` returns.
1472struct 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    /// Largest entry id ever handed out per region, whether it became
1488    /// durable or failed. Unlike the accepted ids of the open batch it never
1489    /// moves down.
1490    issued_entry_ids: HashMap<RegionId, EntryId>,
1491    /// Waiters of the open batch in the `durable` mode.
1492    pending: Vec<PendingAppend>,
1493    /// Batches that are not durable yet, in sequence order.
1494    sealed: VecDeque<SealedBatch>,
1495    /// Creates in flight, including those of batches that already failed.
1496    creates: FuturesUnordered<BoxFuture<'static, CreateOutcome>>,
1497    /// The append held back while the sealed batches are full or, in the
1498    /// `enqueued` mode, the unpersisted backlog is at a threshold, and when
1499    /// it was held back. Later appends wait in their channel.
1500    stalled: Option<(QueuedAppend, Instant)>,
1501    /// Set while the object store refuses the `enqueued` backlog with an
1502    /// error that requires intervention.
1503    failing: Option<Failing>,
1504    durable_waiters: Vec<DurableWaiter>,
1505    /// Callers of `stop`, answered once nothing is in flight.
1506    stop: Vec<oneshot::Sender<Result<()>>>,
1507    /// The failure `stop` reports in the `enqueued` mode once an
1508    /// acknowledged backlog was dropped, recorded when it happens.
1509    stop_error: Option<Arc<Error>>,
1510    /// Sequence of the next sealed batch, `None` once the sequence is
1511    /// exhausted. The open batch assigns its entry ids from it. It is above
1512    /// every sealed batch and never moves back while the store runs.
1513    next_object_seq: Option<u64>,
1514    /// Epoch of this instance, carried by every object it writes.
1515    epoch: u64,
1516    /// The last indexed object, which is the start object until a batch is
1517    /// indexed. A batch sealed while no earlier batch waits extends it.
1518    last_indexed: ChainLink,
1519    /// The reclaim boundary the next sealed batch carries.
1520    reclaim_boundary: u64,
1521    /// The reclaim boundary of the last indexed object.
1522    durable_reclaim_boundary: u64,
1523    /// Sequences of objects that may be present but are not indexed: the
1524    /// objects recovery listed off the chain and the batches that failed.
1525    unindexed: BTreeSet<u64>,
1526    /// Release records of batches that failed, which the next sealed batch
1527    /// carries again, so a release whose call failed still becomes durable
1528    /// while the store runs. A timer tick seals them when no entry is
1529    /// admitted.
1530    failed_releases: BTreeMap<RegionId, EntryId>,
1531    /// Sequences whose create is in flight.
1532    creating: HashSet<u64>,
1533    deleting: Arc<DeletingObjects>,
1534    deletes: FuturesUnordered<BoxFuture<'static, (u64, Result<()>)>>,
1535    /// Deletes that failed since the last collection started.
1536    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        // The first tick completes immediately.
1557        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                    // Nothing starts after stop began; the `enqueued` mode
1564                    // sealed its backlog when stop was requested.
1565                    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                    // Every sender is gone: the store was dropped without
1608                    // `stop`. The creates in flight are dropped with the actor.
1609                    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        // The append was queued before `stop` set the flag; nothing that is
1620        // not durable yet gets admitted once it is set.
1621        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    /// Admits `entries` into the open batch, which assigns their ids under
1643    /// the next object sequence. In the `durable` mode the caller waits for
1644    /// the object, in the `enqueued` mode it is answered now.
1645    fn admit(&mut self, entries: Vec<Entry>, response: AppendResponse) {
1646        // A region would run past the position range of the open batch: the
1647        // batch is sealed and the entries open the next one. The size limit
1648        // seals a batch long before a million entries of one region, so this
1649        // is a theoretical bound.
1650        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            // Nothing was admitted: the append alone runs past the position
1670            // range, which no object can hold.
1671            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    /// Returns true while an append has to wait for an upload: the sealed
1699    /// batches leave no room for the two an admission can seal, or in the
1700    /// `enqueued` mode the unpersisted backlog is at a threshold.
1701    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    /// Returns true once the unpersisted backlog, the open batch and every
1707    /// sealed batch that is not durable, reaches the size or the age threshold.
1708    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    /// Seals the open batch when no create is in flight, so that a stalled
1727    /// append has an upload to wait for.
1728    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    /// Admits the stalled append once it no longer has to be held back.
1735    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            // The append needs an upload to complete.
1747            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    /// Takes the held-back append out on whichever path releases it and
1756    /// records how long it was held back.
1757    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    /// Seals the open batch as the object `next_object_seq` and starts its
1764    /// create when a slot is free. Returns whether a batch was sealed.
1765    fn flush_open_batch(&mut self) -> bool {
1766        self.seal(Vec::new())
1767    }
1768
1769    /// Seals the open batch together with `releases` and the release records
1770    /// of failed batches as the object `next_object_seq`, even when the open
1771    /// batch holds no entry, and starts its create when a slot is free.
1772    /// Returns whether a batch was sealed.
1773    ///
1774    /// A failing store keeps the records of failed batches coalesced instead
1775    /// of sealing them alone behind its backlog on every tick, until `stop`
1776    /// seals them for its last attempt of the backlog.
1777    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            // The batch takes the last representable sequence: it is created
1832            // and acknowledged, but no later batch can be allocated one.
1833            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    /// Starts the creates of pending batches in sequence order while fewer
1867    /// than [`MAX_IN_FLIGHT_CREATES`] are in flight, counting the creates of
1868    /// batches that already failed. Nothing starts once stop began, except
1869    /// the backlog of the `enqueued` mode and, once the stop command was
1870    /// handled, the release records [`handle_stop`](Self::handle_stop)
1871    /// seals in the `durable` mode. A failing store only schedules the
1872    /// next attempt once nothing is in flight, see [`Failing`]; after stop
1873    /// began it uploads its backlog at once.
1874    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    /// Attempts the lowest pending batch of a failing store, whose delay
1906    /// elapsed. After stop began `start_creates` uploads the backlog instead.
1907    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    /// Starts the create of the sealed batch at `index` after `delay`.
1925    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            // The store was dropped while the create was parked: it never
1949            // runs.
1950            #[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            // Counted as the object store answered, before the epoch of a
1973            // conflicting object is read.
1974            record_create(&created, Some(sealed_at));
1975            // Any conflict poisons an `enqueued` store, so only the
1976            // `durable` mode reads the epoch of the existing object.
1977            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        // A batch that already failed, or that the store gave up on when it
1990        // poisoned itself: the object may exist, but it is off the chain, and
1991        // collection may delete it now.
1992        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            // Nobody is left to retry in the `enqueued` mode, so the store
2007            // repeats a create that the object store did not confirm under
2008            // the same sequence with the same bytes, which an identical retry
2009            // accepts: after `CREATE_RETRY_DELAY`, or with backoff while the
2010            // store is failing.
2011            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            // The object store did not confirm the object, which may still
2018            // exist or land later, or an earlier epoch holds the sequence and
2019            // can never be on the chain. A caller of the `durable` mode
2020            // retries the append itself; after stop began a failure drops the
2021            // `enqueued` backlog.
2022            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    /// Records the failure of a create the `enqueued` mode repeats. An error
2035    /// that requires intervention makes the store failing; while it is, every
2036    /// error becomes the one rejected appends carry.
2037    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        // The append held back would wait for an upload that is not coming.
2068        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        // So would a release. Its record stays in the backlog and becomes
2083        // durable with it.
2084        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    /// Ends the failing state, if the store is in it, because of `reason`.
2092    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    /// Indexes and acknowledges the sealed batches from the front as far as
2106    /// they are created, and starts the creates that a free slot allows.
2107    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    /// Indexes the created object at the front and acknowledges its waiters.
2118    /// Returns false when the catalog rejected it, which poisons the store.
2119    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        // The collection starts before the waiters learn that the object is
2143        // indexed.
2144        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    /// Returns true when no id of the region at or below `entry_id` waits to
2177    /// become durable. An id of a batch that failed will never be durable and
2178    /// no longer waits.
2179    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    /// Returns the lowest id of the region that was handed out and is not
2185    /// durable yet. The sealed batches are in sequence order and the open
2186    /// batch is above all of them.
2187    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    /// Fails every batch that is not indexed after a create failed
2201    /// transiently, including batches already created and batches whose
2202    /// create is in flight. Their sequences are not reused: a create that
2203    /// reported an error may still have written its object. The next batch
2204    /// links to the last indexed object, so no later chain reaches a failed
2205    /// batch. Waiters of a store that was stopped meanwhile learn that
2206    /// instead of the I/O error, like every other entry that never became
2207    /// durable.
2208    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        // The stalled append was never admitted; the sealed batches are gone,
2224        // so `settle` admits it, or refuses it once stop began.
2225        self.fail_waiters(failure);
2226        // An acknowledged backlog was dropped: `stop` reports it, whether
2227        // its caller has arrived yet or not. So it does release records once
2228        // stop began, which no later create carries.
2229        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    /// Keeps the release records of a batch that failed for the next sealed
2238    /// batch.
2239    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    /// Records `error` as terminal and fails every waiter that is not
2254    /// acknowledged with it, or with the stopped error if the store was
2255    /// stopped meanwhile. Creates in flight run to completion, but their
2256    /// outcome is ignored: in the `durable` mode the entries of an object they
2257    /// create were never acknowledged, like those of a crash between creation
2258    /// and acknowledgement; in the `enqueued` mode the acknowledged backlog is
2259    /// discarded and `stop` reports it. Returns the recorded error.
2260    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        // As in `roll_back`, release records lost once stop began are
2278        // reported by `stop` in both modes.
2279        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    /// Drops the entries of the open batch; the next admission hands out the
2287    /// same ids again under the sequence the batch is at.
2288    fn reset_open_batch(&mut self) {
2289        self.open_batch.reset();
2290    }
2291
2292    /// Fails the waiters of the open batch, the stalled append and the
2293    /// durability waiters.
2294    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    /// Fails the waiters of the open batch and the durability waiters.
2302    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        // An acknowledged backlog was dropped: no entry that is not durable
2330        // can be certified any more, whether it was in that backlog or not.
2331        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        // A caller that stopped waiting leaves a closed response behind.
2340        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    /// Gives up the start object while it is the only object this store
2352    /// knows of: the catalog indexes nothing else, nothing was ever handed an
2353    /// id, no batch, append or release waits, no other object was listed or
2354    /// created, no region's history started in the store, and the store is
2355    /// neither failing nor terminal. A release seals its own batch without
2356    /// handing an id, so the catalog decides once its object is indexed, and
2357    /// the object it may then leave alone holds a segment. The store then
2358    /// records a terminal error, so nothing is admitted that would link to
2359    /// the object, and deletes it like a collected object; `response` is
2360    /// answered once that delete settled, or at once when the store keeps
2361    /// the object. A later request waits for the same delete.
2362    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        // The start object is the one object indexed, and the only one
2393        // without segments: a release object can be indexed alone once it
2394        // collected the start object.
2395        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    /// Makes sure no id of the region at or below `entry_id` is assigned
2425    /// from now on, then records the obsolete watermark of the region;
2426    /// neither is done when the sequence cannot be raised.
2427    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    /// Moves the watermark of the region up to the release and seals the
2450    /// open batch with a release record of the region; `response` is
2451    /// answered once that object is durable and indexed. The batch carries
2452    /// the reclaim boundary the watermark allows, so the object makes both
2453    /// durable at once.
2454    ///
2455    /// The watermark is not clamped to the durable entry id in the `enqueued`
2456    /// mode: a released region is never replayed, and the record is durable
2457    /// only once every object sealed before it is. A release arrives as a
2458    /// command, so [`MAX_SEALED_BATCHES`] does not hold it back; every caller
2459    /// waits for its own object, so releases add at most one batch per caller.
2460    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(&region_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        // A failing store admits nothing behind its backlog: the release
2487        // fails at once, and its record is kept like that of a batch that
2488        // failed, sealed once a create succeeds.
2489        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        // Sealing failed and poisoned the store.
2501        if let Some(error) = terminal(&self.terminal_error) {
2502            let _ = response.send(Err(shared(&error)));
2503        }
2504    }
2505
2506    /// Raises the reclaim boundary to what the catalog and the obsolete
2507    /// watermarks allow.
2508    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    /// Starts a collection, which also attempts the deletes that failed since
2517    /// the last one again.
2518    fn collect(&mut self) {
2519        self.failed_deletes.clear();
2520        self.start_deletes();
2521    }
2522
2523    /// Starts deleting the objects below the durable reclaim boundary while
2524    /// fewer than [`MAX_IN_FLIGHT_DELETES`] deletes are in flight, skipping
2525    /// objects whose create is in flight or whose delete failed since the
2526    /// collection started.
2527    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    /// Unindexes the object `object_seq` and drops its holes once its delete
2558    /// succeeded, then settles its attempt, so a read that the attempt
2559    /// releases sees the outcome in the catalog.
2560    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    /// Moves the sequence of the next object above the object that holds
2627    /// `entry_id` unless it is there already. The move fails while the open
2628    /// batch has handed out ids under the next sequence. A floor that does
2629    /// not fit an entry id poisons the store like an exhausted sequence.
2630    fn raise_sequence_floor(&mut self, region_id: RegionId, entry_id: EntryId) -> Result<()> {
2631        // Nothing is assigned an id after stop began.
2632        if self.is_stopped() {
2633            return Ok(());
2634        }
2635        if let Some(error) = terminal(&self.terminal_error) {
2636            return Err(shared(&error));
2637        }
2638        // In the `enqueued` mode an id the store handed out is never handed
2639        // out again while it runs: a create that the object store did not
2640        // confirm is repeated under its sequence and any other failure
2641        // poisons the store. Every later id of the region is greater, so the
2642        // id needs no floor even before it is durable.
2643        if self.ack_mode == AckMode::Enqueued
2644            && entry_id <= self.issued_entry_ids.get(&region_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    /// Begins stopping. Nothing is admitted from now on and the append held
2677    /// back is refused; the `durable` mode drops the open batch and the
2678    /// batches whose create has not started, and seals the release records of
2679    /// failed and dropped batches once more; the `enqueued` mode seals its
2680    /// backlog and uploads it, a failing store included. Stop is answered by
2681    /// [`finish_stop`](Self::finish_stop) once nothing is in flight.
2682    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    /// Answers the callers of `stop` once every sealed batch is settled and
2715    /// every create and delete has completed. Returns true when the actor is
2716    /// done.
2717    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    /// Fails every caller that waits for the store; the creates are
2754    /// dropped with the actor.
2755    #[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    /// Settles the deletes the actor still had in flight, so no read waits
2775    /// for an attempt that will never complete, and releases the append still
2776    /// held back, whose caller is gone or about to be.
2777    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/// Actors not dropped yet. An actor outlives the test that dropped its store
2787/// and may still move the process-wide metrics.
2788#[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
2798/// Records `error` as the terminal error unless one is already recorded, and
2799/// returns the recorded one.
2800fn 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
2812/// Counts the outcome of a conditional create and, for the object of a batch
2813/// sealed at `sealed_at`, times a success. The create of a batch is counted
2814/// whether or not the batch is still there to be indexed: an object a
2815/// poisoned store gave up on is as durable as any other.
2816fn 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
2833/// Sets both indexed gauges to the objects `catalog` holds.
2834fn 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
2844/// Returns true for an object store error that repeating the request cannot
2845/// settle until someone changes the credentials, the configuration or the
2846/// bucket: denied access; an invalid configuration, which is how OpenDAL
2847/// reports a missing S3 bucket; and a write to a location that does not
2848/// exist, which is how OSS, GCS and Azure Blob Storage report a missing
2849/// bucket or container. A read that finds nothing is not one: it is what the
2850/// read that reconciles a create meets when the object is gone, and a repeat
2851/// creates it. Every other error, an HTTP 400 or 5xx, a timeout or a refused
2852/// connection, may pass by itself. A create that fails with an error
2853/// requiring intervention is still repeated, with the backoff [`Failing`]
2854/// describes.
2855///
2856/// Only the kind of the error decides. Its status cannot: on object storage
2857/// the store sits behind OpenDAL's retry layer, which marks every error it
2858/// returns as persistent, including those it never retried because they were
2859/// not temporary, so a 403 arrives with the status of a 503 that outlasted
2860/// the retries.
2861fn 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
2874/// Wraps an error that several callers receive.
2875fn shared(error: &Arc<Error>) -> Error {
2876    ObjectStoreWalSnafu.into_error(error.clone())
2877}
2878
2879/// Returns whether collection deleted the object `object_seq` after a read
2880/// listed it. The set of deleting objects is observed before the catalog, the
2881/// reverse of the order the actor updates them, so an object that no longer
2882/// looks deleting but still looks indexed was never collected. An undecided
2883/// delete is waited for.
2884async 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
2898/// Fetches and decodes the segment `entry` describes in the object
2899/// `object_seq`. A segment that does not decode is fetched once more, since
2900/// the download rather than the object may be damaged; when it still does not
2901/// decode the error is returned as [`Error::InvalidWalObject`]. A failed fetch
2902/// is returned as is.
2903async 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
2917/// Takes from `budget` the bytes the segment `entry` describes holds once
2918/// decoded, if they are free: its length plus a [`Record`] per entry.
2919fn 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
2928/// Runs [`fetch_segment`] on a task of its own, so the fetch proceeds while
2929/// the read is busy with earlier segments, and returns the outcome with
2930/// `permit`. The task holds the permit, so the reserved bytes are released
2931/// only with the buffers of the fetch. Dropping the returned future, as a
2932/// dropped read does, aborts the task; a task that panics fails the fetch with
2933/// [`Error::WalSegmentFetch`].
2934fn 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
2958/// Records the segment `entry` describes in the object `object_seq` as a hole
2959/// of its region, once per object, counts the skip and warns about it. An
2960/// object collected meanwhile is skipped without a hole: the catalog is held
2961/// while the hole is recorded, as a collection holds it while it drops the
2962/// holes of the object it removes.
2963fn 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
3005/// Encodes the entries of a batch, and its release records as the single
3006/// entry of [`RELEASE_REGION_ID`], whose id is at position 1 of the object;
3007/// segments are ordered by region id, so it is the last segment.
3008fn 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/// What recovery rebuilt from the objects under a prefix.
3031#[derive(Debug)]
3032struct Recovered {
3033    catalog: ObjectCatalog,
3034    /// Sequence above every present object and every id the catalog holds.
3035    next_object_seq: u64,
3036    durable_entry_ids: HashMap<RegionId, EntryId>,
3037    /// The object the next object extends, `None` on an empty prefix.
3038    tip: Option<ChainLink>,
3039    /// Largest epoch any present object carries, zero on an empty prefix.
3040    max_epoch: u64,
3041    /// The reclaim boundary of the tip, zero on an empty prefix.
3042    reclaim_boundary: u64,
3043    /// Present objects that are not indexed, in sequence order.
3044    unindexed: Vec<u64>,
3045    /// The entry id each region is released through, from the release
3046    /// records of the indexed objects, see [`read_releases`].
3047    released_entry_ids: HashMap<RegionId, EntryId>,
3048    /// Number of objects whose header and footer were read.
3049    recovered_objects: u64,
3050}
3051
3052/// The objects recovery fetched, ordered by sequence, and the listed objects
3053/// that were gone when their header or footer was fetched, with the error the
3054/// fetch met. A collection of an earlier instance may complete after the
3055/// listing.
3056struct FetchedObjects {
3057    objects: Vec<FetchedObject>,
3058    vanished: Vec<(u64, Error)>,
3059}
3060
3061/// An object as recovery fetched it: its key, header and footer, and its
3062/// segment of release records when the reads of the footer returned it.
3063struct FetchedObject {
3064    object: ListedObject,
3065    header: Header,
3066    footer: Vec<FooterEntry>,
3067    releases: Option<Bytes>,
3068}
3069
3070/// Rebuilds the catalog from object headers and footers,
3071/// so recovery costs a few small reads per object however large the objects
3072/// are. Segments are not read, except those of release records, see
3073/// [`read_releases`]; a segment checksum is verified by the read that decodes
3074/// it. Footers are fetched for up to [`RECOVERY_CONCURRENCY`] objects
3075/// at a time while the listing goes on, and indexed in sequence order, so the
3076/// catalog checks the entry ranges of every object against its predecessors
3077/// like a sequential replay. Only the objects on the chain [`select_chain`]
3078/// picks are indexed.
3079async 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
3091/// Reads the release records of the indexed objects, which seed the
3092/// watermarks of their regions, from the segment of [`RELEASE_REGION_ID`] of
3093/// every indexed object that holds one: the bytes in `fetched` by object
3094/// sequence, which the reads of the footers returned, or else one range read.
3095/// The segments of objects that are not indexed are never decoded. A segment
3096/// that cannot be fetched or does not decode fails recovery, whatever
3097/// `on_corrupted_segment` says: skipping it would lose the watermarks it
3098/// seeds.
3099///
3100/// A release record needs no bookkeeping of its own. It is sealed once every
3101/// entry it covers was admitted, so it lies in the object holding the last
3102/// segment it covers or in a later one. Collection deletes, in any order,
3103/// only objects below the durable reclaim boundary, and recovery indexes only
3104/// objects at or above the boundary of the tip, which a start object carries
3105/// forward. So recovery indexes a release record whenever it indexes a
3106/// segment the record covers, and once the record is below the boundary so is
3107/// every segment it covers, which recovery never indexes again, whether
3108/// collection deleted it yet or not. A release in full also covers entries a
3109/// region would be assigned afterwards, so it is made only for a region that
3110/// writes nothing more.
3111async 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
3153/// Indexes the chain of the fetched objects. Objects off the chain, orphans
3154/// and objects below the reclaim boundary of the tip, are not indexed, but no
3155/// later object takes their sequences, nor the sequences of vanished objects.
3156/// A vanished object at or above the boundary of the tip fails recovery with
3157/// the error its fetch met: collection never deletes such an object.
3158fn 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    // Every open writes an object that starts a chain or extends a complete
3167    // one, and collection removes only objects below the reclaim boundary of
3168    // a later object, so present objects without any complete chain mean
3169    // objects of the chain are gone.
3170    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
3240/// Returns, in sequence order, the objects on the chain that ends at the tip:
3241/// the complete object with the largest epoch, then the largest sequence.
3242///
3243/// An object is complete when every link on its chain holds, judged with the
3244/// reclaim boundary of that object: the chain starts at an object without a
3245/// predecessor or reaches a predecessor below the boundary, whether or not it
3246/// is present, and every other link names a present object that carries the
3247/// epoch the link records. The chain of the tip ends there, so objects below
3248/// its boundary are not replayed. Only one instance writes
3249/// under an epoch, so the epoch tells an object of the linking instance from
3250/// another object under the same sequence. A create that
3251/// was reported as failed may still leave its object, but every object
3252/// written after that failure links past it, and every instance writes under
3253/// an epoch above every object present when it opened, so neither such an
3254/// object nor a late object of an earlier instance ends the chosen chain.
3255fn 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
3264/// Returns, in sequence order, the chain that ends at `tip` if `tip` is
3265/// complete, see [`select_chain`].
3266fn 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
3286/// Writes the empty object that starts the epoch of this instance at
3287/// `object_seq`, linked to the recovered `tip` and carrying the recovered
3288/// `reclaim_boundary`, and returns it as the tip later objects extend,
3289/// together with the sequences of earlier-epoch objects it moved past. The
3290/// epoch is one above the sequence the create claims, so no two instances
3291/// share one however their opens interleave, and it is above the epoch of
3292/// every object recovery listed.
3293///
3294/// An object an earlier instance left at that sequence after recovery listed
3295/// the prefix has a lower epoch and never ends a chain, so the start object
3296/// moves to the next sequence and epoch. An object of an equal or later epoch
3297/// belongs to another writer of the prefix, and the conflict fails the open,
3298/// as does a create whose outcome is unknown: the next open counts the object
3299/// in either case. So does a create that finds the same bytes present:
3300/// another open that recovered the same objects writes an identical start
3301/// object, so this open cannot claim the epoch even if the object is its own.
3302async 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
3356/// Resolves a create of this store's `epoch` that met a different object:
3357/// an object of an earlier epoch can never be on the chain, so the create
3358/// fails like a transient error; an object of the same or a later epoch
3359/// belongs to another writer and `conflict` is returned.
3360async 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
3378/// Fetches and verifies the headers and footers of `objects` as they are
3379/// listed, up to `concurrency` objects at a time, and returns them ordered by
3380/// object sequence whatever the order the fetches complete in. An object the
3381/// object store no longer has is returned as vanished; a failed listing or
3382/// any other failure abandons the remaining fetches.
3383async 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
3420/// Reads the header, trailer and footer of `object` and verifies them: the
3421/// header must carry the sequence of the key, the trailer must be well formed,
3422/// the footer must match the checksum the trailer holds and its segments must
3423/// tile the object body. Also returns a copy of the segment of release records
3424/// when the bytes read hold it and it is at most [`MAX_KEPT_RELEASE_BYTES`],
3425/// which is not decoded here.
3426///
3427/// A short object is read whole. Otherwise the header and a window at the end
3428/// of the object are read concurrently, and the footer is read separately only
3429/// when it starts before the window.
3430async 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    // A copy, so the window is not kept until the releases are decoded.
3492    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
3505/// Verifies the header and trailer of the object `object_seq` of `object_len`
3506/// bytes from its first bytes `head` and its last bytes `tail`, and returns
3507/// the header and the trailer with the range the footer occupies in the object.
3508fn 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/// Object access of the store, so tests can inject failures.
3543#[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    /// See [`ObjectStoreIo::list_ahead`]; by default the objects
3556    /// [`list`](Self::list) returns.
3557    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    /// Describes the object store the objects live in, for messages.
3568    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    /// Held by every test of the module: the metrics of the store are
3628    /// process-wide, so tests running in one process would otherwise move the
3629    /// counters and gauges that another test asserts on.
3630    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    /// The same batching as `config`, acknowledging appends on admission.
3646    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    /// Every append reaches the size limit, so it is persisted on its own.
3664    fn eager() -> ObjectStoreWalConfig {
3665        config(Duration::from_secs(3600), 1)
3666    }
3667
3668    /// Nothing is persisted until a test seals the open batch.
3669    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    /// The id of the entry at `position` of its region in object `object_seq`.
3695    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    /// Appends `count` single-entry batches, waiting for each to be admitted
3724    /// before the next is sent, so they are admitted in order.
3725    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    /// Lists the objects once no delete is in flight.
3753    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    /// Waits for the next operation that `ParkedIo` parked.
3775    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    /// Waits for `count` parked operations and returns their releases by
3782    /// sequence.
3783    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        // The start object of the open is created without parking.
3815        io.creates_parked.store(true, Ordering::SeqCst);
3816        (store, io, parked)
3817    }
3818
3819    /// Round-trips a command through the actor, so an assertion that
3820    /// something did not happen runs after the actor handled every command
3821    /// sent before. The open batch must be empty, as under the eager config.
3822    async fn round_trip_actor(store: &ObjectStoreLogStore) {
3823        store.seal_open_batch().await.unwrap();
3824    }
3825
3826    /// An in-memory object store behind the retry layer object storage has
3827    /// in production, whose writes and reads fail with the error kind their
3828    /// switch holds, recording when each refused write started. The retry
3829    /// layer reports such an error as persistent, as it reports a 403 of S3.
3830    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    /// Asserts that an append was rejected because the object store refuses
3914    /// the backlog with `kind`: the error is retryable and carries the
3915    /// refusal.
3916    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        // A root-level object must not be part of any node's recovery.
3984        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    /// A writer that stores the object whether it exists or not, as an object
4018    /// store that ignores the condition of a create does.
4019    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    /// A writer that writes the object and then reports a failure, as a
4042    /// create whose response is lost.
4043    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        // The open stops before it writes a start object, and deletes the
4114        // probe.
4115        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        // The first write of the open is the first create of the probe.
4138        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        // OpenDAL's unit deleter refuses every delete, so the probe stays.
4152        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        // The next open recovers the start object of the first one only.
4168        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    /// Rebuilds the catalog by decoding whole objects as a recovery oracle.
4175    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    /// The header of a fixture object at `object_seq`, which extends the
4249    /// present object right below it under the epoch of that object, so the
4250    /// fixtures a test writes in sequence order form one chain.
4251    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    /// Puts an object of another writer with `epoch` under `object_seq`. A
4287    /// store opened on an empty prefix writes epoch 1.
4288    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    /// Asserts that recovery over `object_store` rejects the object at `path`
4323    /// for `reason`, whatever a read does with a corrupted segment.
4324    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            // The objects `populate` gave the region one entry each.
4374            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[&region_id]);
4378            assert_eq!(durable[&region_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    /// Overwrites the byte range of footer entry `index` and refreshes the
4446    /// footer checksum, so the footer is intact but describes the wrong bytes.
4447    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                // The footer itself still verifies.
4492                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        // The wide object took the header, the tail window and the footer;
4521        // the narrow one was read whole.
4522        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    /// Regions enough for a footer longer than the window recovery reads
4573    /// from the end of an object, so that recovery fetches the header, that
4574    /// window and the footer of such an object separately.
4575    const WIDE_REGIONS: u32 = (RECOVERY_TAIL_WINDOW / FOOTER_ENTRY_LEN + 100) as u32;
4576
4577    /// Writes object 0 with a footer longer than the tail window and returns
4578    /// the offsets of the window and of the footer.
4579    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        // The sequence resumes above the object the largest id names, where
4622        // the start object of the store takes object 6, so the first new id
4623        // of every region is greater than every old one.
4624        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        // The sequence continues after the start object of the restart.
4650        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    /// Returns the message of the missing history error the check of
4665    /// `region_id` at `entry_id` fails with, or `None` when it passes.
4666    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    /// Copies every object under the prefix of `from` to the same key in `to`.
4688    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        // A region written since the store opened holds its history through
4707        // its entries, not past them.
4708        let written = append(&store, region(2), "a").await.unwrap().last_entry_ids[&region(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        // No other region has a history under a prefix that held no object,
4718        // not even one that never flushed. The store admitted an append, so
4719        // it keeps its objects.
4720        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                &region(1).to_string(),
4724                PREFIX,
4725                // A memory store names itself by its address as its bucket.
4726                "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        // A region created on the store has its history here without any
4738        // entry.
4739        store.start_region_history(&provider(region(3))).unwrap();
4740        assert_eq!(None, missing_history(&store, region(3), 0).await);
4741
4742        // The provider must select the store's prefix.
4743        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        // Every open of the prefix refuses the region, since each refusal
4757        // deletes the start object the open wrote and the next open finds
4758        // the prefix empty again.
4759        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            // The catalog and the indexed gauges no longer hold the object.
4767            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            // The store accepts nothing more, and refuses again without a
4777            // second delete.
4778            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        // A store whose region history started keeps its start object.
4800        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        // The refusal that withdraws the start object is cancelled once the
4817        // actor started its delete.
4818        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        // A later withdrawal request and stop reach the actor, and a
4827        // durability wait queued behind them is answered, so the actor
4828        // handled both; neither is answered while the delete is in flight.
4829        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        // The next open finds the prefix empty and refuses the region again.
4867        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        // While the withdrawal of start object 0 waits on its delete, a
4881        // successor opens the prefix, links its start object 1 to object 0
4882        // and stops before any append.
4883        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        // Object 1 reclaims the deleted object 0, so its chain is complete.
4897        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        // The object is still there, so the catalog and the indexed gauges
4924        // keep it.
4925        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        // The start object 0, then objects 1 and 2 of region 1.
4942        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        // Every restart writes one more start object, but the newest object
4948        // holding entries stays object 2, so a refused open does not let
4949        // the next one pass a watermark of object 3.
4950        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        // An entry written since then lies above the objects found, and the
4979        // catalog holds it.
4980        let store = open(object_store, &eager()).await;
4981        let written = append(&store, region(2), "c").await.unwrap().last_entry_ids[&region(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        // A prefix that holds only start objects holds no entry.
4988        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        // Region 1 flushes object 1; object 2 of region 2 carries the boundary
5010        // past it, so only object 2 is left once region 2 flushes it too.
5011        let flushed_1 = append(&store, region(1), "a").await.unwrap().last_entry_ids[&region(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[&region(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        // The object region 1 flushed is gone, but the newest object stays
5025        // and keeps the next sequence above it.
5026        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        // Region 1 flushes object 1; the release of region 2 seals object 2,
5039        // which holds only its release record and carries the boundary past
5040        // object 1.
5041        let flushed = append(&store, region(1), "a").await.unwrap().last_entry_ids[&region(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        // The object holding the release record bounds the history.
5054        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        // The release seals object 1, which collects the start object.
5065        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        // The refusal leaves the object holding the release record, and the
5072        // store keeps accepting appends.
5073        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[&region(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[&region(1)];
5090        assert_eq!(vec![2], collected_object_seqs(&store).await);
5091        store.stop().await.unwrap();
5092
5093        // Another location without the objects does not hold the history.
5094        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        // A copy of every object does, and replays the unflushed entry.
5099        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        // The accepted region's history starts in this store, so a second
5129        // open of it is not counted again, and neither is a history the
5130        // prefix holds.
5131        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[&region(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        // Once Mito records the accepted watermark, the region's next id is
5146        // above it.
5147        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        // Even an empty append fails once stop began.
5239        assert_stopped(&store.append_batch(Vec::new()).await.unwrap_err());
5240    }
5241
5242    /// Builds a store whose commands the test receives instead of an actor.
5243    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        // No further fetch can start while a full wave is parked.
5371        for _ in 0..16 {
5372            tokio::task::yield_now().await;
5373        }
5374        assert!(parked.try_recv().is_err());
5375
5376        // Complete the initial wave in reverse order, parking each replacement.
5377        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        // The sequence after the populated objects holds the object that
5430        // started the epoch of the store.
5431        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    /// Serves the entries of a listing in pages of [`LISTING_PAGE`], each
5445    /// only once `pages` allows it, fails, or panics with `panics`, after
5446    /// `fail_after` entries, and records when it is dropped.
5447    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    /// The prefix of `object_store` listed by [`PagedLister`]s, whose reads
5484    /// go through `read`.
5485    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        // Every read allows one more page, so recovery completes only if it
5518        // reads footers before the listing ends.
5519        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        // Only the first page is ever listed, so the listing waits for good
5572        // unless the failed read stops it.
5573        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            // Segments are fetched concurrently, so in no particular order.
5734            let mut reads = reads.lock().unwrap().clone();
5735            reads.sort_unstable();
5736            assert_eq!(expected_ranges[&region(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        // A watermark is kept in memory only.
5789        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        // A release in full survives the restart, and a later watermark does
5799        // not move it down.
5800        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    /// The range reads of object `object_seq` among `reads`, in the order
5857    /// they were issued.
5858    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    /// Writes object 0 with one entry of region 1 and one of region 2 and
5869    /// object 1 with an entry of region 2, then flips a byte in the segment of
5870    /// region 2 in object 0. Returns the key of object 0 and its footer.
5871    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        // Recovery indexes the object; a read of the other region and one of
5913        // the later object of the damaged region are unaffected.
5914        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        // A read across the damaged segment fetches it twice, continues with
5929        // the next object and records the hole.
5930        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        // Every read across the segment skips it again; the hole is one.
5954        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            // The first download of the segment is damaged, the second is
6025            // not: the read sees the entries and records no hole.
6026            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    /// The entries of region 1 that [`put_object_per_entry`] wrote to the
6076    /// objects `object_seqs`.
6077    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    /// Writes objects 0 to `objects - 1`, object `n` with entry `n + 1` of
6093    /// region 1.
6094    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    /// Opens a store over [`put_object_per_entry`] objects, releasing every
6101    /// read of recovery; range reads park from then on.
6102    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    /// Polls until `settled` holds.
6130    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        // The first poll starts the fetches of the first `depth` objects.
6166        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        // Later objects complete first: they wait for object 0 and still
6175        // count toward the depth.
6176        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        // No fetch starts while the caller holds the entries of object 0;
6186        // each later poll starts the fetch of the next object.
6187        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        // The fetch of object 5 fails before any earlier fetch completes.
6223        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        // The fetches started meanwhile are abandoned.
6248        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        // A collection removes object 9 after the read listed it.
6285        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        // While other reads hold the whole budget, a read fetches only the
6373        // segment it yields next.
6374        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        // Once the budget is free, the next poll fetches ahead up to the depth.
6388        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        // The first poll fetches object 0, and object 1 with its bytes
6431        // reserved; the read of object 1 blocks its thread.
6432        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        // The aborted fetch keeps its bytes until it ends.
6438        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        // Appends admitted into one batch share its object; the regions take
6456        // their own positions in admission order, and every append learns
6457        // the last id of each of its regions.
6458        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        // The next object starts every region at position one again.
6482        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        // The open batch holds every position of the region.
6524        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        // The next entry of the region seals the batch and opens the next
6529        // object; another region would still have fit.
6530        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        // An append that alone runs past the range fits no object: it seals
6540        // the open batch like any append the batch cannot take, then it is
6541        // refused without poisoning the store.
6542        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        // An append is acknowledged once its object is durable, so a completed
6575        // append proves the tick after it sealed the batch. The appends are
6576        // spaced far apart, so every one of them lands in its own tick and
6577        // the ticks in between find an empty batch.
6578        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            // Object 1 carries the boundary past the start object.
6588            let expected_seqs = (1..=index as u64 + 1).collect::<Vec<_>>();
6589            assert_eq!(expected_seqs, collected_object_seqs(&store).await);
6590        }
6591
6592        // Ticks with an empty open batch do not create objects.
6593        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        // Nothing was admitted.
6652        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        // At most the limit of creates run at a time; the rest wait for a slot.
6665        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        // Objects 3 and 2 become durable before object 1 and free a slot
6675        // each, but nothing is acknowledged ahead of object 1.
6676        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        // Object 1 releases the acknowledgements of objects 1 to 3.
6688        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        // Object 6 before object 5: the append of object 6 waits for it.
6704        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        // Every batch extends the batch sealed before it, whichever was
6732        // durable first.
6733        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        // Every slot is taken; the last batch waits for one.
6762        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        // Object 1 fails while objects 2 to 4 are in flight and object 5 waits
6768        // for a slot: every batch fails at once.
6769        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        // The retried entries take the sequences after the last sealed one.
6781        // The creates of the failed batches still hold their slots, so only
6782        // one retry starts until they complete; what they store is off the
6783        // chain.
6784        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        // Object 2 is created while object 1 failed: both batches fail, and
6820        // the store keeps serving.
6821        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        // The retry extends the start object, not the failed batches.
6840        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        // After a restart only the acknowledged entry replays: object 2 is
6865        // off the chain although it exists.
6866        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        // Another writer of the same epoch took sequence 1.
6882        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        // Object 2 is created; object 1 conflicts with the foreign object.
6887        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        // A late object of an earlier epoch lands under sequence 1.
6920        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        // The store keeps serving: the retry takes the next sequence.
6946        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        // The stale object is off the chain after a restart.
6963        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        // A durable watermark names an object below the next sequence and
6996        // changes nothing: the open batch goes on under sequence 2.
6997        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        // A watermark that names a later object, as one inherited from
7005        // another prefix does, cannot move the sequence while the open batch
7006        // handed out ids under it: neither the sequence nor the watermark
7007        // moves.
7008        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(&region_two)
7026                .is_none()
7027        );
7028
7029        // Once the batch is durable the sequence moves above that object, so
7030        // the region's next id is greater than the watermark.
7031        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        // Object 2 carries the boundary past the watermark of region one.
7043        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(&region_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        // The watermark hides nothing of this prefix; the region has no
7062        // entry at or below it.
7063        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        // Two appends share object 1, which is written but reported as
7079        // failed: it exists and is not indexed.
7080        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        // Retried apart, the entries land in later objects rather than
7093        // conflicting with object 1.
7094        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        // Object 3 carries the boundary at object 2, so the failed object 1
7108        // is collected.
7109        assert_eq!(vec![2, 3], collected_object_seqs(&store).await);
7110        store.stop().await.unwrap();
7111
7112        // Only the retries replay.
7113        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        // Object 1 is in flight and the open batch is empty: the floor moves
7135        // the next sequence, which a failure of object 1 does not move back.
7136        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        // The actor exits with the command still queued and never answers
7165        // it: nothing is assigned an id any more, so the watermark alone holds.
7166        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(&region_id)
7177        );
7178
7179        // The same holds for a call that finds the actor gone.
7180        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(&region(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        // Object 1 is stored, but its create reports a failure, so the
7226        // append fails and the region has no durable entry.
7227        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        // Only that create failed.
7237        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        // The append is acknowledged and its create parks; a wait for its
7253        // entry has to wait.
7254        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        // The crash fails the waiter and the store, and the parked create
7271        // never writes its object.
7272        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        // A command sender outlives the store, so the actor keeps running and
7289        // the parked create completes on the closed hold channel instead of
7290        // being dropped with the actor.
7291        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        // Object 1 is in flight and a2 waits in the open batch under
7322        // sequence 2 when the create fails: both fail.
7323        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        // The open batch had not taken its sequence, so the next entry takes
7340        // the first id of sequence 2 again, alone.
7341        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        // Object 3 conflicts while c2 waits in the open batch: both fail.
7361        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        // The start object of the store takes the sequence before the last.
7387        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        // The batch at the last sequence is created and acknowledged, and the
7392        // store is poisoned as soon as it takes that sequence.
7393        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        // Every create is parked: once no two more sealed batches fit, the
7423        // next append is held back and counted as stalled, and further
7424        // appends wait in the channel; none is admitted.
7425        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        // A durable object frees a place, and the held-back append is
7436        // admitted; the next one is held back in its turn.
7437        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        // Stop is handled while appends are held back: the batches whose create
7451        // has not started and the appends still waiting learn of the stop.
7452        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    /// Starts `stop` and waits until it has set the stopped flag.
7473    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        // The create fails after stop began: its waiter learns of the stop.
7496        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        // Four creates are in flight, the fifth batch waits for a slot.
7509        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        // Nothing is assigned an id after stop began, so a watermark needs no
7516        // floor even while creates are in flight.
7517        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(&region(2))
7524        );
7525
7526        // The creates in flight run to completion in any order and are
7527        // acknowledged; the batch that never started learns of the stop.
7528        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        // No create was started after stop began.
7551        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        // Another writer of the same epoch takes the sequence before the
7567        // create runs.
7568        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        // A linear chain is replayed whole.
7614        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        // Object 1 was reported as failed and object 2 links past it.
7623        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        // Object 3 extends object 2, which never landed, so object 1 is the
7632        // tip although object 3 has a higher sequence.
7633        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        // Object 1 links to an object of epoch 2 at sequence 0, where an
7642        // object of epoch 1 is.
7643        assert_eq!(
7644            vec![0],
7645            chain_of(&[chain_header(0, 1, None), chain_header(1, 2, Some((0, 2))),])
7646        );
7647        // Instance A left object 2 behind after instance B, a later epoch,
7648        // acknowledged object 1: the later epoch ends the chain.
7649        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        // A missing predecessor breaks the link, whether or not it lies below
7658        // every present object, so an object that lands late below it cannot
7659        // change which links hold.
7660        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        // A predecessor at or above the object itself never holds.
7680        assert!(chain_of(&[chain_header(5, 1, Some((5, 1)))]).is_empty());
7681
7682        // A link below the reclaim boundary of the object it is judged for
7683        // holds whether or not the predecessor is present; above it a
7684        // missing predecessor still breaks the link.
7685        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        // Object 3 is incomplete with its own boundary, but object 4 only
7695        // needs it to be present.
7696        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        // Object 1 was reported as failed; object 2 reused its row sequences
7719        // and links past it.
7720        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            // Every open starts the epoch one above the sequence of its start
7736            // object, and the first restart's start object extends the chain
7737            // the second restart replays.
7738            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        // Object 2 extends object 1, which never landed.
7751        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        // Object 1 extends object 0, which is gone.
7770        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        // Instance B acknowledged object 1 before the create instance A issued
7794        // for object 2 landed.
7795        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            // An object lands at the sequence of the start object after
7846            // recovery listed the prefix.
7847            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                // Object 3 carries the boundary past the late object, which
7861                // recovery never listed.
7862                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        // On an empty prefix and after object 0 of epoch 1, another open that
7880        // recovered the same objects wrote the very start object this open
7881        // writes.
7882        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            // A prefix without segments is reclaimed up to the start object.
7892            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            // The next open counts that object and starts a later epoch.
7912            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        // Open A recovers object 0 and pauses before its start object.
7932        let recovered = recover(&io).await.unwrap();
7933        // A late object of epoch 1 lands at sequence 2, across the gap at
7934        // sequence 1, and open B, which lists it, completes.
7935        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        // Open A resumes at the sequence it recovered: the epoch follows from
7941        // the sequence it claims, so it differs from the epoch of open B.
7942        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        // No instance that starts at or below sequence 0 writes epoch 5.
7966        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        // The start object is stored, but its create reports an error.
7985        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        // The open does not move to another sequence.
7997        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        // The next open counts the stored start object and claims a later
8002        // epoch.
8003        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        // An id the store never handed out waits for the ids of the region
8075        // that were handed out below it.
8076        let waits = [wait(id(1, 1)), wait(id(1, 7))];
8077        // The other region has nothing to wait for.
8078        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        // Object 1 holds region A's id(1, 1) and object 2 region B's id(2, 1).
8107        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[&region_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        // Every entry of region A up to id(2, 1) is durable, so the wait does
8126        // not depend on the parked create of object 3.
8127        timeout(WAIT, store.wait_durable(&provider(region_a), id(2, 1)))
8128            .await
8129            .unwrap()
8130            .unwrap();
8131        // A wait for id(3, 1) itself is answered only once object 3 is
8132        // indexed; the round trip orders the check after the actor handled it.
8133        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(&region_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(&region_id)
8180        );
8181        assert!(read_entries(&store, region_id, 1).await.is_empty());
8182    }
8183
8184    /// Appends one entry in the enqueued mode with `config`, whose create
8185    /// parks, then appends a second one that the backlog threshold must
8186    /// stall. Returns the store, the parked creates, the stalled append and
8187    /// the release of the first create.
8188    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        // The stall seals the open batch so that an upload is in flight.
8213        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        // Two more appends queue behind the stalled one.
8233        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        // The upload releases the second append, whose entry reaches the
8242        // threshold again; the stall seals it so that the next upload can
8243        // release the third, and so on.
8244        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        // The create stores the object but reports a failure; the repeat
8323        // writes the same bytes under the same sequence.
8324        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        // An object store with a retry layer reports a temporary error that
8341        // outlasted its retries as persistent; the create is repeated too,
8342        // and appends are still admitted meanwhile.
8343        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        // Behind the retry layer every refusal is persistent, whatever its
8364        // kind, so only the kind tells what a repeat can settle: denied
8365        // access, a missing S3 bucket, or a write to a missing bucket of
8366        // another service cannot; an HTTP 400 or a 5xx that outlasted the
8367        // retries may pass.
8368        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        // The object exists, so the create is refused by its condition and
8385        // the read that reconciles it decides: denied access cannot pass, an
8386        // object that is gone is created by the repeat.
8387        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    /// Opens an `enqueued` store over a refusing object store, makes it
8442    /// refuse writes with `kind` and appends one entry, whose create fails,
8443    /// and waits until the store is failing.
8444    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        // Denied access and a missing bucket, as S3 reports them.
8463        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            // A later append is rejected at once and takes no entry id; the
8469            // acknowledged entry waits in the backlog and the store is not
8470            // poisoned.
8471            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            // Once the object store accepts writes again, the backlog is
8494            // uploaded under its sequence and admission resumes.
8495            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        // Each attempt starts at least the doubling delay after the previous
8524        // one started, so the 1.6 s from the first refusal hold at most five
8525        // attempts, where a repeat every 100 ms would make sixteen.
8526        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        // The attempt that succeeds ends the failing state: the other
8571        // batches are created together and everything is acknowledged in
8572        // order.
8573        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        // The sealed batches are full: the next append is held back and the
8606        // ones after it wait in their channel.
8607        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        // The object store denies access: every append waiting is rejected
8620        // instead of hanging, and so is a later one.
8621        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        // Once a create succeeds the backlog drains and the next append
8643        // takes the sequence after it.
8644        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        // Stop does not wait for the next attempt of the failing store: it
8690        // starts the creates of the whole backlog together, which succeed
8691        // now.
8692        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        // A conflict of any epoch poisons the store without reading the
8710        // existing object, since the acknowledged entries cannot move to
8711        // another sequence.
8712        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            // The append was acknowledged; the failure surfaces afterwards.
8726            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            // The acknowledged entry was dropped, which stop reports.
8736            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        // Stop began, but the actor has not received the stop command when
8756        // the create fails: the backlog is dropped and the failure is kept
8757        // for the stop that follows.
8758        store.begin_stop();
8759        release.send(false).unwrap();
8760        assert_stopped(&timeout(WAIT, seal).await.unwrap().unwrap().unwrap_err());
8761        // The lost entry cannot be certified as durable before the stop
8762        // command is handled; a durable id still is.
8763        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        // A backlog that cannot be uploaded is dropped and reported by stop,
8802        // whether the failure is temporary, persistent or permanent; none
8803        // poisons the store.
8804        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        // The open batch holds id(1, 1) under the next sequence. The id was
8832        // handed out, so it is accepted without a floor, with the watermark
8833        // capped to what is durable; an id the store never handed out under
8834        // that sequence is refused.
8835        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(&region_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        // The region has nothing durable here, so the recorded watermark
8880        // stays at zero, but its ids must still start above the watermark.
8881        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(&region_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        // Replay from the watermark sees the entry.
8903        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    /// The reclaim boundary in the header of the object `object_seq`.
8932    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        // Object 1 carries the boundary past the indexed start object, which
8947        // is collected; region one then holds the boundary at object 1.
8948        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        // A watermark moves the boundary; the next indexed object makes it
8955        // durable.
8956        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        // Region two flushed through object 2 and region one released: the
8963        // object of the release record, 4, carries the sequence after the
8964        // last indexed object, and so does the next one.
8965        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        // A region that appears later does not move the boundary back.
8974        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        // Other stores of the process count their deletes as well.
8978        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        // Object 3 carries boundary 3 but its create is reported as failed,
9000        // and the create of object 4 fails before it writes: neither boundary
9001        // permits a deletion.
9002        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        // Object 5 makes boundary 3 durable. The failed batches go once a
9010        // durable boundary passes them, whether their object exists or not.
9011        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        // The object stays indexed, and an indexing that leaves the durable
9041        // boundary where it is starts no collection.
9042        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        // The next `obsolete` starts a collection, which deletes it again.
9055        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            // Objects 0 to 5 are below the durable boundary; four are deleted
9087            // at a time.
9088            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                // The first poll queues the stop command; the actor refuses
9097                // the release of `obsolete_all` queued after it only once it
9098                // handled the stop.
9099                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            // The deletes in flight completed and no other started.
9121            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        // A read met the damaged segment of object 0, which a collection
9166        // removed before the read recorded the hole.
9167        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        // Object 1 fails while the create of object 2 is in flight.
9191        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        // Objects 3 and 4 make boundary 3 durable; object 2 is skipped while
9199        // its create is in flight.
9200        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        // The create completes and its object is deleted without another
9210        // collection.
9211        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        // A delete of an earlier instance removes object 1, below the
9239        // boundary of the tip, after the listing.
9240        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        // Object 2 is needed above the boundary: its disappearance fails
9248        // recovery.
9249        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        // The read lists objects 1 and 2 before a collection deletes object 1.
9264        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            // The read lists objects 1 and 2. The delete of object 1 removes
9307            // it, but its outcome is not reported yet.
9308            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            // A delete that succeeded skips the object; one that failed
9338            // leaves it indexed, and the read fails on the fetch.
9339            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        // Object 2 links to object 1, below its boundary. The start object
9374        // carries the recovered boundary.
9375        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        // Epoch 4 started at object 3, and object 4 carries boundary 4.
9422        // Object 2 of epoch 1 landed late; its link holds, but its epoch is
9423        // lower.
9424        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        // The start object makes boundary 4 durable, and the first
9443        // collection deletes everything below it.
9444        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    /// The release records of the object `object_seq`.
9473    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(&region_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        // The release seals object 2 without any entry; its record releases
9504        // region one through the largest id handed out for it, and it carries
9505        // the boundary region two holds at object 1.
9506        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        // The record is the segment of a reserved region, which names no
9515        // namespace.
9516        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        // Object 1 holds entries of both regions and object 2 of region one.
9530        // The release of region one is the last object written, and region
9531        // two holds the boundary at object 1.
9532        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        // Recovery seeds the watermark of region one, which nothing opens
9542        // again, and a later watermark never moves it down.
9543        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        // Once region two is flushed through object 1, no object holds a
9551        // live segment: object 5 carries the boundary past all of them.
9552        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        // Object 1 holds entries of regions one and two, object 2 the release
9570        // of region one and object 3 the drop of region three; region two
9571        // holds the boundary at object 1.
9572        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        // Every object is shorter than the tail window, so recovery reads
9581        // each one whole, once, and decodes the release records from those
9582        // bytes without a range read.
9583        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        // So many release records that their segment starts before the
9597        // window at the end of the object.
9598        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        // A segment longer than the window comes first, so the object is read
9639        // as its header and its tail, and the segment of release records, the
9640        // last one, lies inside the tail but not at its start.
9641        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        // The object is read whole, but its segment of release records is
9683        // larger than recovery keeps.
9684        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        // Object 0 holds an entry of region one and the release of region
9731        // two. Object 1 releases region three but is off the chain: it links
9732        // to an object 0 of another epoch.
9733        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        // A damaged release segment off the chain is never decoded.
9751        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        // A damaged release segment on the chain fails recovery.
9758        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        // The release fails with its create, and a tick seals the record
9769        // again although nothing is appended.
9770        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        // A `durable` stop creates the record once more, and reports it lost
9785        // when that create fails or conflicts too.
9786        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        // A region the store holds no entry of is released through zero and
9828        // a release in full reaches the largest id, each in an object of its
9829        // own; neither moves the boundary region two holds.
9830        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        // Region one written again stays readable across a restart.
9841        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        // Object 1 holds entries of both regions, object 2 the release of
9860        // region one and object 3 an entry of region two, which holds the
9861        // boundary at object 1.
9862        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        // The record outlives a restart together with the segment it covers.
9872        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        // Region two flushed through object 1 lets object 5 carry boundary
9878        // 3, below which the record and the segment go in any order: the
9879        // record is deleted, the delete of the segment fails.
9880        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        // Recovery indexes nothing below boundary 3, which the start object
9894        // carries forward, so the segment the record covered is never live
9895        // again, and the first collection deletes it.
9896        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    /// Object access whose next create stores the object but reports a
9910    /// transient failure, as a create whose response is lost.
9911    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    /// Object access whose first create lets another object land at a given
9959    /// sequence first, as a create issued before recovery would.
9960    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    /// Object access whose whole-object reads, or conditional creates, and
10001    /// on request deletes and range reads, park until the test releases
10002    /// them, counting how many are in flight. A release of false fails the
10003    /// operation with a transient error before it reaches the object store,
10004    /// or a create with denied access once `creates_refused` is set.
10005    struct ParkedIo {
10006        inner: ObjectStoreIo,
10007        parked: mpsc::UnboundedSender<(u64, oneshot::Sender<bool>)>,
10008        park_creates: bool,
10009        creates_refused: AtomicBool,
10010        /// Whether creates park yet; set once the store opened.
10011        creates_parked: AtomicBool,
10012        /// Whether deletes park.
10013        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        /// Parks until the test releases `object_seq` and returns whether the
10063        /// operation proceeds.
10064        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    /// Object access that records every range read as (sequence, offset,
10135    /// length) and, on request, reports the next conditional create as failed
10136    /// after it wrote the object, fails it with a given error before it
10137    /// writes, stores the same content before it writes, or damages the bytes
10138    /// the next range read returns; it also fails the next listing, or the
10139    /// next range read of an object at an offset, with a transient error,
10140    /// panics in the next range read of a given object, and blocks its thread
10141    /// in the next range read of a given object until the test releases it.
10142    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    /// The object whose range read blocks, the sender that reports the read
10156    /// began and the receiver that releases it.
10157    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        /// Fails the next create as a retry layer reports a temporary error
10190        /// that outlasted its retries.
10191        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                // Blocking the thread keeps an abort from cancelling the read.
10250                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    /// Measurements of costs that grow with the catalog or the prefix,
10284    /// taken through the store. They are ignored by default and print what
10285    /// they measure; run them with `--run-ignored only` and `--no-capture`,
10286    /// preferably with `--cargo-profile release`.
10287    #[allow(clippy::print_stdout)]
10288    mod measurements {
10289        use super::*;
10290
10291        /// Times every watermark update while one object holds a segment of
10292        /// each region and the regions are flushed in region id order, so
10293        /// each update leaves the next region as the first live segment of
10294        /// that object. An update is timed as the caller of `obsolete` sees
10295        /// it, which includes the round trip of its command to the actor.
10296        #[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                // The next object carries the boundary past every flushed
10313                // object.
10314                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        /// Times the steps the actor takes for each of the same watermark
10322        /// updates that depend on the catalog, recording the watermark and
10323        /// recomputing the reclaim boundary, without the round trip: the
10324        /// store is stopped first and the steps run on its catalog.
10325        #[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        /// Opens a store whose object 1 holds one segment of each of
10351        /// `regions` regions.
10352        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        /// Times 100 reads of the last ten objects of a region that holds a
10376        /// segment in every retained object, and 100 lookups of those objects
10377        /// in the catalog alone.
10378        #[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        /// Object access that counts the reads of whole objects and of byte
10435        /// ranges.
10436        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        /// The object store a measurement of recovery runs on: a fresh root
10480        /// in the bucket the `GT_S3_*` variables name when they are set, an
10481        /// in-memory store otherwise.
10482        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        /// Counts the reads of a recovery over a prefix where every dropped
10500        /// region left its release record in an object of its own, while one
10501        /// region still holds the boundary at the object of the first
10502        /// entries, and times the recovery.
10503        #[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    /// Drives every outcome of a conditional create and every failure of the
10548    /// object store access through the store, one case per fault, and checks
10549    /// that every row of the *Failure matrix* has one.
10550    mod fault_matrix {
10551        use object_store::layers::mock::MockLayerBuilder;
10552        use prometheus::Histogram;
10553
10554        use super::*;
10555
10556        /// A row of the *Failure matrix*, named by its *Situation* cell.
10557        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
10558        enum Row {
10559            /// create succeeds, or identical retry
10560            Created,
10561            /// transient I/O error, `durable` mode
10562            TransientDurable,
10563            /// an object of an earlier epoch holds the sequence, `durable` mode
10564            EarlierEpochDurable,
10565            /// transient I/O error, `enqueued` mode
10566            TransientEnqueued,
10567            /// I/O error that requires intervention, `enqueued` mode
10568            InterventionEnqueued,
10569            /// any I/O error, `enqueued` mode after `stop` began
10570            ErrorEnqueuedAfterStop,
10571            /// an object of the same or a later epoch holds the sequence, or
10572            /// any conflicting object in the `enqueued` mode
10573            Conflict,
10574            /// encoding or catalog error
10575            CatalogError,
10576            /// create succeeds at the last representable sequence
10577            LastSequence,
10578            /// the start object meets an object of an earlier epoch
10579            StartMovesPast,
10580            /// the start object meets an object of the same or a later epoch,
10581            /// or its create has an unknown outcome
10582            StartFails,
10583        }
10584
10585        /// Every row of the matrix, in the order the RFC lists them.
10586        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        /// One failure of the object store access and the row it exercises,
10601        /// when the failure is an outcome of a conditional create.
10602        struct Case {
10603            fault: &'static str,
10604            row: Option<Row>,
10605            run: fn() -> BoxFuture<'static, ()>,
10606        }
10607
10608        /// Declares [`Counters`] from one sampling expression per field.
10609        macro_rules! counters {
10610            ($($(#[$doc:meta])* $field:ident: $sample:expr,)*) => {
10611                /// The metrics the cases assert on, sampled around a case.
10612                #[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                    /// The metrics recorded since `before`.
10623                    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            /// Samples of the seal-to-durable histogram.
10634            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            /// Samples of the object size and entry histograms, one per seal.
10639            sealed_objects: sealed_objects(),
10640            /// The sums of those two histograms.
10641            object_bytes: sum_of(&METRIC_OBJECT_STORE_WAL_OBJECT_BYTES),
10642            object_entries: sum_of(&METRIC_OBJECT_STORE_WAL_OBJECT_ENTRIES),
10643            /// Samples of the `durable` acknowledgement histogram.
10644            acknowledged_appends: METRIC_OBJECT_STORE_WAL_APPEND_ACK_SECONDS.get_sample_count(),
10645            /// Objects recovery read footers from, and recoveries timed.
10646            recovered_objects: METRIC_OBJECT_STORE_WAL_RECOVERED_OBJECTS_TOTAL.get(),
10647            recoveries: METRIC_OBJECT_STORE_WAL_RECOVERY_SECONDS.get_sample_count(),
10648            /// Read streams driven to their end.
10649            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            /// No read of the matrix meets a corrupted segment.
10654            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        /// Samples of the two object shape histograms, which a sealed batch
10661        /// feeds together; a batch that fed only one of them fails the case.
10662        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        /// What a histogram of whole numbers has recorded so far. The sums
10673        /// are exact at these magnitudes, so a case states them as counts.
10674        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        /// The length of the object a batch of `entries` encodes into; the
10685        /// header and the entry ids have a fixed width, so any will do.
10686        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        /// The length of the object of one two-byte entry, which most cases seal.
10707        fn one_entry_object() -> u64 {
10708            object_bytes_of(&[(region(1), "a1")])
10709        }
10710
10711        /// The length of an object that holds one release record alone.
10712        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        /// The sum and the count of a timing histogram before a forced wait.
10721        fn timer_before(histogram: &Histogram) -> (f64, u64) {
10722            (histogram.get_sample_sum(), histogram.get_sample_count())
10723        }
10724
10725        /// One sample of a timing histogram, the wait the case forced on it,
10726        /// and what the case itself took around that wait.
10727        #[derive(Debug, Clone, Copy)]
10728        struct Timed {
10729            recorded: f64,
10730            /// The forced wait as measured, which may be far longer than asked.
10731            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            /// Asserts two observations of one timer: each lies between its own
10755            /// wait and its case's elapsed time, and the long wait outlasted the
10756            /// whole short observation, so a timer that records a constant
10757            /// fails one of them however slow the machine is.
10758            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        /// Asserts that both indexed gauges hold exactly the objects `indexed`,
10786        /// with their sizes as listed. Objects a failed create left behind are
10787        /// listed but not counted.
10788        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        /// Asserts every append failed with an error `recognise` accepts.
10805        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        /// Asserts that a read of the poisoned store fails with the terminal error.
10819        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        /// The batches are acknowledged in order and the sequence advances.
11063        /// Each create is held, the second past the whole first observation,
11064        /// so both write path timers are observed around disjoint waits.
11065        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            // The second batch holds entries of two regions, so entries and
11073            // objects differ.
11074            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            // Object 1 carries the boundary past the start object.
11112            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        /// A release seals an object of its own and returns once it is
11140        /// indexed, in both modes.
11141        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                // Object 1 carries the boundary past the start object.
11154                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        /// The release fails with the create, its sequence is not reused, and
11179        /// the release of the region again lands in the next object.
11180        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        /// The store repeats the create, and the release waits for the repeat.
11230        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        /// The object store denies access to the object of a release: the
11276        /// store becomes failing and the release fails at once with the
11277        /// retryable error; its record stays in the backlog and becomes
11278        /// durable with the next create that succeeds.
11279        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        /// Releases that arrive while the store is failing fail at once with
11333        /// the retryable error, and their record is kept coalesced: the
11334        /// ticks that pass seal nothing behind the backlog. A stop seals the
11335        /// record with the backlog, and recovery reads it back.
11336        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            // The next attempt of the backlog comes after several ticks.
11361            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        /// Another writer holds the sequence: the release fails with the
11401        /// conflict, which poisons the store, in both modes.
11402        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        /// Appends over `io` after `arm` made the create meet the very object
11444        /// it writes, and asserts it was accepted as an identical retry.
11445        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(&region_id));
11452            let response = append(&store, region_id, "a2").await.unwrap();
11453            assert_eq!(Some(&id(2, 1)), response.last_entry_ids.get(&region_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        /// The object store writes the object and loses the response, and the
11488        /// read-back that reconciles the create finds the same bytes.
11489        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        /// Object 2 is created and object 3 in flight when object 1 fails.
11511        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(&region_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            // While the repeat is parked nothing is written or indexed.
11583            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        /// Objects 1 to 3 are acknowledged when the object store starts
11666        /// denying access. The store keeps them under their sequences,
11667        /// rejects the next append and attempts object 1 alone; once it is
11668        /// created the others follow and appends are admitted again.
11669        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(&region_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        /// The object store denies access to objects 1 and 2 and still does
11759        /// when `stop` begins: stop attempts both together, drops the backlog
11760        /// when they fail and reports the refusal; the store is not poisoned.
11761        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(&region_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(&region_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        /// The epoch of the object at the sequence cannot be read: the conflict
11941        /// is counted once and the store poisons itself.
11942        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        /// The create of object 2 completes after the conflict at object 1
11972        /// poisoned the store; it is counted and timed all the same.
11973        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            // This store indexed neither object, whatever the case put into
12053            // its catalog.
12054            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            // A poisoned store hands out no entry, so the second is built now.
12077            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(&region_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        /// Writes object 0 of epoch 1 with one entry of region 1.
12114        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        /// Opens a store whose start object meets `late` at sequence 1.
12126        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        /// Asserts a construction failure at the start object: no other
12170        /// sequence was tried, and only the recovery and `create` are counted.
12171        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        /// Opens and stops a store on an empty prefix, which sets both
12274        /// process-wide indexed gauges to its one start object and moves the
12275        /// recovery and create counters, so a case that abandons a recovery
12276        /// can tell the gauges standing still from a publication of what its
12277        /// prefix holds. It runs before the case samples `Counters`.
12278        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        /// Opens a store over `io` and asserts that the recovery was
12287        /// abandoned: `open` fails with an error `recognise` accepts and
12288        /// `retry_hint`, no start object is written so the prefix still holds
12289        /// `present`, both gauges stand still and no metric moves.
12290        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        /// Writes object `object_seq` holding one entry of region 1 with
12309        /// `data`, chained to the object below it.
12310        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        /// The range read of the tail window, or of the footer, of the one
12371        /// object fails; the next open recovers it.
12372        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        /// The entry ranges of the region do not increase with the sequence,
12411        /// which the catalog refuses once every footer is read.
12412        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        /// Writes the chain 0 to 3 of epoch 1, one entry of region 1 each,
12431        /// and `WIDE_REGIONS` more regions per object when `wide`; objects 2
12432        /// and 3 carry reclaim boundary 2, so object 1 is below the boundary
12433        /// of the tip and object 2 is not.
12434        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        /// The fetch of recovery at which an object is found gone.
12458        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12459        enum RecoveryFetchStage {
12460            /// The read of a short object as a whole.
12461            WholeObject,
12462            /// The reads of the header and of the tail window of a wide object.
12463            Header,
12464            /// The separate read of the footer of a wide object.
12465            Footer,
12466        }
12467
12468        /// Waits for `count` parked operations and returns their releases.
12469        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        /// Every fetch of the listed objects parks and one object is deleted
12481        /// before one of its reads: below the boundary of the tip the object
12482        /// is ignored, at or above it the construction fails.
12483        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            // A short object parks once, a wide one twice, at its header and
12516            // at its tail window, and then once more at its footer.
12517            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        /// The delete of the start object fails at the collection that
12622        /// indexing object 1 starts; a watermark that leaves the boundary
12623        /// where it is starts the next collection, which deletes it.
12624        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        /// `stop` returns only after the delete in flight completed, and the
12684        /// catalog then holds exactly what the prefix holds, whether the
12685        /// delete succeeded or failed.
12686        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                // The first poll queues the stop command; the command queued
12697                // behind it is answered once the stop was handled, and the
12698                // delete in flight still holds the answer to the stop back.
12699                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        /// An append that alone runs past the position range of its region
12741        /// is refused; the open batch it sealed first, which holds another
12742        /// region too, is acknowledged, and the store keeps serving.
12743        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(&region_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        /// Round-trips a command through the actor once `stop` began, when
12801        /// the seal command is refused: the actor refuses the release that
12802        /// `obsolete_all` of a region without entries requests as well, which
12803        /// hides nothing of the regions a case reads.
12804        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        /// Four creates are in flight and a fifth batch waits for a slot when
12815        /// `stop` begins: the fifth is dropped; objects 4 to 2 complete while
12816        /// object 1 stays parked and nothing is acknowledged or indexed; then
12817        /// object 1 releases the four acknowledgements in order.
12818        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(&region_id)
12848                );
12849            }
12850            timeout(WAIT, stop).await.unwrap().unwrap().unwrap();
12851
12852            // Nothing is collected once stop began.
12853            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        /// Object 2 is created and object 3 in flight when object 1 fails
12884        /// after `stop` began: every batch receives the stopped error, `stop`
12885        /// waits for object 3, whose late create is an orphan.
12886        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        /// Object 2 is created and object 3 in flight when object 1 meets
12938        /// another writer after `stop` began: the store is poisoned, every
12939        /// batch receives the stopped error and `stop` still returns.
12940        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        /// Seven sealed batches leave no room for the two an admission can
12986        /// seal: the next append is held back and counted as stalled, and it
12987        /// is admitted once object 1 is durable, which records its wait.
12988        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(&region_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        /// The `enqueued` configuration under which the second append stalls
13043        /// behind the create of the first.
13044        fn stalling() -> ObjectStoreWalConfig {
13045            ObjectStoreWalConfig {
13046                max_unpersisted_bytes: ReadableSize(1),
13047                ..enqueued(manual())
13048            }
13049        }
13050
13051        /// What `stall_second_append` leaves in the metrics.
13052        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        /// The first stalled append is admitted once the upload completes;
13065        /// the entry it admits puts the backlog at the threshold again, so
13066        /// the next append stalls behind the create it seals, and the stop
13067        /// releases it with the stopped error. Both waits are timed.
13068        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(&region_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        /// The create the stalled append waits for meets another writer,
13140        /// which poisons the `enqueued` store: the append is released with
13141        /// the terminal error and its wait is recorded.
13142        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        /// The store is dropped without `stop` while the append is stalled:
13180        /// the actor's teardown releases it and records its wait, and the
13181        /// parked create never runs.
13182        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        /// Every fetch of recovery and of a read parks until the case releases
13203        /// it, which is how the recovery and read timers are read for the
13204        /// durations they recorded. Each is observed around two holds, the
13205        /// second longer than anything the first observations took.
13206        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                // Every object is short enough to be read whole, and every
13226                // such read parks.
13227                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}