LCOV - code coverage report
Current view: top level - libs/utils/src - generation.rs (source / functions) Coverage Total Hit
Test: 465a86b0c1fda0069b3e0f6c1c126e6b635a1f72.info Lines: 79.3 % 92 73
Test Date: 2024-06-25 15:47:26 Functions: 50.0 % 30 15

            Line data    Source code
       1              : use std::fmt::Debug;
       2              : 
       3              : use serde::{Deserialize, Serialize};
       4              : 
       5              : /// Tenant generations are used to provide split-brain safety and allow
       6              : /// multiple pageservers to attach the same tenant concurrently.
       7              : ///
       8              : /// See docs/rfcs/025-generation-numbers.md for detail on how generation
       9              : /// numbers are used.
      10              : #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
      11              : pub enum Generation {
      12              :     // Generations with this magic value will not add a suffix to S3 keys, and will not
      13              :     // be included in persisted index_part.json.  This value is only to be used
      14              :     // during migration from pre-generation metadata to generation-aware metadata,
      15              :     // and should eventually go away.
      16              :     //
      17              :     // A special Generation is used rather than always wrapping Generation in an Option,
      18              :     // so that code handling generations doesn't have to be aware of the legacy
      19              :     // case everywhere it touches a generation.
      20              :     None,
      21              :     // Generations with this magic value may never be used to construct S3 keys:
      22              :     // we will panic if someone tries to.  This is for Tenants in the "Broken" state,
      23              :     // so that we can satisfy their constructor with a Generation without risking
      24              :     // a code bug using it in an S3 write (broken tenants should never write)
      25              :     Broken,
      26              :     Valid(u32),
      27              : }
      28              : 
      29              : /// The Generation type represents a number associated with a Tenant, which
      30              : /// increments every time the tenant is attached to a new pageserver, or
      31              : /// an attached pageserver restarts.
      32              : ///
      33              : /// It is included as a suffix in S3 keys, as a protection against split-brain
      34              : /// scenarios where pageservers might otherwise issue conflicting writes to
      35              : /// remote storage
      36              : impl Generation {
      37              :     pub const MAX: Self = Self::Valid(u32::MAX);
      38              : 
      39              :     /// Create a new Generation that represents a legacy key format with
      40              :     /// no generation suffix
      41           62 :     pub fn none() -> Self {
      42           62 :         Self::None
      43           62 :     }
      44              : 
      45              :     // Create a new generation that will panic if you try to use get_suffix
      46            0 :     pub fn broken() -> Self {
      47            0 :         Self::Broken
      48            0 :     }
      49              : 
      50          224 :     pub const fn new(v: u32) -> Self {
      51          224 :         Self::Valid(v)
      52          224 :     }
      53              : 
      54        73517 :     pub fn is_none(&self) -> bool {
      55        73517 :         matches!(self, Self::None)
      56        73517 :     }
      57              : 
      58              :     #[track_caller]
      59         6641 :     pub fn get_suffix(&self) -> impl std::fmt::Display {
      60         6641 :         match self {
      61         6629 :             Self::Valid(v) => GenerationFileSuffix(Some(*v)),
      62           12 :             Self::None => GenerationFileSuffix(None),
      63              :             Self::Broken => {
      64            0 :                 panic!("Tried to use a broken generation");
      65              :             }
      66              :         }
      67         6641 :     }
      68              : 
      69              :     /// `suffix` is the part after "-" in a key
      70              :     ///
      71              :     /// Returns None if parsing was unsuccessful
      72           12 :     pub fn parse_suffix(suffix: &str) -> Option<Generation> {
      73           12 :         u32::from_str_radix(suffix, 16).map(Generation::new).ok()
      74           12 :     }
      75              : 
      76              :     #[track_caller]
      77           18 :     pub fn previous(&self) -> Generation {
      78           18 :         match self {
      79           18 :             Self::Valid(n) => {
      80           18 :                 if *n == 0 {
      81              :                     // Since a tenant may be upgraded from a pre-generations state, interpret the "previous" generation
      82              :                     // to 0 as being "no generation".
      83            0 :                     Self::None
      84              :                 } else {
      85           18 :                     Self::Valid(n - 1)
      86              :                 }
      87              :             }
      88            0 :             Self::None => Self::None,
      89            0 :             Self::Broken => panic!("Attempted to use a broken generation"),
      90              :         }
      91           18 :     }
      92              : 
      93              :     #[track_caller]
      94           12 :     pub fn next(&self) -> Generation {
      95           12 :         match self {
      96           12 :             Self::Valid(n) => Self::Valid(*n + 1),
      97            0 :             Self::None => Self::Valid(1),
      98            0 :             Self::Broken => panic!("Attempted to use a broken generation"),
      99              :         }
     100           12 :     }
     101              : 
     102            0 :     pub fn into(self) -> Option<u32> {
     103            0 :         if let Self::Valid(v) = self {
     104            0 :             Some(v)
     105              :         } else {
     106            0 :             None
     107              :         }
     108            0 :     }
     109              : }
     110              : 
     111              : struct GenerationFileSuffix(Option<u32>);
     112              : 
     113              : impl std::fmt::Display for GenerationFileSuffix {
     114         6641 :     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     115         6641 :         if let Some(g) = self.0 {
     116         6629 :             write!(f, "-{g:08x}")
     117              :         } else {
     118           12 :             Ok(())
     119              :         }
     120         6641 :     }
     121              : }
     122              : 
     123              : impl Serialize for Generation {
     124        35185 :     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     125        35185 :     where
     126        35185 :         S: serde::Serializer,
     127        35185 :     {
     128        35185 :         if let Self::Valid(v) = self {
     129        35185 :             v.serialize(serializer)
     130              :         } else {
     131              :             // We should never be asked to serialize a None or Broken.  Structures
     132              :             // that include an optional generation should convert None to an
     133              :             // Option<Generation>::None
     134            0 :             Err(serde::ser::Error::custom(
     135            0 :                 "Tried to serialize invalid generation ({self})",
     136            0 :             ))
     137              :         }
     138        35185 :     }
     139              : }
     140              : 
     141              : impl<'de> Deserialize<'de> for Generation {
     142           34 :     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     143           34 :     where
     144           34 :         D: serde::Deserializer<'de>,
     145           34 :     {
     146           34 :         Ok(Self::Valid(u32::deserialize(deserializer)?))
     147           34 :     }
     148              : }
     149              : 
     150              : // We intentionally do not implement Display for Generation, to reduce the
     151              : // risk of a bug where the generation is used in a format!() string directly
     152              : // instead of using get_suffix().
     153              : impl Debug for Generation {
     154         1498 :     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     155         1498 :         match self {
     156         1498 :             Self::Valid(v) => {
     157         1498 :                 write!(f, "{:08x}", v)
     158              :             }
     159              :             Self::None => {
     160            0 :                 write!(f, "<none>")
     161              :             }
     162              :             Self::Broken => {
     163            0 :                 write!(f, "<broken>")
     164              :             }
     165              :         }
     166         1498 :     }
     167              : }
     168              : 
     169              : #[cfg(test)]
     170              : mod test {
     171              :     use super::*;
     172              : 
     173              :     #[test]
     174            2 :     fn generation_gt() {
     175            2 :         // Important that a None generation compares less than a valid one, during upgrades from
     176            2 :         // pre-generation systems.
     177            2 :         assert!(Generation::none() < Generation::new(0));
     178            2 :         assert!(Generation::none() < Generation::new(1));
     179            2 :     }
     180              : 
     181              :     #[test]
     182            2 :     fn suffix_is_stable() {
     183            2 :         use std::fmt::Write as _;
     184            2 : 
     185            2 :         // the suffix must remain stable through-out the pageserver remote storage evolution and
     186            2 :         // not be changed accidentially without thinking about migration
     187            2 :         let examples = [
     188            2 :             (line!(), Generation::None, ""),
     189            2 :             (line!(), Generation::Valid(0), "-00000000"),
     190            2 :             (line!(), Generation::Valid(u32::MAX), "-ffffffff"),
     191            2 :         ];
     192            2 : 
     193            2 :         let mut s = String::new();
     194            8 :         for (line, gen, expected) in examples {
     195            6 :             s.clear();
     196            6 :             write!(s, "{}", &gen.get_suffix()).expect("string grows");
     197            6 :             assert_eq!(s, expected, "example on {line}");
     198              :         }
     199            2 :     }
     200              : }
        

Generated by: LCOV version 2.1-beta