genswap 0.1.0

Generation-tracked ArcSwap wrapper for 5-1600x faster cached reads in read-heavy workloads
Documentation
use arc_swap::ArcSwap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;

use crate::CachedReader;

/// The producer-side type that holds the data and generation counter.
///
/// `GenSwap<T>` wraps an `ArcSwap<T>` with an atomic generation counter
/// that increments on every update. This allows consumers to cache `Arc<T>` references
/// locally and only reload when the generation changes.
///
/// # Memory Ordering
///
/// The implementation uses Release ordering when storing data and incrementing
/// the generation counter, and Acquire ordering when loading the generation.
/// This ensures that when a consumer observes a new generation, all writes to
/// the data are visible.
///
/// # Thread Safety
///
/// `GenSwap<T>` is `Send + Sync` where `T: Send + Sync`.
///
/// # Example
///
/// ```
/// use genswap::GenSwap;
///
/// let swap = GenSwap::new(42u64);
/// assert_eq!(swap.generation(), 0);
///
/// swap.update(100);
/// assert_eq!(swap.generation(), 1);
/// ```
pub struct GenSwap<T> {
    data: ArcSwap<T>,
    generation: AtomicU64,
}

impl<T> GenSwap<T> {
    /// Creates a new `GenSwap` with generation starting at 0.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    ///
    /// let swap = GenSwap::new(String::from("hello"));
    /// assert_eq!(swap.generation(), 0);
    /// ```
    #[inline]
    pub fn new(initial: T) -> Self {
        Self {
            data: ArcSwap::from_pointee(initial),
            generation: AtomicU64::new(0),
        }
    }

    /// Creates a `GenSwap` from an existing `Arc<T>`.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    /// use std::sync::Arc;
    ///
    /// let arc = Arc::new(42u64);
    /// let swap = GenSwap::new_from_arc(arc);
    /// assert_eq!(swap.generation(), 0);
    /// ```
    #[inline]
    pub fn new_from_arc(initial: Arc<T>) -> Self {
        Self {
            data: ArcSwap::from(initial),
            generation: AtomicU64::new(0),
        }
    }

    /// Stores new data and increments the generation counter.
    ///
    /// The data is stored first (using `ArcSwap::store`), then the generation
    /// is incremented with Release ordering. This ensures that any thread
    /// observing the new generation will also see the new data.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    ///
    /// let swap = GenSwap::new(1);
    /// swap.update(2);
    /// assert_eq!(swap.generation(), 1);
    /// swap.update(3);
    /// assert_eq!(swap.generation(), 2);
    /// ```
    #[inline]
    pub fn update(&self, new_value: T) {
        self.update_arc(Arc::new(new_value));
    }

    /// Stores new data from an `Arc<T>` and increments the generation counter.
    ///
    /// This is identical to `update` but takes an `Arc<T>` directly, avoiding
    /// an extra allocation if you already have an `Arc`.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    /// use std::sync::Arc;
    ///
    /// let swap = GenSwap::new(1);
    /// swap.update_arc(Arc::new(2));
    /// assert_eq!(swap.generation(), 1);
    /// ```
    #[inline]
    pub fn update_arc(&self, new_value: Arc<T>) {
        // Store data first - ArcSwap uses appropriate ordering internally
        self.data.store(new_value);

        // Increment generation with Release ordering to ensure the store is visible
        // to any thread that observes this generation via Acquire load
        self.generation.fetch_add(1, Ordering::Release);
    }

    /// Read-copy-update: atomically update based on current value.
    ///
    /// This method loads the current value, applies the provided function to
    /// create a new value, and attempts to store it. If another thread updates
    /// the value concurrently, the operation is retried.
    ///
    /// The generation counter is incremented after a successful update.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    ///
    /// let swap = GenSwap::new(5);
    /// swap.rcu(|&n| n * 2);
    /// assert_eq!(*swap.load_full(), 10);
    /// ```
    pub fn rcu<F>(&self, mut f: F)
    where
        F: FnMut(&T) -> T,
    {
        // Use ArcSwap's rcu method to handle the compare-and-swap loop
        self.data.rcu(|current| {
            let new_value = f(&**current);
            Arc::new(new_value)
        });

        // Increment generation after successful update
        self.generation.fetch_add(1, Ordering::Release);
    }

    /// Returns the current generation counter.
    ///
    /// Uses Acquire ordering to ensure that any writes performed before
    /// the generation was incremented are visible.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    ///
    /// let swap = GenSwap::new(1);
    /// assert_eq!(swap.generation(), 0);
    /// swap.update(2);
    /// assert_eq!(swap.generation(), 1);
    /// ```
    #[inline]
    pub fn generation(&self) -> u64 {
        self.generation.load(Ordering::Acquire)
    }

    /// Direct access to underlying `ArcSwap::load()` for one-off reads.
    ///
    /// Returns a `Guard` that dereferences to `&Arc<T>`. This is useful for
    /// single reads where caching is not beneficial.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    ///
    /// let swap = GenSwap::new(42);
    /// let guard = swap.load();
    /// assert_eq!(**guard, 42);
    /// ```
    #[inline]
    pub fn load(&self) -> arc_swap::Guard<Arc<T>> {
        self.data.load()
    }

    /// Direct access to underlying `ArcSwap::load_full()`.
    ///
    /// Returns a full `Arc<T>` clone. This increments the reference count.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    ///
    /// let swap = GenSwap::new(42);
    /// let arc = swap.load_full();
    /// assert_eq!(*arc, 42);
    /// ```
    #[inline]
    pub fn load_full(&self) -> Arc<T> {
        self.data.load_full()
    }

    /// Creates a new cached reader initialized with current data and generation.
    ///
    /// Each reader maintains its own cached `Arc<T>` and only reloads when
    /// the generation changes. This is the primary way to efficiently access
    /// data from multiple threads.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    /// use std::sync::Arc;
    ///
    /// let swap = Arc::new(GenSwap::new(42));
    /// let mut reader = swap.reader();
    /// assert_eq!(**reader.get(), 42);
    /// ```
    #[inline]
    pub fn reader(self: &Arc<Self>) -> CachedReader<T> {
        CachedReader::new(Arc::clone(self))
    }
}

// Safety: GenSwap is Send + Sync when T is Send + Sync
// ArcSwap<T> is already Send + Sync when T is Send + Sync
// AtomicU64 is always Send + Sync
unsafe impl<T: Send + Sync> Send for GenSwap<T> {}
unsafe impl<T: Send + Sync> Sync for GenSwap<T> {}

impl<T> Default for GenSwap<T>
where
    T: Default,
{
    fn default() -> Self {
        Self::new(T::default())
    }
}

impl<T> From<T> for GenSwap<T> {
    fn from(value: T) -> Self {
        Self::new(value)
    }
}