genswap 0.1.0

Generation-tracked ArcSwap wrapper for 5-1600x faster cached reads in read-heavy workloads
Documentation
use std::sync::Arc;

use crate::GenSwap;

/// The consumer-side handle that caches an `Arc<T>` locally.
///
/// `CachedReader` maintains a cached reference to the data along with its
/// generation number. On each access via `get()`, it checks if the source
/// generation has changed and only reloads if necessary.
///
/// # Thread Safety
///
/// `CachedReader` is `Send` but NOT `Sync`. Each thread should have its own
/// `CachedReader` instance to maintain independent caches.
///
/// # Performance
///
/// The hot path of `get()` consists of:
/// 1. An atomic load (Acquire) of the generation counter
/// 2. A comparison with the cached generation
/// 3. Return cached Arc if generations match (no refcount operation!)
///
/// This is significantly faster than `ArcSwap::load_full()` when cache hits
/// are common.
///
/// # Example
///
/// ```
/// use genswap::GenSwap;
/// use std::sync::Arc;
///
/// let swap = Arc::new(GenSwap::new(42));
/// let mut reader = swap.reader();
///
/// // First access loads the data
/// assert_eq!(**reader.get(), 42);
///
/// // Second access uses cached value (very fast!)
/// assert_eq!(**reader.get(), 42);
///
/// // Update the source
/// swap.update(100);
///
/// // Reader automatically sees the new value
/// assert_eq!(**reader.get(), 100);
/// ```
pub struct CachedReader<T> {
    source: Arc<GenSwap<T>>,
    cached: Arc<T>,
    cached_generation: u64,
}

impl<T> CachedReader<T> {
    /// Creates a new `CachedReader` from a `GenSwap`.
    ///
    /// This is typically called via `GenSwap::reader()`.
    ///
    /// The reader is initialized with the current data and generation.
    #[inline]
    pub(crate) fn new(source: Arc<GenSwap<T>>) -> Self {
        let cached_generation = source.generation();
        let cached = source.load_full();

        Self {
            source,
            cached,
            cached_generation,
        }
    }

    /// Returns a reference to the cached `Arc<T>`, reloading if stale.
    ///
    /// This is the primary method for accessing data. It checks if the cached
    /// generation matches the source generation and only reloads if they differ.
    ///
    /// # Performance
    ///
    /// On cache hit (common case): O(1) with just an atomic load and comparison.
    /// On cache miss: Reloads from source (involves refcount operations).
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    /// use std::sync::Arc;
    ///
    /// let swap = Arc::new(GenSwap::new(vec![1, 2, 3]));
    /// let mut reader = swap.reader();
    ///
    /// let data = reader.get();
    /// assert_eq!(data.len(), 3);
    /// ```
    #[inline]
    pub fn get(&mut self) -> &Arc<T> {
        // Load current generation with Acquire ordering
        // This pairs with the Release ordering in update()
        let current_gen = self.source.generation();

        // Only reload if generation has changed
        if current_gen != self.cached_generation {
            self.cached = self.source.load_full();
            self.cached_generation = current_gen;
        }

        &self.cached
    }

    /// Returns a cloned `Arc<T>`, reloading if stale.
    ///
    /// This is equivalent to `reader.get().clone()` but may be more
    /// ergonomic when you need ownership of the `Arc`.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    /// use std::sync::Arc;
    ///
    /// let swap = Arc::new(GenSwap::new(42));
    /// let mut reader = swap.reader();
    ///
    /// let arc: Arc<u64> = reader.get_clone();
    /// assert_eq!(*arc, 42);
    /// ```
    #[inline]
    pub fn get_clone(&mut self) -> Arc<T> {
        Arc::clone(self.get())
    }

    /// Checks if the cached data is stale without updating the cache.
    ///
    /// Returns `true` if the source generation differs from the cached
    /// generation, indicating that a call to `get()` would reload.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    /// use std::sync::Arc;
    ///
    /// let swap = Arc::new(GenSwap::new(42));
    /// let mut reader = swap.reader();
    ///
    /// assert!(!reader.is_stale());
    ///
    /// swap.update(100);
    /// assert!(reader.is_stale());
    ///
    /// reader.get(); // Refresh
    /// assert!(!reader.is_stale());
    /// ```
    #[inline]
    pub fn is_stale(&self) -> bool {
        let current_gen = self.source.generation();
        current_gen != self.cached_generation
    }

    /// Unconditionally reloads from source, even if generation matches.
    ///
    /// This is rarely needed but can be useful for testing or debugging.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    /// use std::sync::Arc;
    ///
    /// let swap = Arc::new(GenSwap::new(42));
    /// let mut reader = swap.reader();
    ///
    /// reader.force_refresh();
    /// assert_eq!(**reader.get(), 42);
    /// ```
    #[inline]
    pub fn force_refresh(&mut self) {
        self.cached_generation = self.source.generation();
        self.cached = self.source.load_full();
    }

    /// Returns the generation of the currently cached data.
    ///
    /// This is the generation that was current when the data was last loaded.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    /// use std::sync::Arc;
    ///
    /// let swap = Arc::new(GenSwap::new(42));
    /// let mut reader = swap.reader();
    ///
    /// assert_eq!(reader.cached_generation(), 0);
    ///
    /// swap.update(100);
    /// assert_eq!(reader.cached_generation(), 0); // Still cached old generation
    ///
    /// reader.get();
    /// assert_eq!(reader.cached_generation(), 1); // Now updated
    /// ```
    #[inline]
    pub fn cached_generation(&self) -> u64 {
        self.cached_generation
    }

    /// Returns a reference to the cached `Arc<T>` without checking for staleness.
    ///
    /// This returns whatever is currently cached, regardless of whether the
    /// source has been updated. Use this when you explicitly want the last
    /// accessed value without potential reloading.
    ///
    /// # Example
    ///
    /// ```
    /// use genswap::GenSwap;
    /// use std::sync::Arc;
    ///
    /// let swap = Arc::new(GenSwap::new(42));
    /// let mut reader = swap.reader();
    ///
    /// let _ = reader.get();
    /// swap.update(100);
    ///
    /// // cached() returns the old value
    /// assert_eq!(**reader.cached(), 42);
    ///
    /// // get() returns the new value
    /// assert_eq!(**reader.get(), 100);
    /// ```
    #[inline]
    pub fn cached(&self) -> &Arc<T> {
        &self.cached
    }
}

// CachedReader is Send when T is Send + Sync
// It holds an Arc<GenSwap<T>> which is Send when GenSwap is Send + Sync
unsafe impl<T: Send + Sync> Send for CachedReader<T> {}

// CachedReader is explicitly NOT Sync because it contains mutable cache state
// Each thread should have its own CachedReader instance