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