Skip to main content

net_io/
id.rs

1//! Connection, listener, and worker identities.
2
3use std::fmt;
4use std::num::NonZeroU64;
5
6use std::sync::atomic::{AtomicU64, Ordering};
7
8/// The identity of one connection, unique and never reused within a process.
9///
10/// Workers take blocks of 1,024 ids from one process-wide counter, so ids are
11/// ordered within a worker, not globally.
12///
13/// # Examples
14///
15/// ```
16/// let id = net_io::ConnectionId::new(42).unwrap();
17/// assert_eq!(id.get(), 42);
18/// assert!(net_io::ConnectionId::new(0).is_none());
19/// ```
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
21pub struct ConnectionId(NonZeroU64);
22
23impl ConnectionId {
24    /// The id as a number.
25    #[inline]
26    #[must_use]
27    pub const fn get(self) -> u64 {
28        self.0.get()
29    }
30
31    /// An id from a number, for tests and conversions; `None` for 0.
32    ///
33    /// An id built this way is not reserved: use it only to name a connection
34    /// net-io issued, or in tests.
35    #[must_use]
36    pub const fn new(id: u64) -> Option<ConnectionId> {
37        match NonZeroU64::new(id) {
38            Some(n) => Some(ConnectionId(n)),
39            None => None,
40        }
41    }
42}
43
44impl fmt::Display for ConnectionId {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        write!(f, "#{}", self.0)
47    }
48}
49
50/// The index of a listener within one server.
51///
52/// ```
53/// # fn main() -> Result<(), net_io::NetError> {
54/// let server = net_io::serve("127.0.0.1:0", |_conn| Ok(()))?;
55/// assert_eq!(server.listeners()[0].id.get(), 0);
56/// # server.handle().request_stop(); let _ = server.wait_for_stop(); Ok(()) }
57/// ```
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
59pub struct ListenerId(pub(crate) u16);
60
61impl ListenerId {
62    /// The index as a number.
63    #[must_use]
64    pub const fn get(self) -> u16 {
65        self.0
66    }
67}
68
69/// The index of a worker within one server.
70///
71/// ```
72/// # fn main() -> Result<(), net_io::NetError> {
73/// let server = net_io::serve("127.0.0.1:0", |_conn| Ok(()))?;
74/// assert!(server.handle().completer(net_io::WorkerId::first()).is_some());
75/// # server.handle().request_stop(); let _ = server.wait_for_stop(); Ok(()) }
76/// ```
77#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
78pub struct WorkerId(pub(crate) u16);
79
80impl WorkerId {
81    /// The index as a number.
82    #[must_use]
83    pub const fn get(self) -> u16 {
84        self.0
85    }
86
87    /// Worker 0.
88    #[must_use]
89    pub const fn first() -> WorkerId {
90        WorkerId(0)
91    }
92}
93
94/// Ids handed out per block.
95pub(crate) const BLOCK: u64 = 1024;
96
97/// The process-wide counter of ids. Starts at 1 so id 0 is never issued; at one
98/// million ids per second it would take about 584,000 years to wrap.
99static NEXT: AtomicU64 = AtomicU64::new(1);
100
101/// A block of ids owned by one worker.
102#[derive(Debug)]
103pub(crate) struct IdBlock {
104    next: u64,
105    end: u64,
106}
107
108impl IdBlock {
109    /// An empty block; the first `issue` takes a block from the counter.
110    pub(crate) const fn new() -> IdBlock {
111        IdBlock { next: 0, end: 0 }
112    }
113
114    /// The next id; `take` returns the start of a fresh block when this one is used up.
115    pub(crate) fn issue_with(&mut self, take: impl FnOnce() -> u64) -> ConnectionId {
116        if self.next >= self.end {
117            let start = take();
118            self.next = start;
119            self.end = start.saturating_add(BLOCK);
120        }
121        let id = self.next;
122        self.next += 1;
123        to_id(id)
124    }
125
126    /// The next id from the process-wide counter.
127    pub(crate) fn issue(&mut self) -> ConnectionId {
128        self.issue_with(|| NEXT.fetch_add(BLOCK, Ordering::Relaxed))
129    }
130}
131
132fn to_id(n: u64) -> ConnectionId {
133    match NonZeroU64::new(n) {
134        Some(n) => ConnectionId(n),
135        // The counter starts at 1 and only grows, so this arm is not reached.
136        None => ConnectionId(NonZeroU64::MIN),
137    }
138}
139
140/// One id for a caller without a block (standalone connections, tests).
141pub(crate) fn issue_single() -> ConnectionId {
142    to_id(NEXT.fetch_add(1, Ordering::Relaxed))
143}
144
145#[cfg(all(test, not(loom)))]
146mod tests {
147    use super::*;
148    use std::collections::HashSet;
149
150    #[test]
151    fn test_id_zero_never_issued() {
152        let counter = AtomicU64::new(1);
153        let mut block = IdBlock::new();
154        let first = block.issue_with(|| counter.fetch_add(BLOCK, Ordering::Relaxed));
155        assert_eq!(first.get(), 1);
156    }
157
158    #[test]
159    fn test_id_block_takes_new_block_after_1024() {
160        let counter = AtomicU64::new(1);
161        let mut block = IdBlock::new();
162        for _ in 0..BLOCK {
163            block.issue_with(|| counter.fetch_add(BLOCK, Ordering::Relaxed));
164        }
165        let next = block.issue_with(|| counter.fetch_add(BLOCK, Ordering::Relaxed));
166        assert_eq!(next.get(), 1 + BLOCK);
167        assert_eq!(counter.load(Ordering::Relaxed), 1 + 2 * BLOCK);
168    }
169
170    #[test]
171    fn test_ids_unique_across_threads() {
172        let handles: Vec<_> = (0..8)
173            .map(|_| {
174                std::thread::spawn(|| {
175                    let mut block = IdBlock::new();
176                    (0..5000).map(|_| block.issue().get()).collect::<Vec<_>>()
177                })
178            })
179            .collect();
180        let mut all = HashSet::new();
181        for h in handles {
182            for id in h.join().unwrap() {
183                assert!(all.insert(id), "id {id} issued twice");
184            }
185        }
186        for _ in 0..100 {
187            assert!(all.insert(issue_single().get()));
188        }
189    }
190
191    #[test]
192    fn test_connection_id_new_and_display() {
193        assert!(ConnectionId::new(0).is_none());
194        assert_eq!(ConnectionId::new(9).unwrap().to_string(), "#9");
195    }
196}
197
198#[cfg(all(test, loom))]
199mod loom_tests {
200    use super::*;
201    use loom::sync::Arc;
202
203    #[test]
204    fn loom_id_blocks_never_overlap() {
205        loom::model(|| {
206            let counter = Arc::new(loom::sync::atomic::AtomicU64::new(1));
207            let c2 = counter.clone();
208            let t = loom::thread::spawn(move || {
209                let mut b = IdBlock::new();
210                b.issue_with(|| c2.fetch_add(BLOCK, loom::sync::atomic::Ordering::Relaxed))
211                    .get()
212            });
213            let mut b = IdBlock::new();
214            let a = b
215                .issue_with(|| counter.fetch_add(BLOCK, loom::sync::atomic::Ordering::Relaxed))
216                .get();
217            let other = t.join().unwrap();
218            assert_ne!(a, other);
219            assert!(a.abs_diff(other) >= BLOCK);
220        });
221    }
222}