Skip to main content

rustfs_io_core/
lock_optimizer.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Lock optimization utilities.
16//!
17//! This module provides lock optimization strategies and statistics
18//! to improve concurrent performance.
19
20use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
21use std::time::{Duration, Instant};
22
23/// Lock optimization configuration.
24#[derive(Debug, Clone)]
25pub struct LockOptimizeConfig {
26    /// Whether optimization is enabled.
27    pub enabled: bool,
28    /// Lock acquire timeout.
29    pub acquire_timeout: Duration,
30    /// Maximum hold time warning threshold.
31    pub max_hold_time_warning: Duration,
32    /// Enable adaptive spinning.
33    pub adaptive_spin: bool,
34    /// Maximum spin iterations.
35    pub max_spin_iterations: usize,
36}
37
38impl Default for LockOptimizeConfig {
39    fn default() -> Self {
40        Self {
41            enabled: true,
42            acquire_timeout: Duration::from_secs(5),
43            max_hold_time_warning: Duration::from_millis(100),
44            adaptive_spin: true,
45            max_spin_iterations: 1000,
46        }
47    }
48}
49
50/// Lock statistics.
51#[derive(Debug, Default)]
52pub struct LockStats {
53    /// Number of locks acquired.
54    pub locks_acquired: AtomicU64,
55    /// Number of locks released early (before timeout).
56    pub locks_released_early: AtomicU64,
57    /// Total hold time in nanoseconds.
58    pub total_hold_time_ns: AtomicU64,
59    /// Maximum hold time in nanoseconds.
60    pub max_hold_time_ns: AtomicU64,
61    /// Number of contention events.
62    pub contentions: AtomicU64,
63    /// Number of spin successes.
64    pub spin_successes: AtomicU64,
65    /// Number of spin failures.
66    pub spin_failures: AtomicU64,
67}
68
69impl LockStats {
70    /// Create new lock statistics.
71    pub fn new() -> Self {
72        Self::default()
73    }
74
75    /// Record a lock acquisition.
76    pub fn record_acquire(&self) {
77        self.locks_acquired.fetch_add(1, Ordering::Relaxed);
78    }
79
80    /// Record a lock release.
81    pub fn record_release(&self, hold_time: Duration) {
82        let ns = hold_time.as_nanos() as u64;
83        self.total_hold_time_ns.fetch_add(ns, Ordering::Relaxed);
84
85        // Update max hold time
86        let mut current = self.max_hold_time_ns.load(Ordering::Relaxed);
87        while ns > current {
88            match self
89                .max_hold_time_ns
90                .compare_exchange_weak(current, ns, Ordering::Relaxed, Ordering::Relaxed)
91            {
92                Ok(_) => break,
93                Err(actual) => current = actual,
94            }
95        }
96    }
97
98    /// Record an early release.
99    pub fn record_early_release(&self) {
100        self.locks_released_early.fetch_add(1, Ordering::Relaxed);
101    }
102
103    /// Record a contention event.
104    pub fn record_contention(&self) {
105        self.contentions.fetch_add(1, Ordering::Relaxed);
106    }
107
108    /// Record a spin success.
109    pub fn record_spin_success(&self) {
110        self.spin_successes.fetch_add(1, Ordering::Relaxed);
111    }
112
113    /// Record a spin failure.
114    pub fn record_spin_failure(&self) {
115        self.spin_failures.fetch_add(1, Ordering::Relaxed);
116    }
117
118    /// Get total locks acquired.
119    pub fn total_acquired(&self) -> u64 {
120        self.locks_acquired.load(Ordering::Relaxed)
121    }
122
123    /// Get average hold time.
124    pub fn avg_hold_time(&self) -> Duration {
125        let total = self.total_hold_time_ns.load(Ordering::Relaxed);
126        let count = self.locks_acquired.load(Ordering::Relaxed);
127        total.checked_div(count).map(Duration::from_nanos).unwrap_or(Duration::ZERO)
128    }
129
130    /// Get maximum hold time.
131    pub fn max_hold_time(&self) -> Duration {
132        Duration::from_nanos(self.max_hold_time_ns.load(Ordering::Relaxed))
133    }
134
135    /// Get contention rate.
136    pub fn contention_rate(&self) -> f64 {
137        let acquired = self.locks_acquired.load(Ordering::Relaxed);
138        let contentions = self.contentions.load(Ordering::Relaxed);
139        if acquired == 0 {
140            0.0
141        } else {
142            contentions as f64 / acquired as f64
143        }
144    }
145
146    /// Get spin success rate.
147    pub fn spin_success_rate(&self) -> f64 {
148        let successes = self.spin_successes.load(Ordering::Relaxed);
149        let failures = self.spin_failures.load(Ordering::Relaxed);
150        let total = successes + failures;
151        if total == 0 { 0.0 } else { successes as f64 / total as f64 }
152    }
153
154    /// Reset all statistics.
155    pub fn reset(&self) {
156        self.locks_acquired.store(0, Ordering::Relaxed);
157        self.locks_released_early.store(0, Ordering::Relaxed);
158        self.total_hold_time_ns.store(0, Ordering::Relaxed);
159        self.max_hold_time_ns.store(0, Ordering::Relaxed);
160        self.contentions.store(0, Ordering::Relaxed);
161        self.spin_successes.store(0, Ordering::Relaxed);
162        self.spin_failures.store(0, Ordering::Relaxed);
163    }
164}
165
166/// Lock optimizer.
167pub struct LockOptimizer {
168    /// Configuration.
169    config: LockOptimizeConfig,
170    /// Statistics.
171    stats: LockStats,
172    /// Current spin iterations (adaptive).
173    current_spin: AtomicUsize,
174}
175
176impl LockOptimizer {
177    /// Create a new lock optimizer.
178    pub fn new(config: LockOptimizeConfig) -> Self {
179        Self {
180            config,
181            stats: LockStats::new(),
182            current_spin: AtomicUsize::new(100),
183        }
184    }
185
186    /// Create with default configuration.
187    pub fn with_defaults() -> Self {
188        Self::new(LockOptimizeConfig::default())
189    }
190
191    /// Get the configuration.
192    pub fn config(&self) -> &LockOptimizeConfig {
193        &self.config
194    }
195
196    /// Get the statistics.
197    pub fn stats(&self) -> &LockStats {
198        &self.stats
199    }
200
201    /// Record lock acquisition.
202    pub fn on_acquire(&self) {
203        if !self.config.enabled {
204            return;
205        }
206        self.stats.record_acquire();
207    }
208
209    /// Record lock release.
210    pub fn on_release(&self, hold_time: Duration) {
211        if !self.config.enabled {
212            return;
213        }
214        self.stats.record_release(hold_time);
215
216        // Check for early release
217        if hold_time < self.config.acquire_timeout / 2 {
218            self.stats.record_early_release();
219        }
220    }
221
222    /// Record contention.
223    pub fn on_contention(&self) {
224        if !self.config.enabled {
225            return;
226        }
227        self.stats.record_contention();
228    }
229
230    /// Perform adaptive spin.
231    ///
232    /// Returns true if the lock was acquired during spinning.
233    pub fn try_spin<F>(&self, mut try_acquire: F) -> bool
234    where
235        F: FnMut() -> bool,
236    {
237        if !self.config.enabled || !self.config.adaptive_spin {
238            return false;
239        }
240
241        let spin_count = self.current_spin.load(Ordering::Relaxed).min(self.config.max_spin_iterations);
242
243        for _ in 0..spin_count {
244            if try_acquire() {
245                self.stats.record_spin_success();
246                self.adapt_spin(true);
247                return true;
248            }
249            // Hint to the CPU that we're spinning
250            std::hint::spin_loop();
251        }
252
253        self.stats.record_spin_failure();
254        self.adapt_spin(false);
255        false
256    }
257
258    /// Adapt spin count based on success/failure.
259    fn adapt_spin(&self, success: bool) {
260        let current = self.current_spin.load(Ordering::Relaxed);
261        let new_count = if success {
262            // Increase spin count on success (up to max)
263            (current * 2).min(self.config.max_spin_iterations)
264        } else {
265            // Decrease spin count on failure (down to min)
266            (current / 2).max(10)
267        };
268        self.current_spin.store(new_count, Ordering::Relaxed);
269    }
270
271    /// Get current spin count.
272    pub fn current_spin_count(&self) -> usize {
273        self.current_spin.load(Ordering::Relaxed)
274    }
275
276    /// Check if hold time is excessive.
277    pub fn is_hold_time_excessive(&self, hold_time: Duration) -> bool {
278        hold_time > self.config.max_hold_time_warning
279    }
280
281    /// Reset statistics.
282    pub fn reset_stats(&self) {
283        self.stats.reset();
284    }
285}
286
287/// RAII guard for tracking lock hold time.
288pub struct LockGuard<'a> {
289    optimizer: &'a LockOptimizer,
290    start: Instant,
291}
292
293impl<'a> LockGuard<'a> {
294    /// Create a new lock guard.
295    pub fn new(optimizer: &'a LockOptimizer) -> Self {
296        optimizer.on_acquire();
297        Self {
298            optimizer,
299            start: Instant::now(),
300        }
301    }
302}
303
304impl Drop for LockGuard<'_> {
305    fn drop(&mut self) {
306        self.optimizer.on_release(self.start.elapsed());
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn test_lock_stats() {
316        let stats = LockStats::new();
317
318        stats.record_acquire();
319        stats.record_acquire();
320        stats.record_release(Duration::from_millis(10));
321        stats.record_release(Duration::from_millis(20));
322
323        assert_eq!(stats.total_acquired(), 2);
324        assert!(stats.avg_hold_time() >= Duration::from_millis(15));
325        assert_eq!(stats.max_hold_time(), Duration::from_millis(20));
326    }
327
328    #[test]
329    fn test_contention_rate() {
330        let stats = LockStats::new();
331
332        stats.record_acquire();
333        stats.record_acquire();
334        stats.record_acquire();
335        stats.record_contention();
336
337        assert!((stats.contention_rate() - 0.3333333333333333).abs() < 0.01);
338    }
339
340    #[test]
341    fn test_spin_stats() {
342        let stats = LockStats::new();
343
344        stats.record_spin_success();
345        stats.record_spin_success();
346        stats.record_spin_failure();
347
348        assert!((stats.spin_success_rate() - 0.6666666666666666).abs() < 0.01);
349    }
350
351    #[test]
352    fn test_lock_optimizer() {
353        let optimizer = LockOptimizer::with_defaults();
354
355        {
356            let _guard = LockGuard::new(&optimizer);
357            std::thread::sleep(Duration::from_millis(10));
358        }
359
360        assert_eq!(optimizer.stats().total_acquired(), 1);
361        assert!(optimizer.stats().avg_hold_time() >= Duration::from_millis(10));
362    }
363
364    #[test]
365    fn test_adaptive_spin() {
366        let optimizer = LockOptimizer::with_defaults();
367
368        // Simulate successful spin
369        let acquired = optimizer.try_spin(|| true);
370        assert!(acquired);
371        assert!(optimizer.current_spin_count() > 100); // Should increase
372
373        // Simulate failed spin
374        let acquired = optimizer.try_spin(|| false);
375        assert!(!acquired);
376        assert!(optimizer.current_spin_count() < 200); // Should decrease
377    }
378
379    #[test]
380    fn test_disabled_optimizer() {
381        let config = LockOptimizeConfig {
382            enabled: false,
383            ..Default::default()
384        };
385        let optimizer = LockOptimizer::new(config);
386
387        optimizer.on_acquire();
388        optimizer.on_release(Duration::from_millis(10));
389
390        // Should not track when disabled
391        assert_eq!(optimizer.stats().total_acquired(), 0);
392    }
393}