LCOV - code coverage report
Current view: top level - pageserver/src/tenant/timeline - uninit.rs (source / functions) Coverage Total Hit
Test: a2f0f8a80fbf1089336086fa360ce27fa555cb1a.info Lines: 70.0 % 150 105
Test Date: 2024-11-20 17:59:39 Functions: 53.3 % 15 8

            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};
       6              : use utils::{fs_ext, id::TimelineId, lsn::Lsn};
       7              : 
       8              : use crate::{
       9              :     context::RequestContext,
      10              :     import_datadir,
      11              :     tenant::{CreateTimelineIdempotency, Tenant, TimelineOrOffloaded},
      12              : };
      13              : 
      14              : use super::Timeline;
      15              : 
      16              : /// A timeline with some of its files on disk, being initialized.
      17              : /// This struct ensures the atomicity of the timeline init: it's either properly created and inserted into pageserver's memory, or
      18              : /// its local files are removed.  If we crash while this class exists, then the timeline's local
      19              : /// state is cleaned up during [`Tenant::clean_up_timelines`], because the timeline's content isn't in remote storage.
      20              : ///
      21              : /// The caller is responsible for proper timeline data filling before the final init.
      22              : #[must_use]
      23              : pub struct UninitializedTimeline<'t> {
      24              :     pub(crate) owning_tenant: &'t Tenant,
      25              :     timeline_id: TimelineId,
      26              :     raw_timeline: Option<(Arc<Timeline>, TimelineCreateGuard<'t>)>,
      27              : }
      28              : 
      29              : impl<'t> UninitializedTimeline<'t> {
      30          412 :     pub(crate) fn new(
      31          412 :         owning_tenant: &'t Tenant,
      32          412 :         timeline_id: TimelineId,
      33          412 :         raw_timeline: Option<(Arc<Timeline>, TimelineCreateGuard<'t>)>,
      34          412 :     ) -> Self {
      35          412 :         Self {
      36          412 :             owning_tenant,
      37          412 :             timeline_id,
      38          412 :             raw_timeline,
      39          412 :         }
      40          412 :     }
      41              : 
      42              :     /// Finish timeline creation: insert it into the Tenant's timelines map
      43              :     ///
      44              :     /// This function launches the flush loop if not already done.
      45              :     ///
      46              :     /// The caller is responsible for activating the timeline (function `.activate()`).
      47          404 :     pub(crate) fn finish_creation(mut self) -> anyhow::Result<Arc<Timeline>> {
      48          404 :         let timeline_id = self.timeline_id;
      49          404 :         let tenant_shard_id = self.owning_tenant.tenant_shard_id;
      50          404 : 
      51          404 :         if self.raw_timeline.is_none() {
      52            0 :             return Err(anyhow::anyhow!(
      53            0 :                 "No timeline for initialization found for {tenant_shard_id}/{timeline_id}"
      54            0 :             ));
      55          404 :         }
      56          404 : 
      57          404 :         // Check that the caller initialized disk_consistent_lsn
      58          404 :         let new_disk_consistent_lsn = self
      59          404 :             .raw_timeline
      60          404 :             .as_ref()
      61          404 :             .expect("checked above")
      62          404 :             .0
      63          404 :             .get_disk_consistent_lsn();
      64          404 : 
      65          404 :         anyhow::ensure!(
      66          404 :             new_disk_consistent_lsn.is_valid(),
      67            0 :             "new timeline {tenant_shard_id}/{timeline_id} has invalid disk_consistent_lsn"
      68              :         );
      69              : 
      70          404 :         let mut timelines = self.owning_tenant.timelines.lock().unwrap();
      71          404 :         match timelines.entry(timeline_id) {
      72            0 :             Entry::Occupied(_) => anyhow::bail!(
      73            0 :                 "Found freshly initialized timeline {tenant_shard_id}/{timeline_id} in the tenant map"
      74            0 :             ),
      75          404 :             Entry::Vacant(v) => {
      76          404 :                 // after taking here should be no fallible operations, because the drop guard will not
      77          404 :                 // cleanup after and would block for example the tenant deletion
      78          404 :                 let (new_timeline, _create_guard) =
      79          404 :                     self.raw_timeline.take().expect("already checked");
      80          404 : 
      81          404 :                 v.insert(Arc::clone(&new_timeline));
      82          404 : 
      83          404 :                 new_timeline.maybe_spawn_flush_loop();
      84          404 : 
      85          404 :                 Ok(new_timeline)
      86              :             }
      87              :         }
      88          404 :     }
      89              : 
      90              :     /// Prepares timeline data by loading it from the basebackup archive.
      91            0 :     pub(crate) async fn import_basebackup_from_tar(
      92            0 :         self,
      93            0 :         tenant: Arc<Tenant>,
      94            0 :         copyin_read: &mut (impl tokio::io::AsyncRead + Send + Sync + Unpin),
      95            0 :         base_lsn: Lsn,
      96            0 :         broker_client: storage_broker::BrokerClientChannel,
      97            0 :         ctx: &RequestContext,
      98            0 :     ) -> anyhow::Result<Arc<Timeline>> {
      99            0 :         let raw_timeline = self.raw_timeline()?;
     100              : 
     101            0 :         import_datadir::import_basebackup_from_tar(raw_timeline, copyin_read, base_lsn, ctx)
     102            0 :             .await
     103            0 :             .context("Failed to import basebackup")?;
     104              : 
     105              :         // Flush the new layer files to disk, before we make the timeline as available to
     106              :         // the outside world.
     107              :         //
     108              :         // Flush loop needs to be spawned in order to be able to flush.
     109            0 :         raw_timeline.maybe_spawn_flush_loop();
     110            0 : 
     111            0 :         fail::fail_point!("before-checkpoint-new-timeline", |_| {
     112            0 :             anyhow::bail!("failpoint before-checkpoint-new-timeline");
     113            0 :         });
     114              : 
     115            0 :         raw_timeline
     116            0 :             .freeze_and_flush()
     117            0 :             .await
     118            0 :             .context("Failed to flush after basebackup import")?;
     119              : 
     120              :         // All the data has been imported. Insert the Timeline into the tenant's timelines map
     121            0 :         let tl = self.finish_creation()?;
     122            0 :         tl.activate(tenant, broker_client, None, ctx);
     123            0 :         Ok(tl)
     124            0 :     }
     125              : 
     126          184 :     pub(crate) fn raw_timeline(&self) -> anyhow::Result<&Arc<Timeline>> {
     127          184 :         Ok(&self
     128          184 :             .raw_timeline
     129          184 :             .as_ref()
     130          184 :             .with_context(|| {
     131            0 :                 format!(
     132            0 :                     "No raw timeline {}/{} found",
     133            0 :                     self.owning_tenant.tenant_shard_id, self.timeline_id
     134            0 :                 )
     135          184 :             })?
     136              :             .0)
     137          184 :     }
     138              : }
     139              : 
     140              : impl Drop for UninitializedTimeline<'_> {
     141          410 :     fn drop(&mut self) {
     142          410 :         if let Some((_, create_guard)) = self.raw_timeline.take() {
     143            6 :             let _entered = info_span!("drop_uninitialized_timeline", tenant_id = %self.owning_tenant.tenant_shard_id.tenant_id, shard_id = %self.owning_tenant.tenant_shard_id.shard_slug(), timeline_id = %self.timeline_id).entered();
     144            6 :             // This is unusual, but can happen harmlessly if the pageserver is stopped while
     145            6 :             // creating a timeline.
     146            6 :             info!("Timeline got dropped without initializing, cleaning its files");
     147            6 :             cleanup_timeline_directory(create_guard);
     148          404 :         }
     149          410 :     }
     150              : }
     151              : 
     152            6 : pub(crate) fn cleanup_timeline_directory(create_guard: TimelineCreateGuard) {
     153            6 :     let timeline_path = &create_guard.timeline_path;
     154            6 :     match fs_ext::ignore_absent_files(|| fs::remove_dir_all(timeline_path)) {
     155              :         Ok(()) => {
     156            6 :             info!("Timeline dir {timeline_path:?} removed successfully")
     157              :         }
     158            0 :         Err(e) => {
     159            0 :             error!("Failed to clean up uninitialized timeline directory {timeline_path:?}: {e:?}")
     160              :         }
     161              :     }
     162              :     // Having cleaned up, we can release this TimelineId in `[Tenant::timelines_creating]` to allow other
     163              :     // timeline creation attempts under this TimelineId to proceed
     164            6 :     drop(create_guard);
     165            6 : }
     166              : 
     167              : /// A guard for timeline creations in process: as long as this object exists, the timeline ID
     168              : /// is kept in `[Tenant::timelines_creating]` to exclude concurrent attempts to create the same timeline.
     169              : #[must_use]
     170              : pub(crate) struct TimelineCreateGuard<'t> {
     171              :     owning_tenant: &'t Tenant,
     172              :     timeline_id: TimelineId,
     173              :     pub(crate) timeline_path: Utf8PathBuf,
     174              :     pub(crate) idempotency: CreateTimelineIdempotency,
     175              : }
     176              : 
     177              : /// Errors when acquiring exclusive access to a timeline ID for creation
     178            0 : #[derive(thiserror::Error, Debug)]
     179              : pub(crate) enum TimelineExclusionError {
     180              :     #[error("Already exists")]
     181              :     AlreadyExists {
     182              :         existing: TimelineOrOffloaded,
     183              :         arg: CreateTimelineIdempotency,
     184              :     },
     185              :     #[error("Already creating")]
     186              :     AlreadyCreating,
     187              : 
     188              :     // e.g. I/O errors, or some failure deep in postgres initdb
     189              :     #[error(transparent)]
     190              :     Other(#[from] anyhow::Error),
     191              : }
     192              : 
     193              : impl<'t> TimelineCreateGuard<'t> {
     194          418 :     pub(crate) fn new(
     195          418 :         owning_tenant: &'t Tenant,
     196          418 :         timeline_id: TimelineId,
     197          418 :         timeline_path: Utf8PathBuf,
     198          418 :         idempotency: CreateTimelineIdempotency,
     199          418 :         allow_offloaded: bool,
     200          418 :     ) -> Result<Self, TimelineExclusionError> {
     201          418 :         // Lock order: this is the only place we take both locks.  During drop() we only
     202          418 :         // lock creating_timelines
     203          418 :         let timelines = owning_tenant.timelines.lock().unwrap();
     204          418 :         let timelines_offloaded = owning_tenant.timelines_offloaded.lock().unwrap();
     205          418 :         let mut creating_timelines: std::sync::MutexGuard<
     206          418 :             '_,
     207          418 :             std::collections::HashSet<TimelineId>,
     208          418 :         > = owning_tenant.timelines_creating.lock().unwrap();
     209              : 
     210          418 :         if let Some(existing) = timelines.get(&timeline_id) {
     211            2 :             return Err(TimelineExclusionError::AlreadyExists {
     212            2 :                 existing: TimelineOrOffloaded::Timeline(existing.clone()),
     213            2 :                 arg: idempotency,
     214            2 :             });
     215          416 :         }
     216          416 :         if !allow_offloaded {
     217          416 :             if let Some(existing) = timelines_offloaded.get(&timeline_id) {
     218            0 :                 return Err(TimelineExclusionError::AlreadyExists {
     219            0 :                     existing: TimelineOrOffloaded::Offloaded(existing.clone()),
     220            0 :                     arg: idempotency,
     221            0 :                 });
     222          416 :             }
     223            0 :         }
     224          416 :         if creating_timelines.contains(&timeline_id) {
     225            0 :             return Err(TimelineExclusionError::AlreadyCreating);
     226          416 :         }
     227          416 :         creating_timelines.insert(timeline_id);
     228          416 :         Ok(Self {
     229          416 :             owning_tenant,
     230          416 :             timeline_id,
     231          416 :             timeline_path,
     232          416 :             idempotency,
     233          416 :         })
     234          418 :     }
     235              : }
     236              : 
     237              : impl Drop for TimelineCreateGuard<'_> {
     238          414 :     fn drop(&mut self) {
     239          414 :         self.owning_tenant
     240          414 :             .timelines_creating
     241          414 :             .lock()
     242          414 :             .unwrap()
     243          414 :             .remove(&self.timeline_id);
     244          414 :     }
     245              : }
        

Generated by: LCOV version 2.1-beta