nu_protocol/engine/
sequence.rs

1use crate::ShellError;
2use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
3
4/// Implements an atomically incrementing sequential series of numbers
5#[derive(Debug, Default)]
6pub struct Sequence(AtomicUsize);
7
8impl Sequence {
9    /// Return the next available id from a sequence, returning an error on overflow
10    #[track_caller]
11    pub fn next(&self) -> Result<usize, ShellError> {
12        // It's totally safe to use Relaxed ordering here, as there aren't other memory operations
13        // that depend on this value having been set for safety
14        //
15        // We're only not using `fetch_add` so that we can check for overflow, as wrapping with the
16        // identifier would lead to a serious bug - however unlikely that is.
17        self.0
18            .fetch_update(Relaxed, Relaxed, |current| current.checked_add(1))
19            .map_err(|_| ShellError::NushellFailedHelp {
20                msg: "an accumulator for identifiers overflowed".into(),
21                help: format!("see {}", std::panic::Location::caller()),
22            })
23    }
24}
25
26#[test]
27fn output_is_sequential() {
28    let sequence = Sequence::default();
29
30    for (expected, generated) in (0..1000).zip(std::iter::repeat_with(|| sequence.next())) {
31        assert_eq!(expected, generated.expect("error in sequence"));
32    }
33}
34
35#[test]
36fn output_is_unique_even_under_contention() {
37    let sequence = Sequence::default();
38
39    std::thread::scope(|scope| {
40        // Spawn four threads, all advancing the sequence simultaneously
41        let threads = (0..4)
42            .map(|_| {
43                scope.spawn(|| {
44                    (0..100000)
45                        .map(|_| sequence.next())
46                        .collect::<Result<Vec<_>, _>>()
47                })
48            })
49            .collect::<Vec<_>>();
50
51        // Collect all of the results into a single flat vec
52        let mut results = threads
53            .into_iter()
54            .flat_map(|thread| thread.join().expect("panicked").expect("error"))
55            .collect::<Vec<usize>>();
56
57        // Check uniqueness
58        results.sort();
59        let initial_length = results.len();
60        results.dedup();
61        let deduplicated_length = results.len();
62        assert_eq!(initial_length, deduplicated_length);
63    })
64}