LCOV - differential code coverage report
Current view: top level - pageserver/src/tenant/timeline - uninit.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 83.2 % 131 109 22 109
Current Date: 2023-10-19 02:04:12 Functions: 57.1 % 28 16 12 16
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           TLA  Line data    Source code
       1                 : use std::{collections::hash_map::Entry, fs, sync::Arc};
       2                 : 
       3                 : use anyhow::Context;
       4                 : use camino::Utf8PathBuf;
       5                 : use tracing::{error, info, info_span, warn};
       6                 : use utils::{crashsafe, fs_ext, id::TimelineId, lsn::Lsn};
       7                 : 
       8                 : use crate::{context::RequestContext, import_datadir, tenant::Tenant};
       9                 : 
      10                 : use super::Timeline;
      11                 : 
      12                 : /// A timeline with some of its files on disk, being initialized.
      13                 : /// This struct ensures the atomicity of the timeline init: it's either properly created and inserted into pageserver's memory, or
      14                 : /// its local files are removed. In the worst case of a crash, an uninit mark file is left behind, which causes the directory
      15                 : /// to be removed on next restart.
      16                 : ///
      17                 : /// The caller is responsible for proper timeline data filling before the final init.
      18                 : #[must_use]
      19                 : pub struct UninitializedTimeline<'t> {
      20                 :     pub(crate) owning_tenant: &'t Tenant,
      21                 :     timeline_id: TimelineId,
      22                 :     raw_timeline: Option<(Arc<Timeline>, TimelineUninitMark)>,
      23                 : }
      24                 : 
      25                 : impl<'t> UninitializedTimeline<'t> {
      26 CBC         966 :     pub(crate) fn new(
      27             966 :         owning_tenant: &'t Tenant,
      28             966 :         timeline_id: TimelineId,
      29             966 :         raw_timeline: Option<(Arc<Timeline>, TimelineUninitMark)>,
      30             966 :     ) -> Self {
      31             966 :         Self {
      32             966 :             owning_tenant,
      33             966 :             timeline_id,
      34             966 :             raw_timeline,
      35             966 :         }
      36             966 :     }
      37                 : 
      38                 :     /// Finish timeline creation: insert it into the Tenant's timelines map and remove the
      39                 :     /// uninit mark file.
      40                 :     ///
      41                 :     /// This function launches the flush loop if not already done.
      42                 :     ///
      43                 :     /// The caller is responsible for activating the timeline (function `.activate()`).
      44             961 :     pub(crate) fn finish_creation(mut self) -> anyhow::Result<Arc<Timeline>> {
      45             961 :         let timeline_id = self.timeline_id;
      46             961 :         let tenant_id = self.owning_tenant.tenant_id;
      47                 : 
      48             961 :         let (new_timeline, uninit_mark) = self.raw_timeline.take().with_context(|| {
      49 UBC           0 :             format!("No timeline for initalization found for {tenant_id}/{timeline_id}")
      50 CBC         961 :         })?;
      51                 : 
      52                 :         // Check that the caller initialized disk_consistent_lsn
      53             961 :         let new_disk_consistent_lsn = new_timeline.get_disk_consistent_lsn();
      54             961 :         anyhow::ensure!(
      55             961 :             new_disk_consistent_lsn.is_valid(),
      56 UBC           0 :             "new timeline {tenant_id}/{timeline_id} has invalid disk_consistent_lsn"
      57                 :         );
      58                 : 
      59 CBC         961 :         let mut timelines = self.owning_tenant.timelines.lock().unwrap();
      60             961 :         match timelines.entry(timeline_id) {
      61 UBC           0 :             Entry::Occupied(_) => anyhow::bail!(
      62               0 :                 "Found freshly initialized timeline {tenant_id}/{timeline_id} in the tenant map"
      63               0 :             ),
      64 CBC         961 :             Entry::Vacant(v) => {
      65             961 :                 uninit_mark.remove_uninit_mark().with_context(|| {
      66 UBC           0 :                     format!(
      67               0 :                         "Failed to remove uninit mark file for timeline {tenant_id}/{timeline_id}"
      68               0 :                     )
      69 CBC         961 :                 })?;
      70             961 :                 v.insert(Arc::clone(&new_timeline));
      71             961 : 
      72             961 :                 new_timeline.maybe_spawn_flush_loop();
      73             961 :             }
      74             961 :         }
      75             961 : 
      76             961 :         Ok(new_timeline)
      77             961 :     }
      78                 : 
      79                 :     /// Prepares timeline data by loading it from the basebackup archive.
      80               5 :     pub(crate) async fn import_basebackup_from_tar(
      81               5 :         self,
      82               5 :         copyin_read: &mut (impl tokio::io::AsyncRead + Send + Sync + Unpin),
      83               5 :         base_lsn: Lsn,
      84               5 :         broker_client: storage_broker::BrokerClientChannel,
      85               5 :         ctx: &RequestContext,
      86               5 :     ) -> anyhow::Result<Arc<Timeline>> {
      87               5 :         let raw_timeline = self.raw_timeline()?;
      88                 : 
      89               5 :         import_datadir::import_basebackup_from_tar(raw_timeline, copyin_read, base_lsn, ctx)
      90             811 :             .await
      91               5 :             .context("Failed to import basebackup")?;
      92                 : 
      93                 :         // Flush the new layer files to disk, before we make the timeline as available to
      94                 :         // the outside world.
      95                 :         //
      96                 :         // Flush loop needs to be spawned in order to be able to flush.
      97               3 :         raw_timeline.maybe_spawn_flush_loop();
      98               3 : 
      99               3 :         fail::fail_point!("before-checkpoint-new-timeline", |_| {
     100 UBC           0 :             anyhow::bail!("failpoint before-checkpoint-new-timeline");
     101 CBC           3 :         });
     102                 : 
     103               3 :         raw_timeline
     104               3 :             .freeze_and_flush()
     105               3 :             .await
     106               3 :             .context("Failed to flush after basebackup import")?;
     107                 : 
     108                 :         // All the data has been imported. Insert the Timeline into the tenant's timelines
     109                 :         // map and remove the uninit mark file.
     110               3 :         let tl = self.finish_creation()?;
     111               3 :         tl.activate(broker_client, None, ctx);
     112               3 :         Ok(tl)
     113               5 :     }
     114                 : 
     115             616 :     pub(crate) fn raw_timeline(&self) -> anyhow::Result<&Arc<Timeline>> {
     116             616 :         Ok(&self
     117             616 :             .raw_timeline
     118             616 :             .as_ref()
     119             616 :             .with_context(|| {
     120 UBC           0 :                 format!(
     121               0 :                     "No raw timeline {}/{} found",
     122               0 :                     self.owning_tenant.tenant_id, self.timeline_id
     123               0 :                 )
     124 CBC         616 :             })?
     125                 :             .0)
     126             616 :     }
     127                 : }
     128                 : 
     129                 : impl Drop for UninitializedTimeline<'_> {
     130                 :     fn drop(&mut self) {
     131             965 :         if let Some((_, uninit_mark)) = self.raw_timeline.take() {
     132               4 :             let _entered = info_span!("drop_uninitialized_timeline", tenant_id = %self.owning_tenant.tenant_id, timeline_id = %self.timeline_id).entered();
     133               4 :             error!("Timeline got dropped without initializing, cleaning its files");
     134               4 :             cleanup_timeline_directory(uninit_mark);
     135             961 :         }
     136             965 :     }
     137                 : }
     138                 : 
     139               5 : pub(crate) fn cleanup_timeline_directory(uninit_mark: TimelineUninitMark) {
     140               5 :     let timeline_path = &uninit_mark.timeline_path;
     141               5 :     match fs_ext::ignore_absent_files(|| fs::remove_dir_all(timeline_path)) {
     142                 :         Ok(()) => {
     143               5 :             info!("Timeline dir {timeline_path:?} removed successfully, removing the uninit mark")
     144                 :         }
     145 UBC           0 :         Err(e) => {
     146               0 :             error!("Failed to clean up uninitialized timeline directory {timeline_path:?}: {e:?}")
     147                 :         }
     148                 :     }
     149 CBC           5 :     drop(uninit_mark); // mark handles its deletion on drop, gets retained if timeline dir exists
     150               5 : }
     151                 : 
     152                 : /// An uninit mark file, created along the timeline dir to ensure the timeline either gets fully initialized and loaded into pageserver's memory,
     153                 : /// or gets removed eventually.
     154                 : ///
     155                 : /// XXX: it's important to create it near the timeline dir, not inside it to ensure timeline dir gets removed first.
     156                 : #[must_use]
     157                 : pub(crate) struct TimelineUninitMark {
     158                 :     uninit_mark_deleted: bool,
     159                 :     uninit_mark_path: Utf8PathBuf,
     160                 :     pub(crate) timeline_path: Utf8PathBuf,
     161                 : }
     162                 : 
     163                 : impl TimelineUninitMark {
     164             975 :     pub(crate) fn new(uninit_mark_path: Utf8PathBuf, timeline_path: Utf8PathBuf) -> Self {
     165             975 :         Self {
     166             975 :             uninit_mark_deleted: false,
     167             975 :             uninit_mark_path,
     168             975 :             timeline_path,
     169             975 :         }
     170             975 :     }
     171                 : 
     172             961 :     fn remove_uninit_mark(mut self) -> anyhow::Result<()> {
     173             961 :         if !self.uninit_mark_deleted {
     174             961 :             self.delete_mark_file_if_present()?;
     175 UBC           0 :         }
     176                 : 
     177 CBC         961 :         Ok(())
     178             961 :     }
     179                 : 
     180             974 :     fn delete_mark_file_if_present(&mut self) -> anyhow::Result<()> {
     181             974 :         let uninit_mark_file = &self.uninit_mark_path;
     182             974 :         let uninit_mark_parent = uninit_mark_file
     183             974 :             .parent()
     184             974 :             .with_context(|| format!("Uninit mark file {uninit_mark_file:?} has no parent"))?;
     185             974 :         fs_ext::ignore_absent_files(|| fs::remove_file(uninit_mark_file)).with_context(|| {
     186 UBC           0 :             format!("Failed to remove uninit mark file at path {uninit_mark_file:?}")
     187 CBC         974 :         })?;
     188             974 :         crashsafe::fsync(uninit_mark_parent).context("Failed to fsync uninit mark parent")?;
     189             974 :         self.uninit_mark_deleted = true;
     190             974 : 
     191             974 :         Ok(())
     192             974 :     }
     193                 : }
     194                 : 
     195                 : impl Drop for TimelineUninitMark {
     196             974 :     fn drop(&mut self) {
     197             974 :         if !self.uninit_mark_deleted {
     198              13 :             if self.timeline_path.exists() {
     199 UBC           0 :                 error!(
     200               0 :                     "Uninit mark {} is not removed, timeline {} stays uninitialized",
     201               0 :                     self.uninit_mark_path, self.timeline_path
     202               0 :                 )
     203                 :             } else {
     204                 :                 // unblock later timeline creation attempts
     205 CBC          13 :                 warn!(
     206              13 :                     "Removing intermediate uninit mark file {}",
     207              13 :                     self.uninit_mark_path
     208              13 :                 );
     209              13 :                 if let Err(e) = self.delete_mark_file_if_present() {
     210 UBC           0 :                     error!("Failed to remove the uninit mark file: {e}")
     211 CBC          13 :                 }
     212                 :             }
     213             961 :         }
     214             974 :     }
     215                 : }
        

Generated by: LCOV version 2.1-beta