Skip to main content

qubit_cas/fast/
fast_cas_policy.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Retry policy for fast compare-and-swap operations.
11
12use konst::cmp::min;
13
14/// Retry policy used by [`crate::FastCas`] when compare-and-swap loses a race.
15#[derive(Debug, Clone, Copy, Eq, PartialEq)]
16pub enum FastCasPolicy {
17    /// Try the operation once and return conflict on the first failed CAS.
18    Once,
19
20    /// Retry immediately until CAS succeeds, the operation aborts, or the
21    /// attempt budget is exhausted.
22    Spin {
23        /// Maximum number of CAS attempts, including the first attempt.
24        max_attempts: u32,
25    },
26
27    /// Spin for a short prefix, then yield between retries until the attempt
28    /// budget is exhausted.
29    SpinYield {
30        /// Number of attempts to run without yielding.
31        spin_attempts: u32,
32
33        /// Maximum number of CAS attempts, including the first attempt.
34        max_attempts: u32,
35    },
36}
37
38impl FastCasPolicy {
39    /// Creates a policy that performs one CAS attempt.
40    ///
41    /// # Returns
42    /// A single-attempt policy.
43    #[inline]
44    pub const fn once() -> Self {
45        Self::Once
46    }
47
48    /// Creates an immediate-spin retry policy.
49    ///
50    /// # Parameters
51    /// - `max_attempts`: Maximum number of CAS attempts. Zero is normalized to
52    ///   one attempt.
53    ///
54    /// # Returns
55    /// A spin policy with a non-zero attempt budget.
56    #[inline]
57    pub const fn spin(max_attempts: u32) -> Self {
58        Self::Spin {
59            max_attempts: normalize_attempts(max_attempts),
60        }
61    }
62
63    /// Creates a spin-then-yield retry policy.
64    ///
65    /// # Parameters
66    /// - `spin_attempts`: Number of attempts to run before yielding.
67    /// - `max_attempts`: Maximum number of CAS attempts. Zero is normalized to
68    ///   one attempt.
69    ///
70    /// # Returns
71    /// A spin-yield policy with a non-zero attempt budget.
72    #[inline]
73    pub const fn spin_yield(spin_attempts: u32, max_attempts: u32) -> Self {
74        let max_attempts = normalize_attempts(max_attempts);
75        Self::SpinYield {
76            spin_attempts: min!(spin_attempts, max_attempts),
77            max_attempts,
78        }
79    }
80
81    /// Returns the maximum number of attempts allowed by this policy.
82    ///
83    /// # Returns
84    /// A non-zero attempt count.
85    #[inline]
86    pub const fn max_attempts(self) -> u32 {
87        match self {
88            Self::Once => 1,
89            Self::Spin { max_attempts } => normalize_attempts(max_attempts),
90            Self::SpinYield { max_attempts, .. } => normalize_attempts(max_attempts),
91        }
92    }
93
94    /// Returns whether the caller should yield before the next attempt.
95    ///
96    /// # Parameters
97    /// - `next_attempt`: One-based number of the next attempt to execute.
98    ///
99    /// # Returns
100    /// `true` when this policy should yield before `next_attempt`.
101    #[inline]
102    pub const fn should_yield_before(self, next_attempt: u32) -> bool {
103        match self {
104            Self::SpinYield { spin_attempts, .. } => next_attempt > spin_attempts,
105            Self::Once | Self::Spin { .. } => false,
106        }
107    }
108}
109
110/// Normalizes an attempt budget so every policy can execute at least once.
111///
112/// # Parameters
113/// - `attempts`: Requested attempt budget.
114///
115/// # Returns
116/// `attempts` when non-zero, otherwise `1`.
117#[inline]
118const fn normalize_attempts(attempts: u32) -> u32 {
119    if attempts == 0 { 1 } else { attempts }
120}