rustfs-io-core 1.0.1

Shared I/O primitives for RustFS (buffer pool, storage profiling, backpressure, deadlock detection)
Documentation
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Lock optimization utilities.
//!
//! This module provides lock optimization strategies and statistics
//! to improve concurrent performance.

use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::{Duration, Instant};

/// Lock optimization configuration.
#[derive(Debug, Clone)]
pub struct LockOptimizeConfig {
    /// Whether optimization is enabled.
    pub enabled: bool,
    /// Lock acquire timeout.
    pub acquire_timeout: Duration,
    /// Maximum hold time warning threshold.
    pub max_hold_time_warning: Duration,
    /// Enable adaptive spinning.
    pub adaptive_spin: bool,
    /// Maximum spin iterations.
    pub max_spin_iterations: usize,
}

impl Default for LockOptimizeConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            acquire_timeout: Duration::from_secs(5),
            max_hold_time_warning: Duration::from_millis(100),
            adaptive_spin: true,
            max_spin_iterations: 1000,
        }
    }
}

/// Lock statistics.
#[derive(Debug, Default)]
pub struct LockStats {
    /// Number of locks acquired.
    pub locks_acquired: AtomicU64,
    /// Number of locks released early (before timeout).
    pub locks_released_early: AtomicU64,
    /// Total hold time in nanoseconds.
    pub total_hold_time_ns: AtomicU64,
    /// Maximum hold time in nanoseconds.
    pub max_hold_time_ns: AtomicU64,
    /// Number of contention events.
    pub contentions: AtomicU64,
    /// Number of spin successes.
    pub spin_successes: AtomicU64,
    /// Number of spin failures.
    pub spin_failures: AtomicU64,
}

impl LockStats {
    /// Create new lock statistics.
    pub fn new() -> Self {
        Self::default()
    }

    /// Record a lock acquisition.
    pub fn record_acquire(&self) {
        self.locks_acquired.fetch_add(1, Ordering::Relaxed);
    }

    /// Record a lock release.
    pub fn record_release(&self, hold_time: Duration) {
        let ns = hold_time.as_nanos() as u64;
        self.total_hold_time_ns.fetch_add(ns, Ordering::Relaxed);

        // Update max hold time
        let mut current = self.max_hold_time_ns.load(Ordering::Relaxed);
        while ns > current {
            match self
                .max_hold_time_ns
                .compare_exchange_weak(current, ns, Ordering::Relaxed, Ordering::Relaxed)
            {
                Ok(_) => break,
                Err(actual) => current = actual,
            }
        }
    }

    /// Record an early release.
    pub fn record_early_release(&self) {
        self.locks_released_early.fetch_add(1, Ordering::Relaxed);
    }

    /// Record a contention event.
    pub fn record_contention(&self) {
        self.contentions.fetch_add(1, Ordering::Relaxed);
    }

    /// Record a spin success.
    pub fn record_spin_success(&self) {
        self.spin_successes.fetch_add(1, Ordering::Relaxed);
    }

    /// Record a spin failure.
    pub fn record_spin_failure(&self) {
        self.spin_failures.fetch_add(1, Ordering::Relaxed);
    }

    /// Get total locks acquired.
    pub fn total_acquired(&self) -> u64 {
        self.locks_acquired.load(Ordering::Relaxed)
    }

    /// Get average hold time.
    pub fn avg_hold_time(&self) -> Duration {
        let total = self.total_hold_time_ns.load(Ordering::Relaxed);
        let count = self.locks_acquired.load(Ordering::Relaxed);
        total.checked_div(count).map(Duration::from_nanos).unwrap_or(Duration::ZERO)
    }

    /// Get maximum hold time.
    pub fn max_hold_time(&self) -> Duration {
        Duration::from_nanos(self.max_hold_time_ns.load(Ordering::Relaxed))
    }

    /// Get contention rate.
    pub fn contention_rate(&self) -> f64 {
        let acquired = self.locks_acquired.load(Ordering::Relaxed);
        let contentions = self.contentions.load(Ordering::Relaxed);
        if acquired == 0 {
            0.0
        } else {
            contentions as f64 / acquired as f64
        }
    }

    /// Get spin success rate.
    pub fn spin_success_rate(&self) -> f64 {
        let successes = self.spin_successes.load(Ordering::Relaxed);
        let failures = self.spin_failures.load(Ordering::Relaxed);
        let total = successes + failures;
        if total == 0 { 0.0 } else { successes as f64 / total as f64 }
    }

    /// Reset all statistics.
    pub fn reset(&self) {
        self.locks_acquired.store(0, Ordering::Relaxed);
        self.locks_released_early.store(0, Ordering::Relaxed);
        self.total_hold_time_ns.store(0, Ordering::Relaxed);
        self.max_hold_time_ns.store(0, Ordering::Relaxed);
        self.contentions.store(0, Ordering::Relaxed);
        self.spin_successes.store(0, Ordering::Relaxed);
        self.spin_failures.store(0, Ordering::Relaxed);
    }
}

/// Lock optimizer.
pub struct LockOptimizer {
    /// Configuration.
    config: LockOptimizeConfig,
    /// Statistics.
    stats: LockStats,
    /// Current spin iterations (adaptive).
    current_spin: AtomicUsize,
}

impl LockOptimizer {
    /// Create a new lock optimizer.
    pub fn new(config: LockOptimizeConfig) -> Self {
        Self {
            config,
            stats: LockStats::new(),
            current_spin: AtomicUsize::new(100),
        }
    }

    /// Create with default configuration.
    pub fn with_defaults() -> Self {
        Self::new(LockOptimizeConfig::default())
    }

    /// Get the configuration.
    pub fn config(&self) -> &LockOptimizeConfig {
        &self.config
    }

    /// Get the statistics.
    pub fn stats(&self) -> &LockStats {
        &self.stats
    }

    /// Record lock acquisition.
    pub fn on_acquire(&self) {
        if !self.config.enabled {
            return;
        }
        self.stats.record_acquire();
    }

    /// Record lock release.
    pub fn on_release(&self, hold_time: Duration) {
        if !self.config.enabled {
            return;
        }
        self.stats.record_release(hold_time);

        // Check for early release
        if hold_time < self.config.acquire_timeout / 2 {
            self.stats.record_early_release();
        }
    }

    /// Record contention.
    pub fn on_contention(&self) {
        if !self.config.enabled {
            return;
        }
        self.stats.record_contention();
    }

    /// Perform adaptive spin.
    ///
    /// Returns true if the lock was acquired during spinning.
    pub fn try_spin<F>(&self, mut try_acquire: F) -> bool
    where
        F: FnMut() -> bool,
    {
        if !self.config.enabled || !self.config.adaptive_spin {
            return false;
        }

        let spin_count = self.current_spin.load(Ordering::Relaxed).min(self.config.max_spin_iterations);

        for _ in 0..spin_count {
            if try_acquire() {
                self.stats.record_spin_success();
                self.adapt_spin(true);
                return true;
            }
            // Hint to the CPU that we're spinning
            std::hint::spin_loop();
        }

        self.stats.record_spin_failure();
        self.adapt_spin(false);
        false
    }

    /// Adapt spin count based on success/failure.
    fn adapt_spin(&self, success: bool) {
        let current = self.current_spin.load(Ordering::Relaxed);
        let new_count = if success {
            // Increase spin count on success (up to max)
            (current * 2).min(self.config.max_spin_iterations)
        } else {
            // Decrease spin count on failure (down to min)
            (current / 2).max(10)
        };
        self.current_spin.store(new_count, Ordering::Relaxed);
    }

    /// Get current spin count.
    pub fn current_spin_count(&self) -> usize {
        self.current_spin.load(Ordering::Relaxed)
    }

    /// Check if hold time is excessive.
    pub fn is_hold_time_excessive(&self, hold_time: Duration) -> bool {
        hold_time > self.config.max_hold_time_warning
    }

    /// Reset statistics.
    pub fn reset_stats(&self) {
        self.stats.reset();
    }
}

/// RAII guard for tracking lock hold time.
pub struct LockGuard<'a> {
    optimizer: &'a LockOptimizer,
    start: Instant,
}

impl<'a> LockGuard<'a> {
    /// Create a new lock guard.
    pub fn new(optimizer: &'a LockOptimizer) -> Self {
        optimizer.on_acquire();
        Self {
            optimizer,
            start: Instant::now(),
        }
    }
}

impl Drop for LockGuard<'_> {
    fn drop(&mut self) {
        self.optimizer.on_release(self.start.elapsed());
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_lock_stats() {
        let stats = LockStats::new();

        stats.record_acquire();
        stats.record_acquire();
        stats.record_release(Duration::from_millis(10));
        stats.record_release(Duration::from_millis(20));

        assert_eq!(stats.total_acquired(), 2);
        assert!(stats.avg_hold_time() >= Duration::from_millis(15));
        assert_eq!(stats.max_hold_time(), Duration::from_millis(20));
    }

    #[test]
    fn test_contention_rate() {
        let stats = LockStats::new();

        stats.record_acquire();
        stats.record_acquire();
        stats.record_acquire();
        stats.record_contention();

        assert!((stats.contention_rate() - 0.3333333333333333).abs() < 0.01);
    }

    #[test]
    fn test_spin_stats() {
        let stats = LockStats::new();

        stats.record_spin_success();
        stats.record_spin_success();
        stats.record_spin_failure();

        assert!((stats.spin_success_rate() - 0.6666666666666666).abs() < 0.01);
    }

    #[test]
    fn test_lock_optimizer() {
        let optimizer = LockOptimizer::with_defaults();

        {
            let _guard = LockGuard::new(&optimizer);
            std::thread::sleep(Duration::from_millis(10));
        }

        assert_eq!(optimizer.stats().total_acquired(), 1);
        assert!(optimizer.stats().avg_hold_time() >= Duration::from_millis(10));
    }

    #[test]
    fn test_adaptive_spin() {
        let optimizer = LockOptimizer::with_defaults();

        // Simulate successful spin
        let acquired = optimizer.try_spin(|| true);
        assert!(acquired);
        assert!(optimizer.current_spin_count() > 100); // Should increase

        // Simulate failed spin
        let acquired = optimizer.try_spin(|| false);
        assert!(!acquired);
        assert!(optimizer.current_spin_count() < 200); // Should decrease
    }

    #[test]
    fn test_disabled_optimizer() {
        let config = LockOptimizeConfig {
            enabled: false,
            ..Default::default()
        };
        let optimizer = LockOptimizer::new(config);

        optimizer.on_acquire();
        optimizer.on_release(Duration::from_millis(10));

        // Should not track when disabled
        assert_eq!(optimizer.stats().total_acquired(), 0);
    }
}