1use crate::disk::{self, DiskState, Ident, LINEAGE_FILE};
111use crate::lease::{new_instance_id, BucketLease, Exclusion, LeaseError, LeaseWatch};
112use crate::manifest::{
113 archive_key, crc32_hex, format_crc32, latest_key, manifest_key, object_key, parse_archive_name,
114 parse_segment_key, parse_versioned_generation, segment_key, side_key, snapshot_key, FileEntry,
115 Manifest, SegmentEntry, MANIFEST_VERSION,
116};
117use crate::segment::{complete_frames, read_new_segment, FrameCounter};
118use crate::store::BackupConfig;
119use crate::BackupError;
120use mushroomdb::{SharedDb, WalPosition};
121use object_store::buffered::BufWriter;
122use object_store::path::Path as ObjectPath;
123use object_store::{ObjectStore, PutMode, PutPayload, PutResult, UpdateVersion};
124use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
125use std::future::Future;
126use std::path::PathBuf;
127use std::pin::Pin;
128use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
129use std::sync::{Arc, Mutex};
130use std::time::{Duration, SystemTime, UNIX_EPOCH};
131use tokio::io::{AsyncReadExt, AsyncWriteExt};
132use tokio::sync::watch;
133use tokio::task::JoinHandle;
134use tokio::time::Instant;
135
136pub const SIDE_FILES: [&str; 7] = [
141 LINEAGE_FILE,
142 "commit_times.bin",
143 "roles.json",
144 "keys.json",
145 "wal.genesis",
146 "wal.genesis.v11",
148 "wal.floor",
149];
150
151const CHAIN_FILES: [&str; 3] = ["wal.genesis", "wal.genesis.v11", "wal.floor"];
155
156pub const UNHEALTHY_AFTER_FAILURES: u32 = 3;
158pub const FIRST_BACKOFF: Duration = Duration::from_secs(1);
160pub const MAX_BACKOFF: Duration = Duration::from_secs(60);
162pub const MIN_UNHEALTHY_LAG: Duration = Duration::from_secs(30);
165pub const RENEW_RETRY: Duration = Duration::from_secs(1);
167const UPLOAD_CHUNK: usize = 8 * 1024 * 1024;
169
170pub trait WalSource: Send + Sync + 'static {
175 fn durable_wal_position(&self) -> WalPosition;
176
177 fn catch_up(&self) {}
181}
182
183impl WalSource for SharedDb {
184 fn durable_wal_position(&self) -> WalPosition {
185 SharedDb::durable_wal_position(self)
186 }
187
188 fn catch_up(&self) {
189 self.refresh_if_stale_now();
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
201pub enum BackupTrigger {
202 #[default]
204 Idle,
205 Snapshot,
207 Shutdown,
210}
211
212#[derive(Debug)]
218pub struct BackupHandle {
219 pub error_count: Arc<AtomicU64>,
221 pub last_success_ts: Arc<AtomicU64>,
224 pub healthy: Arc<AtomicBool>,
229 pub lag_s: Arc<AtomicU64>,
232 pub lease_held: Arc<AtomicBool>,
236 pub reason: Arc<Mutex<Option<String>>>,
239 pub join: JoinHandle<()>,
241}
242
243#[derive(Debug, Clone)]
245pub struct BackupCounters {
246 pub error_count: Arc<AtomicU64>,
247 pub last_success_ts: Arc<AtomicU64>,
248 pub healthy: Arc<AtomicBool>,
249 pub lag_s: Arc<AtomicU64>,
250 pub lease_held: Arc<AtomicBool>,
251 pub reason: Arc<Mutex<Option<String>>>,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256struct WalKey {
257 engine: u64,
258 snapshot: Option<Ident>,
259 wal: Option<u64>,
260}
261
262impl WalKey {
263 fn of(pos: &WalPosition) -> Self {
264 Self {
265 engine: pos.generation,
266 snapshot: pos.snapshot,
267 wal: pos.wal,
268 }
269 }
270}
271
272#[derive(Debug, Clone)]
274struct Generation {
275 number: u64,
277 key: WalKey,
278 cursor: u64,
280 floor: u64,
282 chain: Vec<FileEntry>,
284 segments: Vec<SegmentEntry>,
285}
286
287#[derive(Debug, Default)]
289struct HealthState {
290 shipped: Option<(WalKey, u64)>,
292 consecutive_failures: u32,
293 deferral: Option<String>,
295 seen: VecDeque<(u64, Instant)>,
297 seen_key: Option<WalKey>,
298 base_due_since: Option<Instant>,
300 lease: Option<String>,
302 lineage: Option<String>,
305 stopped: Option<String>,
306}
307
308#[derive(Clone)]
310struct Health {
311 state: Arc<Mutex<HealthState>>,
312 healthy: Arc<AtomicBool>,
313 lag_s: Arc<AtomicU64>,
314 last_success_ts: Arc<AtomicU64>,
315 lease_held: Arc<AtomicBool>,
316 reason: Arc<Mutex<Option<String>>>,
317 limit: Duration,
318}
319
320impl Health {
321 fn lock(&self) -> std::sync::MutexGuard<'_, HealthState> {
322 self.state.lock().unwrap_or_else(|e| e.into_inner())
323 }
324
325 fn observe(&self, pos: &WalPosition, now: Instant) {
332 let mut s = self.lock();
333 let key = WalKey::of(pos);
334 let lag = match s.shipped {
335 Some((k, cursor)) if k == key => {
336 s.base_due_since = None;
337 if s.seen_key != Some(key) {
338 s.seen.clear();
339 s.seen_key = Some(key);
340 }
341 if pos.len > s.seen.back().map_or(cursor, |(l, _)| *l) {
342 s.seen.push_back((pos.len, now));
343 }
344 while s.seen.front().is_some_and(|(l, _)| *l <= cursor) {
345 s.seen.pop_front();
346 }
347 s.seen
348 .front()
349 .map(|(_, t)| now.saturating_duration_since(*t))
350 }
351 _ => {
352 let since = *s.base_due_since.get_or_insert(now);
353 Some(now.saturating_duration_since(since))
354 }
355 };
356 let lag = lag.unwrap_or(Duration::ZERO);
357 if lag.is_zero() {
358 self.last_success_ts.store(unix_now(), Ordering::Release);
359 }
360 self.lag_s.store(lag.as_secs(), Ordering::Release);
361 let reason = if let Some(r) = &s.stopped {
362 Some(r.clone())
363 } else if let Some(r) = &s.lineage {
364 Some(r.clone())
365 } else if let Some(r) = &s.lease {
366 Some(r.clone())
367 } else if s.consecutive_failures >= UNHEALTHY_AFTER_FAILURES {
368 Some(format!(
369 "{} consecutive failed backup steps",
370 s.consecutive_failures
371 ))
372 } else if lag > self.limit {
373 Some(match &s.deferral {
374 Some(d) => format!("durable data unshipped for {}s: {d}", lag.as_secs()),
375 None => format!("durable data unshipped for {}s", lag.as_secs()),
376 })
377 } else {
378 None
379 };
380 self.healthy.store(reason.is_none(), Ordering::Release);
381 *self.reason.lock().unwrap_or_else(|e| e.into_inner()) = reason;
382 }
383
384 fn stop(&self, why: String) {
386 self.lock().stopped = Some(why.clone());
387 self.healthy.store(false, Ordering::Release);
388 self.lease_held.store(false, Ordering::Release);
389 *self.reason.lock().unwrap_or_else(|e| e.into_inner()) = Some(why);
390 }
391}
392
393pub struct BackupWorker {
396 config: BackupConfig,
397 store: Arc<dyn ObjectStore>,
398 source: Arc<dyn WalSource>,
399 dir: PathBuf,
400 prefix: String,
401 error_count: Arc<AtomicU64>,
402 health: Health,
403 backoff: Duration,
404 resumed: bool,
406 next_generation: u64,
407 generations: BTreeSet<u64>,
410 current: Option<Generation>,
411 snapshot: Option<FileEntry>,
413 archives: BTreeMap<u64, Archive>,
418 base_archives: Vec<u64>,
420 side: BTreeMap<&'static str, (Ident, FileEntry)>,
423 manifest_dirty: bool,
424 last_manifest_at: Option<Instant>,
425 write_budget: Option<u64>,
428 panic_next: bool,
430 lists: u64,
432 lease: Option<Arc<BucketLease>>,
434 renewer: Option<JoinHandle<()>>,
436 tenures: u64,
438 foreign: Option<String>,
440 instance_id: String,
442 watch: LeaseWatch,
444 pending_lease: Option<PendingLease>,
447 lineage: Option<String>,
449 parent_lineage: Option<String>,
452 parent_keys: HashSet<String>,
455 refused: bool,
457 latest_version: Option<UpdateVersion>,
460 latest_target: Option<String>,
462 manifest_versions: HashMap<String, UpdateVersion>,
464 allocated: BTreeSet<u64>,
466}
467
468type LeaseAttempt = Result<Result<BucketLease, LeaseError>, tokio::time::error::Elapsed>;
469type PendingLease = Mutex<Pin<Box<dyn Future<Output = (LeaseWatch, LeaseAttempt)> + Send>>>;
472
473#[derive(Debug, Clone)]
475struct Archive {
476 len: u64,
478 entry: FileEntry,
479 verified: bool,
482}
483
484impl BackupWorker {
485 pub fn new(
488 config: BackupConfig,
489 store: Arc<dyn ObjectStore>,
490 source: Arc<dyn WalSource>,
491 store_dir: PathBuf,
492 ) -> Result<Self, BackupError> {
493 let prefix = config.location()?.prefix;
494 let limit = MIN_UNHEALTHY_LAG.max(Duration::from_millis(config.wal_interval_ms) * 10);
495 Ok(Self {
496 config,
497 store,
498 source,
499 dir: store_dir,
500 prefix,
501 error_count: Arc::new(AtomicU64::new(0)),
502 health: Health {
503 state: Arc::default(),
504 healthy: Arc::new(AtomicBool::new(true)),
505 lag_s: Arc::new(AtomicU64::new(0)),
506 last_success_ts: Arc::new(AtomicU64::new(0)),
507 lease_held: Arc::new(AtomicBool::new(false)),
508 reason: Arc::default(),
509 limit,
510 },
511 backoff: FIRST_BACKOFF,
512 resumed: false,
513 next_generation: 0,
514 generations: BTreeSet::new(),
515 current: None,
516 snapshot: None,
517 archives: BTreeMap::new(),
518 base_archives: Vec::new(),
519 side: BTreeMap::new(),
520 manifest_dirty: false,
521 last_manifest_at: None,
522 write_budget: None,
523 panic_next: false,
524 lists: 0,
525 lease: None,
526 renewer: None,
527 tenures: 0,
528 foreign: None,
529 instance_id: new_instance_id(),
530 watch: LeaseWatch::default(),
531 pending_lease: None,
532 lineage: None,
533 parent_lineage: None,
534 parent_keys: HashSet::new(),
535 refused: false,
536 latest_version: None,
537 latest_target: None,
538 manifest_versions: HashMap::new(),
539 allocated: BTreeSet::new(),
540 })
541 }
542
543 pub fn spawn(
553 config: BackupConfig,
554 store: Arc<dyn ObjectStore>,
555 source: Arc<dyn WalSource>,
556 store_dir: PathBuf,
557 snapshot_rx: watch::Receiver<BackupTrigger>,
558 ) -> Result<BackupHandle, BackupError> {
559 Ok(Self::new(config, store, source, store_dir)?.start(snapshot_rx))
560 }
561
562 pub fn start(self, snapshot_rx: watch::Receiver<BackupTrigger>) -> BackupHandle {
566 let BackupCounters {
567 error_count,
568 last_success_ts,
569 healthy,
570 lag_s,
571 lease_held,
572 reason,
573 } = self.counters();
574 let health = self.health.clone();
575 let source = Arc::clone(&self.source);
576 let cadence = Duration::from_millis(self.config.wal_interval_ms);
577 let worker = tokio::spawn(self.run(snapshot_rx));
578 let join = tokio::spawn(async move {
579 let mut tick = tokio::time::interval(cadence);
580 tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
581 tokio::pin!(worker);
582 let ended = loop {
583 tokio::select! {
584 ended = &mut worker => break ended,
585 _ = tick.tick() => {
586 let pos = position_of(&source).await;
587 health.observe(&pos, Instant::now());
588 }
589 }
590 };
591 let why = match ended {
592 Ok(()) => "stopped".to_string(),
593 Err(e) if e.is_panic() => {
594 let p = e.into_panic();
595 let msg = p
596 .downcast_ref::<&str>()
597 .map(|s| s.to_string())
598 .or_else(|| p.downcast_ref::<String>().cloned())
599 .unwrap_or_else(|| "a non-string panic".into());
600 tracing::error!(panic = %msg, "backup worker panicked; backup has stopped");
601 format!("backup worker panicked: {msg}")
602 }
603 Err(e) => format!("backup worker ended: {e}"),
604 };
605 health.stop(why);
606 });
607 BackupHandle {
608 error_count,
609 last_success_ts,
610 healthy,
611 lag_s,
612 lease_held,
613 reason,
614 join,
615 }
616 }
617
618 pub fn counters(&self) -> BackupCounters {
621 BackupCounters {
622 error_count: Arc::clone(&self.error_count),
623 last_success_ts: Arc::clone(&self.health.last_success_ts),
624 healthy: Arc::clone(&self.health.healthy),
625 lag_s: Arc::clone(&self.health.lag_s),
626 lease_held: Arc::clone(&self.health.lease_held),
627 reason: Arc::clone(&self.health.reason),
628 }
629 }
630
631 pub fn instance_id(&self) -> &str {
634 &self.instance_id
635 }
636
637 pub fn adopt_lease(&mut self, lease: Arc<BucketLease>) {
642 self.instance_id = lease.instance_id().to_string();
643 self.hold(lease, Duration::ZERO);
644 }
645
646 #[doc(hidden)]
651 pub fn fail_after_writes(&mut self, n: u64) {
652 self.write_budget = Some(n);
653 }
654
655 #[doc(hidden)]
657 pub fn panic_on_next_step(&mut self) {
658 self.panic_next = true;
659 }
660
661 #[doc(hidden)]
663 pub fn list_requests(&self) -> u64 {
664 self.lists
665 }
666
667 async fn run(mut self, mut rx: watch::Receiver<BackupTrigger>) {
668 #[cfg(test)]
669 let probe_control = self.watch.probe_control.clone();
670 let mut delay = Duration::ZERO;
671 loop {
672 let shutdown = tokio::select! {
673 _ = tokio::time::sleep(delay) => false,
674 changed = rx.changed() => is_shutdown(changed, &mut rx),
675 };
676 if shutdown || *rx.borrow() == BackupTrigger::Shutdown || rx.has_changed().is_err() {
679 break;
680 }
681 if !self.holds_lease() {
683 match until_shutdown(&mut rx, self.take_lease()).await {
684 None => break,
685 Some(Some(wait)) => {
686 delay = wait;
687 continue;
688 }
689 Some(None) => {}
690 }
691 }
692 match until_shutdown(&mut rx, self.step()).await {
696 Some(d) => delay = d,
697 None => break,
698 }
699 if self.refused {
700 break;
701 }
702 }
703 #[cfg(test)]
704 if let Some(control) = &probe_control {
705 control.shutdown.notify_one();
706 }
707 if self.refused {
708 self.release_lease().await;
711 until_shutdown(&mut rx, std::future::pending::<()>()).await;
712 return;
713 }
714 let grace = Duration::from_millis(self.config.shutdown_grace_ms);
717 let flush_deadline = Instant::now() + grace;
718 if self.pending_lease.is_some()
719 && tokio::time::timeout_at(flush_deadline, self.finish_lease_attempt(true))
720 .await
721 .is_err()
722 {
723 self.error_count.fetch_add(1, Ordering::AcqRel);
724 tracing::warn!("backup lease acquisition exceeded the shutdown grace; canceled");
725 }
726 self.pending_lease = None;
729 if self.holds_lease()
730 && Instant::now() < flush_deadline
731 && tokio::time::timeout_at(flush_deadline, self.flush())
732 .await
733 .is_err()
734 {
735 self.error_count.fetch_add(1, Ordering::AcqRel);
736 tracing::warn!(
737 grace_ms = self.config.shutdown_grace_ms,
738 "backup flush did not finish within the shutdown grace; abandoned"
739 );
740 }
741 self.release_lease().await;
742 }
743
744 fn holds_lease(&self) -> bool {
747 self.lease.as_ref().is_some_and(|l| !l.is_lost())
748 }
749
750 async fn take_lease(&mut self) -> Option<Duration> {
753 if self.lease.as_ref().is_some_and(|l| l.is_lost()) {
754 if let Some(r) = self.renewer.take() {
755 r.abort();
756 }
757 self.lease = None;
758 self.health.lease_held.store(false, Ordering::Release);
759 }
760 if self.pending_lease.is_none() {
761 let store = Arc::clone(&self.store);
762 let prefix = self.prefix.clone();
763 let instance_id = self.instance_id.clone();
764 let ttl_s = self.config.lease_ttl_s;
765 let mut watch = std::mem::take(&mut self.watch);
766 let limit = self.op_timeout() * 4;
769 self.pending_lease = Some(Mutex::new(Box::pin(async move {
770 let attempt =
771 BucketLease::acquire_as(store, &prefix, ttl_s, &instance_id, &mut watch);
772 let outcome = tokio::time::timeout(limit, attempt).await;
773 (watch, outcome)
774 })));
775 }
776 let wait = self.finish_lease_attempt(false).await;
777 let pos = self.position().await;
778 self.health.observe(&pos, Instant::now());
779 wait
780 }
781
782 async fn finish_lease_attempt(&mut self, shutting_down: bool) -> Option<Duration> {
785 let (watch, outcome) = self
786 .pending_lease
787 .as_mut()?
788 .get_mut()
789 .unwrap_or_else(|error| error.into_inner())
790 .await;
791 self.pending_lease = None;
792 self.watch = watch;
793 match outcome {
794 Ok(Ok(lease)) => {
795 let first = if shutting_down {
796 Duration::ZERO
797 } else {
798 lease.renew_interval()
799 };
800 self.hold(Arc::new(lease), first);
801 None
802 }
803 Ok(Err(LeaseError::ForeignActive { instance_id })) => {
804 if self.foreign.as_deref() != Some(instance_id.as_str()) {
805 tracing::warn!(
806 holder = %instance_id,
807 "the backup bucket's lease is held by another instance; \
808 this one uploads nothing while that lease is fresh"
809 );
810 self.foreign = Some(instance_id.clone());
811 }
812 self.health.lease_held.store(false, Ordering::Release);
813 self.health.lock().lease = Some(format!(
814 "the backup bucket's lease is held by another instance ({instance_id}); \
815 this instance uploads nothing while it is"
816 ));
817 Some(poll_interval(self.config.lease_ttl_s))
818 }
819 Ok(Err(e @ LeaseError::PreconditionsIgnored { .. })) => {
820 tracing::error!(error = %e, "backup is not started");
821 self.health.lease_held.store(false, Ordering::Release);
822 self.health.lock().lease = Some(e.to_string());
823 Some(MAX_BACKOFF)
824 }
825 Ok(Err(LeaseError::Store(e))) if self.config.lease_conditional => {
826 let why = format!(
827 "taking the bucket lease: {} (if this service rejects conditional PUT \
828 headers, set MUSHROOMDB_BACKUP_LEASE_CONDITIONAL=off)",
829 crate::redact(&e.to_string())
830 );
831 Some(self.failed(&BackupError::Lease(why)))
832 }
833 Ok(Err(e)) => Some(self.failed(&BackupError::Lease(e.to_string()))),
834 Err(_) => Some(self.failed(&BackupError::Timeout("taking the bucket lease".into()))),
835 }
836 }
837
838 fn hold(&mut self, lease: Arc<BucketLease>, first: Duration) {
842 if self.tenures > 0 {
843 self.reset_tenure();
844 }
845 self.tenures += 1;
846 self.foreign = None;
847 self.health.lock().lease = None;
848 self.health.lease_held.store(true, Ordering::Release);
849 match lease.exclusion() {
850 Exclusion::Conditional => tracing::info!(
851 instance_id = lease.instance_id(),
852 "took the backup bucket lease (conditional writes: one writer at a time)"
853 ),
854 Exclusion::BestEffort => tracing::warn!(
855 instance_id = lease.instance_id(),
856 "took the backup bucket lease BEST-EFFORT: conditional writes are off, so two \
857 instances started together may both upload and overwrite each other's objects"
858 ),
859 }
860 self.renewer = Some(tokio::spawn(renew_loop(
861 Arc::clone(&lease),
862 first,
863 self.op_timeout(),
864 self.health.clone(),
865 Arc::clone(&self.error_count),
866 )));
867 self.lease = Some(lease);
868 }
869
870 fn reset_tenure(&mut self) {
873 self.resumed = false;
874 self.lineage = None;
875 self.parent_lineage = None;
876 self.parent_keys.clear();
877 self.latest_version = None;
878 self.latest_target = None;
879 self.manifest_versions.clear();
880 self.allocated.clear();
881 self.next_generation = 0;
882 self.generations.clear();
883 self.current = None;
884 self.snapshot = None;
885 self.archives.clear();
886 self.base_archives.clear();
887 self.side.clear();
888 self.manifest_dirty = false;
889 self.last_manifest_at = None;
890 self.health.lock().shipped = None;
891 }
892
893 async fn release_lease(&mut self) {
895 self.pending_lease = None;
896 self.health.lease_held.store(false, Ordering::Release);
897 let deadline = Instant::now() + self.op_timeout();
898 let release = async {
899 if let Some(renewer) = self.renewer.take() {
900 renewer.abort();
901 let _ = renewer.await;
902 }
903 if let Some(lease) = self.lease.take() {
904 if lease.is_lost() {
906 return Ok(());
907 }
908 if let Ok(lease) = Arc::try_unwrap(lease) {
909 return lease.release().await;
910 }
911 }
912 crate::lease::release_own_record(
915 &self.store,
916 &ObjectPath::from(crate::lease::lease_key(&self.prefix)),
917 &self.instance_id,
918 )
919 .await
920 };
921 match tokio::time::timeout_at(deadline, release).await {
922 Ok(Ok(())) => tracing::info!("released the backup bucket lease"),
923 Ok(Err(e)) => {
924 tracing::warn!(error = %e, "could not release the backup bucket lease; it expires by itself")
925 }
926 Err(_) => {
927 tracing::warn!("releasing the backup bucket lease timed out; it expires by itself")
928 }
929 }
930 }
931
932 pub async fn step(&mut self) -> Duration {
936 let result = self.run_once().await;
937 let delay = match result {
938 Ok(()) => {
939 self.health.lock().consecutive_failures = 0;
940 self.backoff = FIRST_BACKOFF;
941 Duration::from_millis(self.config.wal_interval_ms)
942 }
943 Err(e) => self.failed(&e),
944 };
945 let pos = self.position().await;
946 self.health.observe(&pos, Instant::now());
947 delay
948 }
949
950 fn failed(&mut self, e: &BackupError) -> Duration {
952 #[cfg(test)]
953 if let Some(control) = &self.watch.probe_control {
954 control.failed.notify_one();
955 }
956 self.error_count.fetch_add(1, Ordering::AcqRel);
957 let failures = {
958 let mut s = self.health.lock();
959 s.consecutive_failures += 1;
960 s.consecutive_failures
961 };
962 let delay = self.backoff;
963 self.backoff = (self.backoff * 2).min(MAX_BACKOFF);
964 tracing::warn!(
965 error = %e,
966 consecutive_failures = failures,
967 retry_in_s = delay.as_secs(),
968 "backup step failed"
969 );
970 delay
971 }
972
973 pub async fn flush(&mut self) {
976 for attempt in 0..3u32 {
977 let done = match self.run_once().await {
978 Ok(()) => self.write_manifest().await,
979 other => other,
980 };
981 match done {
982 Ok(()) => {
983 let pos = self.position().await;
984 self.health.observe(&pos, Instant::now());
985 return;
986 }
987 Err(e) => {
988 self.error_count.fetch_add(1, Ordering::AcqRel);
989 tracing::warn!(error = %e, attempt, "backup flush failed");
990 if attempt < 2 {
991 tokio::time::sleep(FIRST_BACKOFF * (1 << attempt)).await;
992 }
993 }
994 }
995 }
996 }
997
998 fn defer(&mut self, why: &str) -> Result<(), BackupError> {
1000 self.health.lock().deferral = Some(why.to_string());
1001 Ok(())
1002 }
1003
1004 pub async fn run_once(&mut self) -> Result<(), BackupError> {
1009 if std::mem::take(&mut self.panic_next) {
1010 panic!("test hook: backup worker panics");
1011 }
1012 if !self.resumed && !self.resume().await? {
1013 return self.defer("the store's commit count is not known yet");
1014 }
1015 let mut disk = self.observe().await?;
1016 if disk.intent_nonempty {
1017 return self.defer("a snapshot is in progress (snapshot.intent is not empty)");
1018 }
1019 let mut pos = self.position().await;
1020 if pos.snapshot != disk.snapshot || pos.wal != disk.wal || disk.wal_len != pos.len {
1025 self.catch_up().await?;
1026 let after = self.observe().await?;
1027 if !disk.unchanged_until(&after) {
1028 return self.defer("the store's files changed while catching up");
1029 }
1030 disk = after;
1031 pos = self.position().await;
1032 }
1033 if pos.snapshot_in_progress {
1034 return self.defer("this handle is taking a snapshot");
1035 }
1036 if pos.snapshot != disk.snapshot || pos.wal != disk.wal || disk.wal_len < pos.len {
1037 return self.defer("the handle has not caught up with the files on disk");
1038 }
1039 self.health.lock().deferral = None;
1040 if self.current.as_ref().map(|g| g.key) != Some(WalKey::of(&pos)) {
1041 return self.upload_base(pos, disk).await;
1042 }
1043 self.ship_segments().await?;
1044 if self.manifest_due() {
1045 self.write_manifest().await?;
1046 }
1047 Ok(())
1048 }
1049
1050 fn manifest_due(&self) -> bool {
1051 let interval = Duration::from_secs(self.config.manifest_interval_s);
1052 self.manifest_dirty
1053 && self
1054 .last_manifest_at
1055 .is_none_or(|t| t.elapsed() >= interval)
1056 }
1057
1058 async fn observe(&self) -> Result<DiskState, BackupError> {
1059 let dir = self.dir.clone();
1060 Ok(blocking(move || disk::observe(&dir)).await?)
1061 }
1062
1063 async fn position(&self) -> WalPosition {
1064 position_of(&self.source).await
1065 }
1066
1067 async fn catch_up(&self) -> Result<(), BackupError> {
1068 let source = Arc::clone(&self.source);
1069 blocking(move || {
1070 source.catch_up();
1071 Ok(())
1072 })
1073 .await?;
1074 Ok(())
1075 }
1076
1077 async fn resume(&mut self) -> Result<bool, BackupError> {
1089 let latest = self.latest_manifest().await?;
1090 let dir = self.dir.clone();
1091 let (local, parent) =
1092 blocking(move || Ok((disk::read_lineage(&dir)?, disk::read_parent(&dir)?))).await?;
1093 let adopt = match (&local, &latest, &parent) {
1096 (Some(ours), Some(m), Some(p)) if *ours != m.lineage && p.lineage == m.lineage => {
1097 if self.latest_target.as_deref() != Some(p.manifest.as_str()) {
1098 return Err(self.refuse(format!(
1099 "this store was restored from {} of lineage {}, but the backup prefix \
1100 has moved on since (LATEST names {}): another instance has written \
1101 to it. Nothing is uploaded or deleted. Point MUSHROOMDB_BACKUP_S3_URL \
1102 at a new prefix",
1103 p.manifest,
1104 p.lineage,
1105 self.latest_target.as_deref().unwrap_or("nothing")
1106 )));
1107 }
1108 true
1109 }
1110 _ => false,
1111 };
1112 let lineage = match (local, &latest) {
1113 (Some(ours), Some(m)) if ours != m.lineage && !adopt => {
1114 return Err(self.refuse(format!(
1115 "this backup prefix holds the backups of another store (lineage {}), \
1116 not this one (lineage {ours}); nothing is uploaded or deleted. Point \
1117 MUSHROOMDB_BACKUP_S3_URL at a new prefix, or restore this store from \
1118 that prefix first",
1119 m.lineage
1120 )))
1121 }
1122 (None, Some(m)) => {
1123 return Err(self.refuse(format!(
1124 "this backup prefix holds the backups of another store (lineage {}), and \
1125 this store has never been backed up; nothing is uploaded or deleted. \
1126 Point MUSHROOMDB_BACKUP_S3_URL at a new prefix, or restore this store \
1127 from that prefix first",
1128 m.lineage
1129 )))
1130 }
1131 (Some(ours), Some(m)) => match self.position().await.frames {
1132 None => return Ok(false),
1133 Some(have) if have < m.commit_seq => {
1134 return Err(self.refuse(format!(
1135 "this store holds {have} commits, fewer than the {} of the newest \
1136 restore point in the backup prefix: it was restored from an older \
1137 backup, or rewound. Uploading would bury the newer history, so \
1138 nothing is uploaded or deleted. Point MUSHROOMDB_BACKUP_S3_URL at \
1139 a new prefix",
1140 m.commit_seq
1141 )))
1142 }
1143 Some(_) => ours,
1144 },
1145 (Some(ours), None) => ours,
1146 (None, None) => {
1147 let id = new_instance_id();
1148 let (dir, new) = (self.dir.clone(), id.clone());
1149 blocking(move || disk::create_lineage(&dir, &new)).await?;
1150 tracing::info!(lineage = %id, "this store's first backup: a new backup lineage");
1151 id
1152 }
1153 };
1154 if adopt {
1155 tracing::info!(
1156 lineage = %lineage,
1157 parent = parent.as_ref().map_or("", |p| p.lineage.as_str()),
1158 "continuing the backup prefix this store was restored from, as a new lineage"
1159 );
1160 }
1161 self.parent_lineage = parent.map(|p| p.lineage);
1162 let wal_dir = ObjectPath::from(object_key(&self.prefix, "wal"));
1163 for p in self.list(&wal_dir).await?.common_prefixes {
1164 if let Some(g) = p.filename().and_then(|s| s.parse::<u64>().ok()) {
1165 self.generations.insert(g);
1166 }
1167 }
1168 for dir in ["snapshots", "archives"] {
1169 let path = ObjectPath::from(object_key(&self.prefix, dir));
1170 for o in self.list(&path).await?.objects {
1171 if let Some(g) = parse_versioned_generation(&self.prefix, o.location.as_ref()) {
1172 self.generations.insert(g);
1173 }
1174 }
1175 }
1176 let mut highest = self.generations.last().copied();
1177 if let Some(m) = latest {
1178 highest = highest.max(Some(m.wal_generation));
1179 let files = if m.lineage == lineage {
1182 m.files
1183 } else {
1184 Vec::new()
1185 };
1186 for f in &files {
1187 if let Some(n) = parse_archive_name(&f.name) {
1188 self.archives.insert(
1189 n,
1190 Archive {
1191 len: f.size,
1192 entry: f.clone(),
1193 verified: false,
1194 },
1195 );
1196 }
1197 }
1198 }
1199 self.next_generation = highest.map_or(0, |g| g + 1);
1200 self.lineage = Some(lineage);
1201 self.resumed = true;
1202 Ok(true)
1203 }
1204
1205 fn refuse(&mut self, why: String) -> BackupError {
1207 tracing::error!(reason = %why, "backup refuses this prefix");
1208 self.health.lock().lineage = Some(why.clone());
1209 self.refused = true;
1210 BackupError::Lineage(why)
1211 }
1212
1213 fn lineage(&self) -> String {
1215 self.lineage.clone().unwrap_or_default()
1216 }
1217
1218 async fn latest_manifest(&mut self) -> Result<Option<Manifest>, BackupError> {
1221 let key = match self.get_versioned(&latest_key(&self.prefix)).await? {
1222 Some((b, v)) => {
1223 self.latest_version = Some(v);
1224 String::from_utf8(b)
1225 .map_err(|_| BackupError::Manifest("LATEST is not UTF-8".into()))?
1226 }
1227 None => {
1228 self.latest_version = None;
1229 return Ok(None);
1230 }
1231 };
1232 self.latest_target = Some(key.trim().to_string());
1233 let bytes = self.get(key.trim()).await?.ok_or_else(|| {
1234 BackupError::Manifest(format!("LATEST names {key}, which does not exist"))
1235 })?;
1236 Manifest::from_json(&bytes)
1237 .map(Some)
1238 .map_err(BackupError::Manifest)
1239 }
1240
1241 async fn upload_base(&mut self, pos: WalPosition, disk: DiskState) -> Result<(), BackupError> {
1248 let dir = self.dir.clone();
1249 let read = blocking(move || {
1250 let snapshot = disk::open_snapshot(&dir)?;
1251 let mut archives = Vec::new();
1252 for (n, len) in disk::local_archives(&dir)? {
1253 if let Ok(f) = std::fs::File::open(dir.join(format!("wal.{n}.archive"))) {
1254 archives.push((n, len, f));
1255 }
1256 }
1257 let mut chain = Vec::new();
1258 for name in CHAIN_FILES {
1259 if let Some((_, Some(bytes))) = disk::read_side(&dir, name, None)? {
1260 chain.push((name, bytes));
1261 }
1262 }
1263 Ok((snapshot, archives, chain))
1264 })
1265 .await?;
1266 let (snapshot, archives, chain) = read;
1267 let after = self.observe().await?;
1268 let pos_after = self.position().await;
1269 let opened_ident = snapshot.as_ref().map(|(_, i)| *i);
1270 if !disk.unchanged_until(&after)
1271 || opened_ident != disk.snapshot
1272 || WalKey::of(&pos_after) != WalKey::of(&pos)
1273 || pos_after.snapshot_in_progress
1274 {
1275 return self.defer("the store's files changed while a restore point was read");
1276 }
1277 let number = self.next_generation;
1280 self.next_generation += 1;
1281 self.generations.insert(number);
1282 self.allocated.insert(number);
1283
1284 let mut present = Vec::new();
1285 for (n, len, mut file) in archives {
1286 let reusable = match self.archives.get(&n) {
1287 Some(a) if a.len == len && a.verified => true,
1288 Some(a) if a.len == len => {
1289 let want = a.entry.crc32.clone();
1292 let (crc, f) =
1293 blocking(move || disk::crc32_of(&mut file).map(|c| (c, file))).await?;
1294 file = f;
1295 crc == want
1296 }
1297 _ => false,
1298 };
1299 if reusable {
1300 if let Some(a) = self.archives.get_mut(&n) {
1301 a.verified = true;
1302 }
1303 } else {
1304 let name = format!("wal.{n}.archive");
1305 let key = archive_key(&self.prefix, number, n);
1306 let entry = self.put_file(&name, &key, file, true).await?;
1307 self.archives.insert(
1308 n,
1309 Archive {
1310 len: entry.size,
1311 entry,
1312 verified: true,
1313 },
1314 );
1315 }
1316 present.push(n);
1317 }
1318 let mut floor = 0;
1319 let mut chain_entries = Vec::new();
1320 for (name, bytes) in chain {
1321 if name == "wal.floor" {
1322 floor = bytes
1323 .get(..8)
1324 .map_or(0, |b| u64::from_le_bytes(b.try_into().unwrap()));
1325 }
1326 chain_entries.push(self.put_side(name, bytes).await?);
1327 }
1328 let archive_frames: u64 = present
1329 .iter()
1330 .filter_map(|n| self.archives.get(n))
1331 .map(|a| a.entry.frames)
1332 .sum();
1333 let snapshot = match snapshot {
1334 Some((file, _)) => {
1335 let key = snapshot_key(&self.prefix, number, floor + archive_frames);
1336 Some(self.put_file("snapshot.bin", &key, file, false).await?)
1337 }
1338 None => None,
1339 };
1340 self.sync_side_files().await?;
1341 self.snapshot = snapshot;
1342 self.base_archives = present;
1343 self.current = Some(Generation {
1344 number,
1345 key: WalKey::of(&pos),
1346 cursor: 0,
1347 floor,
1348 chain: chain_entries,
1349 segments: Vec::new(),
1350 });
1351 self.record_shipped();
1352 self.manifest_dirty = true;
1353 self.ship_segments().await?;
1354 self.write_manifest().await
1355 }
1356
1357 async fn put_side(&mut self, name: &str, bytes: Vec<u8>) -> Result<FileEntry, BackupError> {
1359 let crc32 = crc32_hex(&bytes);
1360 let size = bytes.len() as u64;
1361 let key = side_key(&self.prefix, name, &crc32, size);
1362 self.put_immutable(&key, bytes, &crc32).await?;
1363 Ok(FileEntry {
1364 name: name.to_string(),
1365 key,
1366 size,
1367 crc32,
1368 frames: 0,
1369 lineage: self.lineage(),
1370 })
1371 }
1372
1373 async fn sync_side_files(&mut self) -> Result<(), BackupError> {
1377 for name in SIDE_FILES {
1378 if CHAIN_FILES.contains(&name) {
1379 continue;
1380 }
1381 let known = self.side.get(name).map(|(i, _)| *i);
1382 let dir = self.dir.clone();
1383 let read = blocking(move || disk::read_side(&dir, name, known)).await?;
1384 let Some((ident, bytes)) = read else {
1385 if self.side.remove(name).is_some() {
1386 self.manifest_dirty = true;
1387 }
1388 continue;
1389 };
1390 let Some(bytes) = bytes else {
1391 continue;
1392 };
1393 let crc32 = crc32_hex(&bytes);
1394 let size = bytes.len() as u64;
1395 let unchanged = self
1396 .side
1397 .get(name)
1398 .is_some_and(|(_, e)| e.key == side_key(&self.prefix, name, &crc32, size));
1399 let entry = if unchanged {
1400 self.side[name].1.clone()
1401 } else {
1402 self.manifest_dirty = true;
1403 self.put_side(name, bytes).await?
1404 };
1405 self.side.insert(name, (ident, entry));
1406 }
1407 Ok(())
1408 }
1409
1410 async fn ship_segments(&mut self) -> Result<(), BackupError> {
1416 loop {
1417 let Some(g) = self.current.as_ref() else {
1418 return Ok(());
1419 };
1420 let (key, cursor, number) = (g.key, g.cursor, g.number);
1421 let before = self.observe().await?;
1422 let pos = self.position().await;
1423 if before.intent_nonempty
1424 || before.snapshot != key.snapshot
1425 || before.wal != key.wal
1426 || WalKey::of(&pos) != key
1427 {
1428 return Ok(());
1429 }
1430 if pos.len <= cursor {
1431 return Ok(());
1432 }
1433 let wal = self.dir.join("wal.bin");
1434 let len = pos.len;
1435 let read = blocking(move || read_new_segment(&wal, cursor, len)).await?;
1436 let after = self.observe().await?;
1437 if !before.unchanged_until(&after) || WalKey::of(&self.position().await) != key {
1438 return Ok(());
1439 }
1440 let Some((start, end, bytes)) = read else {
1441 return Ok(());
1442 };
1443 let (_, frames) = complete_frames(&bytes);
1444 let seg_key = segment_key(&self.prefix, number, start, end);
1445 let crc32 = crc32_hex(&bytes);
1446 self.put_immutable(&seg_key, bytes, &crc32).await?;
1447 let lineage = self.lineage();
1448 if let Some(g) = self.current.as_mut() {
1449 g.cursor = end;
1450 g.segments.push(SegmentEntry {
1451 key: seg_key,
1452 crc32,
1453 frames,
1454 lineage,
1455 });
1456 }
1457 self.record_shipped();
1458 self.manifest_dirty = true;
1459 }
1460 }
1461
1462 fn record_shipped(&self) {
1463 if let Some(g) = &self.current {
1464 self.health.lock().shipped = Some((g.key, g.cursor));
1465 }
1466 }
1467
1468 async fn write_manifest(&mut self) -> Result<(), BackupError> {
1473 self.sync_side_files().await?;
1474 let Some(generation) = self.current.as_ref() else {
1475 return Ok(());
1476 };
1477 let mut files = Vec::new();
1478 files.extend(self.snapshot.clone());
1479 for n in &self.base_archives {
1480 if let Some(a) = self.archives.get(n) {
1481 files.push(a.entry.clone());
1482 }
1483 }
1484 files.extend(generation.chain.iter().cloned());
1485 files.extend(self.side.values().map(|(_, e)| e.clone()));
1486 let commit_seq = generation.floor
1489 + files.iter().map(|f| f.frames).sum::<u64>()
1490 + generation.segments.iter().map(|s| s.frames).sum::<u64>();
1491 let manifest = Manifest {
1492 version: MANIFEST_VERSION,
1493 lineage: self.lineage(),
1494 created_at: now_rfc3339(),
1495 commit_seq,
1496 oldest_commit_seq: generation.floor,
1497 files,
1498 wal_generation: generation.number,
1499 segments: generation.segments.clone(),
1500 };
1501 manifest.verify_lineage().map_err(BackupError::Manifest)?;
1503 let key = manifest_key(&self.prefix, manifest.commit_seq);
1504 if self.parent_keys.contains(&key) || !self.put_manifest(&key, manifest.to_json()).await? {
1505 return Ok(());
1509 }
1510 self.put_latest(&key).await?;
1511 self.manifest_dirty = false;
1512 self.last_manifest_at = Some(Instant::now());
1513 if self.config.retain_n > 0 {
1514 self.apply_retention(&key).await?;
1515 }
1516 Ok(())
1517 }
1518
1519 async fn apply_retention(&mut self, just_written: &str) -> Result<(), BackupError> {
1537 let dir = ObjectPath::from(object_key(&self.prefix, "manifest"));
1538 let mut keys: Vec<String> = self
1539 .list(&dir)
1540 .await?
1541 .objects
1542 .into_iter()
1543 .map(|o| o.location.to_string())
1544 .filter(|k| k.ends_with(".json"))
1545 .collect();
1546 keys.sort();
1547 let ours = self.lineage();
1548 let mut mine: Vec<(String, Manifest)> = Vec::new();
1549 let mut foreign: HashSet<String> = HashSet::new();
1552 for k in keys {
1553 let Some(bytes) = self.get(&k).await? else {
1554 continue;
1555 };
1556 match Manifest::from_json(&bytes) {
1557 Ok(m) if m.lineage == ours => mine.push((k, m)),
1558 Ok(m) => {
1559 foreign.extend(m.files.into_iter().map(|f| f.key));
1560 foreign.extend(m.segments.into_iter().map(|s| s.key));
1561 }
1562 Err(_) => {}
1563 }
1564 }
1565 if mine.len() <= self.config.retain_n {
1566 return Ok(());
1567 }
1568 let newest_start = mine.len() - self.config.retain_n;
1569 let protected = |k: &str| k == just_written || self.latest_target.as_deref() == Some(k);
1570 let (mut kept, mut dropped) = (Vec::new(), Vec::new());
1571 for (i, (k, m)) in mine.into_iter().enumerate() {
1572 if i >= newest_start || protected(&k) {
1573 kept.push((k, m));
1574 } else {
1575 dropped.push((k, m));
1576 }
1577 }
1578 if dropped.is_empty() {
1579 return Ok(());
1580 }
1581 let mut owned: BTreeSet<u64> = self.allocated.clone();
1582 owned.extend(kept.iter().chain(&dropped).map(|(_, m)| m.wal_generation));
1583 for (k, _) in &dropped {
1584 self.delete(k).await?;
1585 }
1586 let mut referenced: HashSet<String> = HashSet::new();
1587 let mut oldest_kept = u64::MAX;
1588 for (_, m) in &kept {
1589 oldest_kept = oldest_kept.min(m.wal_generation);
1590 referenced.extend(m.files.iter().map(|f| f.key.clone()));
1591 referenced.extend(m.segments.iter().map(|s| s.key.clone()));
1592 }
1593 let mut doomed = Vec::new();
1594 for top in ["snapshots", "archives"] {
1595 for k in self.list_all(&object_key(&self.prefix, top)).await? {
1596 let ours = parse_versioned_generation(&self.prefix, &k)
1597 .is_some_and(|g| owned.contains(&g));
1598 if ours && !referenced.contains(&k) {
1599 doomed.push(k);
1600 }
1601 }
1602 }
1603 let side_prefix = object_key(&self.prefix, "side/");
1604 let side: BTreeSet<String> = dropped
1605 .iter()
1606 .flat_map(|(_, m)| m.files.iter().map(|f| f.key.clone()))
1607 .filter(|k| k.starts_with(&side_prefix) && !referenced.contains(k))
1608 .collect();
1609 doomed.extend(side);
1610 let current = self.current.as_ref().map(|g| g.number);
1611 let retiring: Vec<u64> = owned
1612 .iter()
1613 .copied()
1614 .filter(|g| *g < oldest_kept && Some(*g) != current)
1615 .collect();
1616 for g in &retiring {
1617 let prefix = object_key(&self.prefix, &format!("wal/{g}"));
1618 for k in self.list_all(&prefix).await? {
1619 if parse_segment_key(&self.prefix, &k).is_some_and(|(sg, _, _)| sg == *g)
1620 && !referenced.contains(&k)
1621 {
1622 doomed.push(k);
1623 }
1624 }
1625 }
1626 for k in doomed {
1627 if !foreign.contains(&k) {
1628 self.delete(&k).await?;
1629 }
1630 }
1631 for g in retiring {
1632 self.generations.remove(&g);
1633 }
1634 let keep: HashSet<&String> = referenced.iter().collect();
1635 self.archives.retain(|_, a| keep.contains(&a.entry.key));
1636 Ok(())
1637 }
1638
1639 async fn list_all(&mut self, prefix: &str) -> Result<Vec<String>, BackupError> {
1641 let mut out = Vec::new();
1642 let mut dirs = vec![ObjectPath::from(prefix)];
1643 while let Some(d) = dirs.pop() {
1644 let r = self.list(&d).await?;
1645 out.extend(r.objects.into_iter().map(|o| o.location.to_string()));
1646 dirs.extend(r.common_prefixes);
1647 }
1648 Ok(out)
1649 }
1650
1651 fn op_timeout(&self) -> Duration {
1654 Duration::from_millis(self.config.op_timeout_ms)
1655 }
1656
1657 async fn deadline<T>(
1658 &self,
1659 what: impl FnOnce() -> String,
1660 fut: impl Future<Output = Result<T, BackupError>>,
1661 ) -> Result<T, BackupError> {
1662 match tokio::time::timeout(self.op_timeout(), fut).await {
1663 Ok(r) => r,
1664 Err(_) => Err(BackupError::Timeout(what())),
1665 }
1666 }
1667
1668 fn spend_write(&mut self) -> Result<(), BackupError> {
1670 match self.write_budget.as_mut() {
1671 Some(0) => Err(BackupError::Inconsistent(
1672 "test hook: the process is gone".into(),
1673 )),
1674 Some(n) => {
1675 *n -= 1;
1676 Ok(())
1677 }
1678 None => Ok(()),
1679 }
1680 }
1681
1682 async fn fenced<T>(
1685 &self,
1686 what: impl FnOnce() -> String,
1687 fut: impl Future<Output = Result<T, BackupError>>,
1688 ) -> Result<T, BackupError> {
1689 fence(self.lease.as_deref(), self.deadline(what, fut)).await
1690 }
1691
1692 async fn put_mode(
1696 &mut self,
1697 key: &str,
1698 payload: PutPayload,
1699 mode: PutMode,
1700 ) -> Result<Result<PutResult, object_store::Error>, BackupError> {
1701 self.spend_write()?;
1702 let path = ObjectPath::from(key);
1703 let store = Arc::clone(&self.store);
1704 self.fenced(|| format!("PUT {key}"), async move {
1705 Ok(store.put_opts(&path, payload, mode.into()).await)
1706 })
1707 .await
1708 }
1709
1710 async fn put_immutable(
1714 &mut self,
1715 key: &str,
1716 bytes: Vec<u8>,
1717 crc32: &str,
1718 ) -> Result<(), BackupError> {
1719 let payload = PutPayload::from(bytes);
1720 match self.put_mode(key, payload.clone(), PutMode::Create).await? {
1721 Ok(_) => Ok(()),
1722 Err(object_store::Error::NotImplemented) => {
1723 self.put_mode(key, payload, PutMode::Overwrite).await??;
1724 Ok(())
1725 }
1726 Err(object_store::Error::AlreadyExists { .. }) => match self.get(key).await? {
1727 Some(b) if crc32_hex(&b) == crc32 => Ok(()),
1728 _ => Err(self.conflict(format!("{key} already exists with other content"))),
1729 },
1730 Err(e) => Err(e.into()),
1731 }
1732 }
1733
1734 async fn put_manifest(&mut self, key: &str, bytes: Vec<u8>) -> Result<bool, BackupError> {
1740 if self.parent_lineage.is_some() && !self.manifest_versions.contains_key(key) {
1742 if let Some((b, _)) = self.get_versioned(key).await? {
1743 if Manifest::from_json(&b).ok().map(|m| m.lineage) == self.parent_lineage {
1744 tracing::info!(
1745 key,
1746 "the parent lineage's restore point holds this key; not written"
1747 );
1748 self.parent_keys.insert(key.to_string());
1749 return Ok(false);
1750 }
1751 }
1752 }
1753 let payload = PutPayload::from(bytes);
1754 let mode = match self.manifest_versions.get(key) {
1755 Some(v) => PutMode::Update(v.clone()),
1756 None => PutMode::Create,
1757 };
1758 let written = match self.put_mode(key, payload.clone(), mode).await? {
1759 Err(object_store::Error::AlreadyExists { .. }) => {
1760 let existing = self.get_versioned(key).await?;
1761 let owner = existing
1762 .as_ref()
1763 .and_then(|(b, _)| Manifest::from_json(b).ok())
1764 .map(|m| m.lineage);
1765 let ours = owner.as_deref() == Some(self.lineage().as_str());
1766 if owner.is_some() && owner == self.parent_lineage {
1767 tracing::info!(
1768 key,
1769 "the parent lineage's restore point holds this key; not written"
1770 );
1771 self.parent_keys.insert(key.to_string());
1772 return Ok(false);
1773 }
1774 match existing {
1775 Some((_, v)) if ours => {
1776 self.put_mode(key, payload.clone(), PutMode::Update(v))
1777 .await?
1778 }
1779 _ => return Err(self.conflict(format!("{key} is another lineage's manifest"))),
1780 }
1781 }
1782 other => other,
1783 };
1784 let r = match written {
1785 Ok(r) => r,
1786 Err(object_store::Error::NotImplemented) => {
1787 self.put_mode(key, payload, PutMode::Overwrite).await??
1788 }
1789 Err(object_store::Error::Precondition { .. })
1790 | Err(object_store::Error::AlreadyExists { .. }) => {
1791 return Err(self.conflict(format!("{key} changed since this worker wrote it")))
1792 }
1793 Err(e) => return Err(e.into()),
1794 };
1795 self.manifest_versions
1796 .insert(key.to_string(), version_of(r));
1797 Ok(true)
1798 }
1799
1800 async fn put_latest(&mut self, target: &str) -> Result<(), BackupError> {
1804 let key = latest_key(&self.prefix);
1805 let payload = PutPayload::from(target.as_bytes().to_vec());
1806 let mode = match &self.latest_version {
1807 Some(v) => PutMode::Update(v.clone()),
1808 None => PutMode::Create,
1809 };
1810 let r = match self.put_mode(&key, payload.clone(), mode).await? {
1811 Ok(r) => r,
1812 Err(object_store::Error::NotImplemented) => {
1813 self.put_mode(&key, payload, PutMode::Overwrite).await??
1814 }
1815 Err(object_store::Error::Precondition { .. })
1816 | Err(object_store::Error::AlreadyExists { .. })
1817 | Err(object_store::Error::NotFound { .. }) => {
1818 return Err(self.conflict("LATEST changed since this worker last wrote it".into()))
1819 }
1820 Err(e) => return Err(e.into()),
1821 };
1822 self.latest_version = Some(version_of(r));
1823 self.latest_target = Some(target.to_string());
1824 Ok(())
1825 }
1826
1827 fn conflict(&mut self, what: String) -> BackupError {
1831 let why = format!("another writer is using this backup prefix: {what}");
1832 tracing::warn!(reason = %why, "backup write refused by the bucket");
1833 if let Some(l) = &self.lease {
1834 l.mark_lost();
1835 self.health.lock().lease = Some(why.clone());
1836 }
1837 BackupError::Conflict(why)
1838 }
1839
1840 async fn put_file(
1849 &mut self,
1850 name: &str,
1851 key: &str,
1852 file: std::fs::File,
1853 count_frames: bool,
1854 ) -> Result<FileEntry, BackupError> {
1855 if self.head(key).await? {
1859 return Err(self.conflict(format!("{key} already exists")));
1860 }
1861 self.spend_write()?;
1862 let mut file = tokio::fs::File::from_std(file);
1863 let mut writer = BufWriter::new(Arc::clone(&self.store), ObjectPath::from(key));
1864 let mut hasher = crc32fast::Hasher::new();
1865 let mut counter = FrameCounter::default();
1866 let mut size = 0u64;
1867 let mut buf = vec![0u8; UPLOAD_CHUNK];
1868 let timeout = self.op_timeout();
1869 let lease = self.lease.clone();
1870 let written: Result<(), BackupError> = async {
1871 loop {
1872 let n = file.read(&mut buf).await?;
1873 if n == 0 {
1874 return Ok(());
1875 }
1876 hasher.update(&buf[..n]);
1877 if count_frames {
1878 counter.feed(&buf[..n]);
1879 }
1880 size += n as u64;
1881 fence(lease.as_deref(), async {
1882 tokio::time::timeout(timeout, writer.write_all(&buf[..n]))
1883 .await
1884 .map_err(|_| BackupError::Timeout(format!("upload of {key}")))??;
1885 Ok(())
1886 })
1887 .await?;
1888 }
1889 }
1890 .await;
1891 if let Err(e) = written {
1892 let _ = tokio::time::timeout(timeout, writer.abort()).await;
1893 return Err(e);
1894 }
1895 fence(lease.as_deref(), async {
1896 tokio::time::timeout(timeout, writer.shutdown())
1897 .await
1898 .map_err(|_| BackupError::Timeout(format!("upload of {key}")))??;
1899 Ok(())
1900 })
1901 .await?;
1902 Ok(FileEntry {
1903 name: name.to_string(),
1904 key: key.to_string(),
1905 size,
1906 crc32: format_crc32(hasher.finalize()),
1907 frames: counter.frames(),
1908 lineage: self.lineage(),
1909 })
1910 }
1911
1912 async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, BackupError> {
1913 let path = ObjectPath::from(key);
1914 let store = Arc::clone(&self.store);
1915 self.deadline(|| format!("GET {key}"), async move {
1916 match store.get(&path).await {
1917 Ok(r) => Ok(Some(r.bytes().await?.to_vec())),
1918 Err(object_store::Error::NotFound { .. }) => Ok(None),
1919 Err(e) => Err(e.into()),
1920 }
1921 })
1922 .await
1923 }
1924
1925 async fn get_versioned(
1926 &self,
1927 key: &str,
1928 ) -> Result<Option<(Vec<u8>, UpdateVersion)>, BackupError> {
1929 let path = ObjectPath::from(key);
1930 let store = Arc::clone(&self.store);
1931 self.deadline(|| format!("GET {key}"), async move {
1932 match store.get(&path).await {
1933 Ok(r) => {
1934 let v = UpdateVersion {
1935 e_tag: r.meta.e_tag.clone(),
1936 version: r.meta.version.clone(),
1937 };
1938 Ok(Some((r.bytes().await?.to_vec(), v)))
1939 }
1940 Err(object_store::Error::NotFound { .. }) => Ok(None),
1941 Err(e) => Err(e.into()),
1942 }
1943 })
1944 .await
1945 }
1946
1947 async fn head(&self, key: &str) -> Result<bool, BackupError> {
1948 let path = ObjectPath::from(key);
1949 let store = Arc::clone(&self.store);
1950 self.deadline(|| format!("HEAD {key}"), async move {
1951 match store.head(&path).await {
1952 Ok(_) => Ok(true),
1953 Err(object_store::Error::NotFound { .. }) => Ok(false),
1954 Err(e) => Err(e.into()),
1955 }
1956 })
1957 .await
1958 }
1959
1960 async fn delete(&mut self, key: &str) -> Result<(), BackupError> {
1961 self.spend_write()?;
1962 let path = ObjectPath::from(key);
1963 let store = Arc::clone(&self.store);
1964 self.fenced(|| format!("DELETE {key}"), async move {
1965 match store.delete(&path).await {
1966 Ok(()) | Err(object_store::Error::NotFound { .. }) => Ok(()),
1967 Err(e) => Err(e.into()),
1968 }
1969 })
1970 .await
1971 }
1972
1973 async fn list(&mut self, dir: &ObjectPath) -> Result<object_store::ListResult, BackupError> {
1974 self.lists += 1;
1975 let store = Arc::clone(&self.store);
1976 let d = dir.clone();
1977 self.deadline(|| format!("LIST {dir}"), async move {
1978 Ok(store.list_with_delimiter(Some(&d)).await?)
1979 })
1980 .await
1981 }
1982}
1983
1984async fn position_of(source: &Arc<dyn WalSource>) -> WalPosition {
1987 let s = Arc::clone(source);
1988 match tokio::task::spawn_blocking(move || s.durable_wal_position()).await {
1989 Ok(p) => p,
1990 Err(_) => source.durable_wal_position(),
1993 }
1994}
1995
1996async fn until_shutdown<F: Future>(
1998 rx: &mut watch::Receiver<BackupTrigger>,
1999 fut: F,
2000) -> Option<F::Output> {
2001 tokio::pin!(fut);
2002 loop {
2003 tokio::select! {
2004 out = &mut fut => return Some(out),
2005 changed = rx.changed() => {
2006 if is_shutdown(changed, rx) {
2007 return None;
2008 }
2009 }
2010 }
2011 }
2012}
2013
2014fn poll_interval(ttl_s: u64) -> Duration {
2016 Duration::from_millis(ttl_s.max(1) * 1000 / 3)
2017}
2018
2019async fn fence<T>(
2024 lease: Option<&BucketLease>,
2025 fut: impl Future<Output = Result<T, BackupError>>,
2026) -> Result<T, BackupError> {
2027 let Some(lease) = lease else {
2028 return fut.await;
2029 };
2030 let fenced = || {
2031 Err(BackupError::Lease(if lease.is_lost() {
2032 "lost to another instance; this one no longer writes to the bucket".into()
2033 } else {
2034 "not renewed in time; writes are stopped until it is".into()
2035 }))
2036 };
2037 tokio::pin!(fut);
2038 let mut wake: Option<std::pin::Pin<Box<tokio::time::Sleep>>> = None;
2039 std::future::poll_fn(|cx| {
2040 let Some(deadline) = lease.write_deadline() else {
2041 return std::task::Poll::Ready(fenced());
2042 };
2043 if let std::task::Poll::Ready(out) = fut.as_mut().poll(cx) {
2044 return std::task::Poll::Ready(out);
2045 }
2046 let at = deadline.min(Instant::now() + Duration::from_secs(1));
2049 let sleep = wake.get_or_insert_with(|| Box::pin(tokio::time::sleep_until(at)));
2050 if sleep.deadline() != at {
2051 sleep.as_mut().reset(at);
2052 }
2053 if sleep.as_mut().poll(cx).is_ready() {
2054 sleep
2055 .as_mut()
2056 .reset(Instant::now() + Duration::from_millis(1));
2057 cx.waker().wake_by_ref();
2058 }
2059 std::task::Poll::Pending
2060 })
2061 .await
2062}
2063
2064async fn renew_loop(
2067 lease: Arc<BucketLease>,
2068 first: Duration,
2069 op_timeout: Duration,
2070 health: Health,
2071 errors: Arc<AtomicU64>,
2072) {
2073 let every = lease.renew_interval().max(Duration::from_millis(1));
2074 let mut wait = first;
2075 loop {
2076 tokio::time::sleep(wait).await;
2077 let why = match tokio::time::timeout(op_timeout, lease.renew()).await {
2078 Ok(Ok(())) => {
2079 health.lock().lease = None;
2080 health.lease_held.store(true, Ordering::Release);
2081 wait = every;
2082 continue;
2083 }
2084 Ok(Err(e @ LeaseError::Lost { .. })) => {
2085 tracing::warn!(error = %e, "lost the backup bucket lease; uploads stop");
2086 health.lease_held.store(false, Ordering::Release);
2087 health.lock().lease = Some(format!(
2088 "{e}; this instance uploads nothing while another holds it"
2089 ));
2090 return;
2091 }
2092 Ok(Err(e)) => e.to_string(),
2093 Err(_) => "the renewal timed out".to_string(),
2094 };
2095 errors.fetch_add(1, Ordering::AcqRel);
2096 tracing::warn!(error = %why, "backup bucket lease renewal failed; retrying");
2097 if !lease.may_write() {
2098 health.lease_held.store(false, Ordering::Release);
2099 health.lock().lease = Some(format!(
2100 "the bucket lease could not be renewed ({why}); uploads are stopped until it is"
2101 ));
2102 }
2103 wait = RENEW_RETRY.min(every);
2104 }
2105}
2106
2107fn version_of(r: PutResult) -> UpdateVersion {
2108 UpdateVersion {
2109 e_tag: r.e_tag,
2110 version: r.version,
2111 }
2112}
2113
2114fn is_shutdown(
2115 changed: Result<(), watch::error::RecvError>,
2116 rx: &mut watch::Receiver<BackupTrigger>,
2117) -> bool {
2118 match changed {
2119 Err(_) => true,
2122 Ok(()) => *rx.borrow_and_update() == BackupTrigger::Shutdown,
2123 }
2124}
2125
2126async fn blocking<T: Send + 'static>(
2128 f: impl FnOnce() -> std::io::Result<T> + Send + 'static,
2129) -> std::io::Result<T> {
2130 tokio::task::spawn_blocking(f)
2131 .await
2132 .unwrap_or_else(|e| Err(std::io::Error::other(format!("blocking read failed: {e}"))))
2133}
2134
2135fn unix_now() -> u64 {
2136 SystemTime::now()
2137 .duration_since(UNIX_EPOCH)
2138 .map(|d| d.as_secs())
2139 .unwrap_or(0)
2140}
2141
2142fn now_rfc3339() -> String {
2143 time::OffsetDateTime::now_utc()
2144 .format(&time::format_description::well_known::Rfc3339)
2145 .unwrap_or_default()
2146}
2147
2148#[cfg(test)]
2149mod tests;