Skip to main content

qubit_atomic/atomic/
atomic_count.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9//! # Atomic Count
10//!
11//! Provides a non-negative atomic counter for values whose transitions are
12//! used as synchronization signals.
13
14use std::fmt;
15use std::sync::atomic::{
16    AtomicUsize as StdAtomicUsize,
17    Ordering,
18};
19
20/// Applies a checked update through the production weak-CAS retry loop.
21///
22/// This hidden test bridge lets Loom supply instrumented atomic operations
23/// while exercising the same retry core used by [`AtomicCount`].
24///
25/// # Parameters
26///
27/// * `load_acquire` - Loads the current value with acquire ordering.
28/// * `compare_exchange_weak_acqrel_acquire` - Performs weak CAS with
29///   acquire-release success ordering and acquire failure ordering.
30/// * `update` - Maps the observed value to a proposed next value, or returns
31///   `None` to reject the update.
32///
33/// # Returns
34///
35/// `Some(new_value)` after a successful update, or `None` when `update`
36/// rejects the observed value. The update and CAS callbacks may run more than
37/// once when a weak CAS fails.
38#[doc(hidden)]
39pub fn try_update_atomic_count<L, C, F>(
40    mut load_acquire: L,
41    mut compare_exchange_weak_acqrel_acquire: C,
42    mut update: F,
43) -> Option<usize>
44where
45    L: FnMut() -> usize,
46    C: FnMut(usize, usize) -> Result<usize, usize>,
47    F: FnMut(usize) -> Option<usize>,
48{
49    let mut current = load_acquire();
50    loop {
51        let next = update(current)?;
52        match compare_exchange_weak_acqrel_acquire(current, next) {
53            Ok(_) => return Some(next),
54            Err(actual) => current = actual,
55        }
56    }
57}
58
59/// A non-negative atomic counter with synchronization-oriented operations.
60///
61/// Use this type when the counter value is part of a concurrent state machine,
62/// such as active task counts, in-flight request counts, resource usage counts,
63/// or shutdown and termination checks. Its operations return the value after
64/// the update, which makes zero-transition logic straightforward.
65///
66/// For pure metrics, statistics, event counters, or ID generation, prefer the
67/// regular atomic integer types such as [`Atomic<usize>`](crate::Atomic).
68/// Those types keep arithmetic operations lightweight for pure counting.
69///
70/// This counter never wraps. Incrementing past [`usize::MAX`] panics, and
71/// decrementing below zero panics. Use [`try_add`](Self::try_add),
72/// [`try_dec`](Self::try_dec), or [`try_sub`](Self::try_sub) when overflow or
73/// underflow is a normal business outcome.
74///
75/// # Examples
76///
77/// ```rust
78/// use qubit_atomic::AtomicCount;
79///
80/// let active_tasks = AtomicCount::zero();
81///
82/// active_tasks.inc();
83/// assert!(!active_tasks.is_zero());
84///
85/// if active_tasks.dec() == 0 {
86///     // The last active task finished; notify termination waiters here.
87/// }
88/// ```
89#[repr(transparent)]
90pub struct AtomicCount {
91    /// Standard-library atomic storage for the non-negative counter value.
92    inner: StdAtomicUsize,
93}
94
95impl AtomicCount {
96    /// Creates a new non-negative atomic counter.
97    ///
98    /// # Parameters
99    ///
100    /// * `value` - The initial counter value.
101    ///
102    /// # Returns
103    ///
104    /// A counter initialized to `value`.
105    ///
106    /// # Examples
107    ///
108    /// ```rust
109    /// use qubit_atomic::AtomicCount;
110    ///
111    /// let counter = AtomicCount::new(3);
112    /// assert_eq!(counter.get(), 3);
113    /// ```
114    #[inline]
115    pub const fn new(value: usize) -> Self {
116        Self {
117            inner: StdAtomicUsize::new(value),
118        }
119    }
120
121    /// Creates a new counter initialized to zero.
122    ///
123    /// # Returns
124    ///
125    /// A counter whose current value is zero.
126    ///
127    /// # Examples
128    ///
129    /// ```rust
130    /// use qubit_atomic::AtomicCount;
131    ///
132    /// let counter = AtomicCount::zero();
133    /// assert!(counter.is_zero());
134    /// ```
135    #[inline(always)]
136    pub const fn zero() -> Self {
137        Self::new(0)
138    }
139
140    /// Gets the current counter value.
141    ///
142    /// # Returns
143    ///
144    /// The current counter value.
145    ///
146    /// # Examples
147    ///
148    /// ```rust
149    /// use qubit_atomic::AtomicCount;
150    ///
151    /// let counter = AtomicCount::new(7);
152    /// assert_eq!(counter.get(), 7);
153    /// ```
154    #[must_use]
155    #[inline(always)]
156    pub fn get(&self) -> usize {
157        self.inner.load(Ordering::Acquire)
158    }
159
160    /// Returns whether the current counter value is zero.
161    ///
162    /// # Returns
163    ///
164    /// `true` if the current value is zero, otherwise `false`.
165    ///
166    /// # Examples
167    ///
168    /// ```rust
169    /// use qubit_atomic::AtomicCount;
170    ///
171    /// let counter = AtomicCount::zero();
172    /// assert!(counter.is_zero());
173    /// ```
174    ///
175    /// The result must be observed:
176    ///
177    /// ```compile_fail
178    /// #![deny(unused_must_use)]
179    /// use qubit_atomic::AtomicCount;
180    ///
181    /// AtomicCount::zero().is_zero();
182    /// ```
183    #[must_use]
184    #[inline(always)]
185    pub fn is_zero(&self) -> bool {
186        self.get() == 0
187    }
188
189    /// Returns whether the current counter value is greater than zero.
190    ///
191    /// # Returns
192    ///
193    /// `true` if the current value is greater than zero, otherwise `false`.
194    ///
195    /// # Examples
196    ///
197    /// ```rust
198    /// use qubit_atomic::AtomicCount;
199    ///
200    /// let counter = AtomicCount::new(1);
201    /// assert!(counter.is_positive());
202    /// ```
203    #[must_use]
204    #[inline(always)]
205    pub fn is_positive(&self) -> bool {
206        self.get() > 0
207    }
208
209    /// Increments the counter by one and returns the new value.
210    ///
211    /// # Returns
212    ///
213    /// The counter value after the increment.
214    ///
215    /// # Panics
216    ///
217    /// Panics if the increment would overflow [`usize::MAX`].
218    ///
219    /// # Examples
220    ///
221    /// ```rust
222    /// use qubit_atomic::AtomicCount;
223    ///
224    /// let counter = AtomicCount::zero();
225    /// assert_eq!(counter.inc(), 1);
226    /// ```
227    #[inline(always)]
228    pub fn inc(&self) -> usize {
229        self.add(1)
230    }
231
232    /// Adds `delta` to the counter and returns the new value.
233    ///
234    /// # Parameters
235    ///
236    /// * `delta` - The amount to add.
237    ///
238    /// # Returns
239    ///
240    /// The counter value after the addition.
241    ///
242    /// # Panics
243    ///
244    /// Panics if the addition would overflow [`usize::MAX`].
245    ///
246    /// # Examples
247    ///
248    /// ```rust
249    /// use qubit_atomic::AtomicCount;
250    ///
251    /// let counter = AtomicCount::new(2);
252    /// assert_eq!(counter.add(3), 5);
253    /// ```
254    #[inline(always)]
255    pub fn add(&self, delta: usize) -> usize {
256        self.try_add(delta).expect("atomic counter overflow")
257    }
258
259    /// Tries to add `delta` to the counter.
260    ///
261    /// # Parameters
262    ///
263    /// * `delta` - The amount to add.
264    ///
265    /// # Returns
266    ///
267    /// `Some(new_value)` if the addition succeeds, or `None` if it would
268    /// overflow [`usize::MAX`]. On `None`, the counter is left unchanged.
269    ///
270    /// # Examples
271    ///
272    /// ```rust
273    /// use qubit_atomic::AtomicCount;
274    ///
275    /// let counter = AtomicCount::new(2);
276    /// assert_eq!(counter.try_add(3), Some(5));
277    /// ```
278    #[inline(always)]
279    pub fn try_add(&self, delta: usize) -> Option<usize> {
280        self.try_update(|current| current.checked_add(delta))
281    }
282
283    /// Decrements the counter by one and returns the new value.
284    ///
285    /// This method is useful for detecting the transition to zero:
286    ///
287    /// ```rust
288    /// use qubit_atomic::AtomicCount;
289    ///
290    /// let counter = AtomicCount::new(1);
291    /// if counter.dec() == 0 {
292    ///     // This call consumed the final counted item.
293    /// }
294    /// ```
295    ///
296    /// # Returns
297    ///
298    /// The counter value after the decrement.
299    ///
300    /// # Panics
301    ///
302    /// Panics if the current value is zero.
303    #[inline(always)]
304    pub fn dec(&self) -> usize {
305        self.try_dec().expect("atomic counter underflow")
306    }
307
308    /// Tries to decrement the counter by one.
309    ///
310    /// # Returns
311    ///
312    /// `Some(new_value)` if the decrement succeeds, or `None` if the current
313    /// value is zero. On `None`, the counter is left unchanged.
314    ///
315    /// # Examples
316    ///
317    /// ```rust
318    /// use qubit_atomic::AtomicCount;
319    ///
320    /// let counter = AtomicCount::new(1);
321    /// assert_eq!(counter.try_dec(), Some(0));
322    /// assert_eq!(counter.try_dec(), None);
323    /// ```
324    #[inline(always)]
325    pub fn try_dec(&self) -> Option<usize> {
326        self.try_sub(1)
327    }
328
329    /// Subtracts `delta` from the counter and returns the new value.
330    ///
331    /// # Parameters
332    ///
333    /// * `delta` - The amount to subtract.
334    ///
335    /// # Returns
336    ///
337    /// The counter value after the subtraction.
338    ///
339    /// # Panics
340    ///
341    /// Panics if the subtraction would make the counter negative.
342    ///
343    /// # Examples
344    ///
345    /// ```rust
346    /// use qubit_atomic::AtomicCount;
347    ///
348    /// let counter = AtomicCount::new(5);
349    /// assert_eq!(counter.sub(2), 3);
350    /// ```
351    #[inline(always)]
352    pub fn sub(&self, delta: usize) -> usize {
353        self.try_sub(delta).expect("atomic counter underflow")
354    }
355
356    /// Tries to subtract `delta` from the counter.
357    ///
358    /// # Parameters
359    ///
360    /// * `delta` - The amount to subtract.
361    ///
362    /// # Returns
363    ///
364    /// `Some(new_value)` if the subtraction succeeds, or `None` if it would
365    /// make the counter negative. On `None`, the counter is left unchanged.
366    ///
367    /// # Examples
368    ///
369    /// ```rust
370    /// use qubit_atomic::AtomicCount;
371    ///
372    /// let counter = AtomicCount::new(3);
373    /// assert_eq!(counter.try_sub(2), Some(1));
374    /// assert_eq!(counter.try_sub(2), None);
375    /// ```
376    #[inline(always)]
377    pub fn try_sub(&self, delta: usize) -> Option<usize> {
378        self.try_update(|current| current.checked_sub(delta))
379    }
380
381    /// Applies a checked update with synchronization semantics.
382    ///
383    /// # Parameters
384    ///
385    /// * `update` - A function that maps the current value to the next value,
386    ///   or returns `None` to reject the update.
387    ///
388    /// # Returns
389    ///
390    /// `Some(new_value)` if the update succeeds, or `None` if `update`
391    /// rejects the current value. A rejected update leaves the counter
392    /// unchanged.
393    #[inline]
394    fn try_update<F>(&self, update: F) -> Option<usize>
395    where
396        F: FnMut(usize) -> Option<usize>,
397    {
398        try_update_atomic_count(
399            || self.inner.load(Ordering::Acquire),
400            |current, next| {
401                self.inner.compare_exchange_weak(
402                    current,
403                    next,
404                    Ordering::AcqRel,
405                    Ordering::Acquire,
406                )
407            },
408            update,
409        )
410    }
411}
412
413impl Default for AtomicCount {
414    /// Creates a zero-valued atomic counter.
415    ///
416    /// # Returns
417    ///
418    /// A counter whose current value is zero.
419    #[inline(always)]
420    fn default() -> Self {
421        Self::zero()
422    }
423}
424
425impl From<usize> for AtomicCount {
426    /// Converts an initial counter value into an [`AtomicCount`].
427    ///
428    /// # Parameters
429    ///
430    /// * `value` - The initial counter value.
431    ///
432    /// # Returns
433    ///
434    /// A counter initialized to `value`.
435    #[inline(always)]
436    fn from(value: usize) -> Self {
437        Self::new(value)
438    }
439}
440
441impl fmt::Debug for AtomicCount {
442    /// Formats the current counter value for debugging.
443    ///
444    /// # Parameters
445    ///
446    /// * `f` - The formatter receiving the debug representation.
447    ///
448    /// # Returns
449    ///
450    /// A formatting result from the formatter.
451    #[inline]
452    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453        f.debug_struct("AtomicCount")
454            .field("value", &self.get())
455            .finish()
456    }
457}
458
459impl fmt::Display for AtomicCount {
460    /// Formats the current counter value with decimal display formatting.
461    ///
462    /// # Parameters
463    ///
464    /// * `f` - The formatter receiving the displayed value.
465    ///
466    /// # Returns
467    ///
468    /// A formatting result from the formatter.
469    #[inline]
470    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
471        write!(f, "{}", self.get())
472    }
473}