1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use std::sync::atomic::Ordering;

use super::{BLOCK_SIZE, COUNTER, DISCRETE_VALUES};
use crate::error::CuidError;
use crate::text::{pad, to_base_string};

/// Fetch the counter value and increment it.
///
/// If the counter has reached its max (DISCRETE VALUES), reset it to 0.
fn fetch_and_increment() -> Result<u32, CuidError> {
    COUNTER
        .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |i| match i {
            i if i == DISCRETE_VALUES - 1 => Some(0),
            _ => Some(i + 1),
        })
        .map_err(|_| CuidError::CounterError)
}

/// Return the current counter value in the appropriate base as a String.
pub fn current() -> Result<String, CuidError> {
    fetch_and_increment()
        .map(to_base_string)?
        .map(|s| pad(BLOCK_SIZE, s))
}

#[cfg(test)]
mod tests {
    use super::*;

    // These tests are ignored because they depend on global state and must be
    // run with --test-threads=1 in order to work.

    #[test]
    #[ignore]
    fn counter_increasing_basic() {
        let start = 0;
        COUNTER.store(start, Ordering::SeqCst);
        // Tests run in parallel, so we're not necessarily guaranteed
        // consistent ordering in the context of a test.
        let first = fetch_and_increment().unwrap();
        assert!(first >= start);
        let second = fetch_and_increment().unwrap();
        assert!(second > first);
        let third = fetch_and_increment().unwrap();
        assert!(third > second);
    }

    #[test]
    #[ignore]
    fn counter_is_monotonic_with_increasing_length() {
        // Ensure that counter is lexigraphically-monotonic even if the counter length in base-36
        // increases (1 digit -> 2 digits).
        let start = 35;
        COUNTER.store(start, Ordering::SeqCst);
        // Tests run in parallel, so we're not necessarily guaranteed
        // consistent ordering in the context of a test.
        // Prefer running tests with: cargo test -- --test-threads=1
        let first = current().unwrap();
        let second = current().unwrap();
        assert!(second > first);
    }

    #[test]
    #[ignore]
    fn counter_increasing_rollover() {
        let max = DISCRETE_VALUES - 1;
        COUNTER.store(max, Ordering::SeqCst);
        // Tests run in parallel, so we're not necessarily guaranteed
        // consistent ordering in the context of a test.
        fetch_and_increment().unwrap(); // will return the max unless another thread rolled us over
        let rolled_over = fetch_and_increment().unwrap(); // must be rolled over

        assert!(rolled_over < max);
    }

    // TODO: Multi-thread counter tests
}

#[cfg(nightly)]
#[cfg(test)]
mod benchmarks {
    use super::*;
    use test::Bencher;

    #[bench]
    fn basic_increment(b: &mut Bencher) {
        b.iter(|| fetch_and_increment())
    }
}