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}