Skip to main content

elfo_core/
addr.rs

1use std::{
2    fmt,
3    num::{NonZeroU16, NonZeroU64, NonZeroU8},
4};
5
6use derive_more::Display;
7use idr_ebr::Key;
8use serde::{Deserialize, Serialize};
9
10// === NodeNo ===
11
12/// Represents the node's number in a distributed system.
13/// Cannot be `0`, it's reserved to represent the local node.
14///
15/// Nodes with the same `node_no` cannot be connected.
16///
17/// NOTE: It's 16-bit unsigned integer, which requires manual management for
18/// bigger-than-small clusters and will be replaced with [`NodeLaunchId`]
19/// totally in the future in order to simplify the management.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[derive(Display, Serialize, Deserialize)]
22pub struct NodeNo(NonZeroU16);
23
24impl NodeNo {
25    pub(crate) fn generate() -> Self {
26        Self::from_bits((random_u64() as u16).max(1)).unwrap()
27    }
28
29    #[instability::unstable]
30    #[inline]
31    pub const fn from_bits(bits: u16) -> Option<Self> {
32        match NonZeroU16::new(bits) {
33            Some(node_no) => Some(Self(node_no)),
34            None => None,
35        }
36    }
37
38    #[instability::unstable]
39    #[inline]
40    pub const fn into_bits(self) -> u16 {
41        self.0.get()
42    }
43}
44
45// === NodeLaunchId ===
46
47/// Randomly generated identifier at the node start.
48///
49/// Used for several purposes:
50/// * To distinguish between different launches of the same node.
51/// * To detect reusing of the same node no.
52/// * To improve [`Addr`] uniqueness in the cluster.
53#[instability::stable(since = "v0.2.0")]
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
55pub struct NodeLaunchId(NonZeroU64);
56
57impl NodeLaunchId {
58    pub(crate) fn generate() -> Self {
59        Self::from_bits(random_u64().max(1)).unwrap()
60    }
61
62    #[instability::unstable]
63    #[inline]
64    pub fn from_bits(bits: u64) -> Option<Self> {
65        NonZeroU64::new(bits).map(Self)
66    }
67
68    #[instability::unstable]
69    #[inline]
70    pub fn into_bits(self) -> u64 {
71        self.0.get()
72    }
73}
74
75// === GroupNo ===
76
77/// Represents the actor group's number.
78///
79/// Cannot be `0`, it's reserved to represent `Addr::NULL` unambiguously.
80/// XORed with random [`NodeLaunchId`] if the `network` feature is enabled.
81#[instability::stable(since = "v0.2.0")]
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
83#[derive(Display, Serialize, Deserialize)]
84pub struct GroupNo(NonZeroU8);
85
86impl GroupNo {
87    #[cfg(feature = "network-2")] // TODO(loyd): enable after fixing reconnects
88    pub(crate) fn new(no: u8, launch_id: NodeLaunchId) -> Option<Self> {
89        if no == 0 {
90            return None;
91        }
92
93        let xor = (launch_id.into_bits() >> GROUP_NO_SHIFT) as u8;
94
95        // `no = 0` is forbidden, thus there is no mapping to just `xor`.
96        let group_no = if no != xor { no ^ xor } else { xor };
97
98        Some(Self(NonZeroU8::new(group_no).unwrap()))
99    }
100
101    #[cfg(not(feature = "network-2"))]
102    pub(crate) fn new(no: u8, _launch_id: NodeLaunchId) -> Option<Self> {
103        NonZeroU8::new(no).map(Self)
104    }
105
106    #[instability::unstable]
107    #[inline]
108    pub fn from_bits(bits: u8) -> Option<Self> {
109        NonZeroU8::new(bits).map(Self)
110    }
111
112    #[instability::unstable]
113    #[inline]
114    pub fn into_bits(self) -> u8 {
115        self.0.get()
116    }
117}
118
119// === Addr ===
120
121/// Represents the global, usually unique address of an actor or a group.
122///
123/// # Uniqueness
124///
125/// An address is based on an [IDR] to make it a simple sendable number
126/// (as opposed to reference counting) and provide better performance of lookups
127/// than hashmaps. However, it means deletions and insertions to the same
128/// underlying slot multiple times can lead to reusing the address for a
129/// different actor.
130///
131/// Elfo tries to do its best to ensure the uniqueness of this value:
132/// * Alive actors on the same node always have different addresses.
133/// * Actors in different nodes have different address spaces.
134/// * Actors in different groups have different address spaces.
135/// * An address includes the version number to guard against the [ABA] problem.
136/// * An address is randomized between restarts of the same node if the
137///   `network` feature is enabled.
138///
139/// # Using addresses in messages
140///
141/// The current implementation of network depends on the fact that
142/// `Addr` cannot be sent inside messages. It prevents from different
143/// possible errors like responding without having a valid connection.
144/// The only way to get an address of remote actor is `envelope.sender()`.
145/// If sending `Addr` inside a message is unavoidable, use `Local<Addr>`,
146/// however it won't be possible to send such message to a remote actor.
147///
148/// [IDR]: https://crates.io/crates/idr-ebr
149/// [ABA]: https://en.wikipedia.org/wiki/ABA_problem
150// ~
151// The structure:
152//  64           48         40           25                       0
153//  ┌────────────┬──────────┬────────────┬────────────────────────┐
154//  │   node_no  │ group_no │ generation │    page_no + slot_no   │
155//  │     16b    │    8b    │     15b    │           25b          │
156//  └────────────┴──────────┴────────────┴────────────────────────┘
157//   (0 if local)           └─────────── IDR key (40b) ───────────┘
158//
159//
160// Limits:
161// - max nodes in a cluster                        65'535  (1)
162// - max groups in a node                             255  (2, 3)
163// - max active actors                         33'554'400
164// - slot generations to prevent ABA               32'768
165//
166// 1. `0` is reserved to represent the local node.
167// 2. `0` is reserved to represent `Addr::NULL` unambiguously.
168// 3. at least one group (for `system.init`) is always present.
169//
170// If the `network` feature is enabled, bottom 48 bits are XORed with the current node's launch
171// number, generated at startup. It ensures that the same actor on different launches of the same
172// node will have different addresses. The original address is never printed or even represented
173// and the slot key part is restored only by calling private `Addr::slot_key(launch_no)`.
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
175pub struct Addr(u64); // TODO: make it `NonZeroU64` instead of `Addr::NULL`
176
177const NODE_NO_SHIFT: u32 = 48;
178const GROUP_NO_SHIFT: u32 = 40;
179
180// See `Addr` docs for details.
181assert_not_impl_all!(Addr: Serialize, Deserialize<'static>);
182
183impl fmt::Display for Addr {
184    #[inline]
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        if self.is_null() {
187            return f.write_str("null");
188        }
189
190        let group_no = self.group_no().expect("invalid addr");
191        let bottom = self.0 & ((1 << GROUP_NO_SHIFT) - 1);
192
193        if let Some(node_no) = self.node_no() {
194            write!(f, "{node_no}/{group_no}/{bottom}")
195        } else {
196            write!(f, "{group_no}/{bottom}")
197        }
198    }
199}
200
201impl Addr {
202    #[instability::unstable]
203    pub const NULL: Addr = Addr(0);
204
205    #[cfg(feature = "network")]
206    pub(crate) fn new_local(slot_key: Key, group_no: GroupNo, launch_id: NodeLaunchId) -> Self {
207        let slot_key = u64::from(slot_key);
208        debug_assert!(slot_key < (1 << GROUP_NO_SHIFT));
209        let slot_key = (slot_key ^ launch_id.into_bits()) & ((1 << GROUP_NO_SHIFT) - 1);
210        Self::new_local_inner(slot_key, group_no)
211    }
212
213    #[cfg(not(feature = "network"))]
214    pub(crate) fn new_local(slot_key: Key, group_no: GroupNo, _launch_id: NodeLaunchId) -> Self {
215        let slot_key = u64::from(slot_key);
216        debug_assert!(slot_key < (1 << GROUP_NO_SHIFT));
217        Self::new_local_inner(slot_key, group_no)
218    }
219
220    fn new_local_inner(slot_key: u64, group_no: GroupNo) -> Self {
221        Self((u64::from(group_no.into_bits()) << GROUP_NO_SHIFT) | slot_key)
222    }
223
224    #[instability::unstable]
225    #[inline]
226    pub fn from_bits(bits: u64) -> Option<Self> {
227        Some(Self(bits)).filter(|addr| addr.is_null() ^ addr.group_no().is_some())
228    }
229
230    #[instability::unstable]
231    #[inline]
232    pub fn into_bits(self) -> u64 {
233        self.0
234    }
235
236    #[inline]
237    pub fn is_null(self) -> bool {
238        self == Self::NULL
239    }
240
241    #[inline]
242    pub fn is_local(self) -> bool {
243        !self.is_null() && self.node_no().is_none()
244    }
245
246    #[cfg(feature = "network")]
247    #[inline]
248    pub fn is_remote(self) -> bool {
249        self.node_no().is_some()
250    }
251
252    #[instability::unstable]
253    #[inline]
254    pub fn node_no(self) -> Option<NodeNo> {
255        NodeNo::from_bits((self.0 >> NODE_NO_SHIFT) as u16)
256    }
257
258    #[instability::unstable]
259    #[inline]
260    pub fn group_no(self) -> Option<GroupNo> {
261        GroupNo::from_bits((self.0 >> GROUP_NO_SHIFT) as u8)
262    }
263
264    #[cfg(feature = "network")]
265    pub(crate) fn node_no_group_no(self) -> u32 {
266        (self.0 >> GROUP_NO_SHIFT) as u32
267    }
268
269    #[cfg(feature = "network")]
270    pub(crate) fn slot_key(self, launch_id: NodeLaunchId) -> Option<Key> {
271        // IDR uses the lower bits only, so we can xor the whole address.
272        (self.0 ^ launch_id.into_bits()).try_into().ok()
273    }
274
275    #[cfg(not(feature = "network"))]
276    pub(crate) fn slot_key(self, _launch_id: NodeLaunchId) -> Option<Key> {
277        self.0.try_into().ok()
278    }
279
280    #[cfg(feature = "network")]
281    #[instability::unstable]
282    #[inline]
283    pub fn into_remote(self, node_no: NodeNo) -> Self {
284        if self.is_local() {
285            Self(self.0 | (u64::from(node_no.into_bits()) << NODE_NO_SHIFT))
286        } else {
287            self
288        }
289    }
290
291    #[instability::unstable]
292    #[inline]
293    pub fn into_local(self) -> Self {
294        Self(self.0 & ((1 << NODE_NO_SHIFT) - 1))
295    }
296}
297
298// === IdrConfig ===
299
300pub(crate) struct IdrConfig;
301
302impl idr_ebr::Config for IdrConfig {
303    const INITIAL_PAGE_SIZE: u32 = 32;
304    const MAX_PAGES: u32 = 20;
305    const RESERVED_BITS: u32 = 24;
306}
307const_assert_eq!(
308    idr_ebr::Idr::<crate::object::Object, IdrConfig>::USED_BITS,
309    GROUP_NO_SHIFT
310);
311
312// === random_u64 ===
313
314fn random_u64() -> u64 {
315    use std::{
316        collections::hash_map::RandomState,
317        hash::{BuildHasher, Hash, Hasher},
318        thread,
319        time::Instant,
320    };
321
322    let mut hasher = RandomState::new().build_hasher();
323    0xE1F0E1F0E1F0E1F0u64.hash(&mut hasher);
324    Instant::now().hash(&mut hasher);
325    thread::current().id().hash(&mut hasher);
326    hasher.finish()
327}
328
329#[cfg(test)]
330mod tests {
331    use std::collections::HashSet;
332
333    use proptest::prelude::*;
334
335    use super::*;
336
337    #[test]
338    fn node_no_generate() {
339        for _ in 0..1_000_000 {
340            NodeNo::generate();
341        }
342    }
343
344    #[test]
345    fn node_launch_id_generate() {
346        let count = 10;
347        let set: HashSet<_> = (0..count).map(|_| NodeLaunchId::generate()).collect();
348        assert_eq!(set.len(), count);
349    }
350
351    #[test]
352    fn group_no() {
353        let launch_ids = (0..5).map(|_| NodeLaunchId::generate()).collect::<Vec<_>>();
354
355        for launch_id in launch_ids {
356            // no = 0 is always invalid.
357            assert_eq!(GroupNo::new(0, launch_id), None);
358
359            // `GroupNo` is unique for any `NodeLaunchId`.
360            let set = (1..=u8::MAX)
361                .map(|no| GroupNo::new(no, launch_id).unwrap())
362                .collect::<HashSet<_>>();
363
364            assert_eq!(set.len(), usize::from(u8::MAX));
365        }
366    }
367
368    proptest! {
369        #[test]
370        fn addr(
371            slot_keys in prop::collection::hash_set(1u64..(1 << GROUP_NO_SHIFT), 10),
372            group_nos in prop::collection::hash_set(1..=u8::MAX, 10),
373            launch_ids in prop::collection::hash_set(1..=u64::MAX, 10),
374        ) {
375            #[cfg(feature = "network")]
376            let expected_count = slot_keys.len() * group_nos.len() * launch_ids.len();
377            #[cfg(not(feature = "network"))]
378            let expected_count = slot_keys.len() * group_nos.len();
379
380            let mut set = HashSet::with_capacity(expected_count);
381
382            for slot_key in &slot_keys {
383                for group_no in &group_nos {
384                    for launch_id in &launch_ids {
385                        let slot_key = Key::try_from(*slot_key).unwrap();
386                        let launch_id = NodeLaunchId::from_bits(*launch_id).unwrap();
387                        let group_no = GroupNo::new(*group_no, launch_id).unwrap();
388                        let addr = Addr::new_local(slot_key, group_no, launch_id);
389                        set.insert(addr);
390
391                        prop_assert!(!addr.is_null());
392                        prop_assert!(addr.is_local());
393                        prop_assert_eq!(addr.group_no(), Some(group_no));
394                        prop_assert_eq!(addr.node_no(), None);
395
396                        let actual_slot_key = u64::from(addr.slot_key(launch_id).unwrap());
397                        prop_assert_eq!(actual_slot_key & ((1 << GROUP_NO_SHIFT) - 1), u64::from(slot_key));
398                        prop_assert_eq!(addr.into_local(), addr);
399                        prop_assert_eq!(Addr::from_bits(addr.into_bits()), Some(addr));
400                        prop_assert_eq!(addr.to_string().split('/').count(), 2);
401                        prop_assert!(addr.to_string().starts_with(&group_no.to_string()));
402
403                        #[cfg(feature = "network")]
404                        {
405                            prop_assert!(!addr.is_remote());
406                            let node_no = NodeNo::from_bits(42).unwrap();
407                            let remote = addr.into_remote(node_no);
408                            prop_assert!(!remote.is_null());
409                            prop_assert!(!remote.is_local());
410                            prop_assert!(remote.is_remote());
411                            prop_assert_eq!(remote.group_no(), Some(group_no));
412                            prop_assert_eq!(remote.node_no(), Some(node_no));
413                            prop_assert_eq!(addr.into_local(), addr);
414                            prop_assert_eq!(remote.node_no_group_no() >> 8, u32::from(node_no.into_bits()));
415                            prop_assert_eq!(remote.node_no_group_no() & 0xff, u32::from(group_no.into_bits()));
416                            prop_assert_eq!(remote.to_string().split('/').count(), 3);
417                            prop_assert!(remote.to_string().starts_with(&node_no.to_string()));
418                        }
419                    }
420                }
421            }
422
423            // Check uniqueness.
424            prop_assert_eq!(set.len(), expected_count);
425        }
426    }
427
428    #[test]
429    fn addr_null() {
430        let null = Addr::NULL;
431        assert_eq!(null.to_string(), "null");
432        assert!(null.is_null());
433        assert_eq!(null.into_local(), null);
434        assert_eq!(null.group_no(), None);
435        assert_eq!(null.node_no(), None);
436        #[cfg(feature = "network")]
437        {
438            assert!(!null.is_remote());
439            assert_eq!(null.into_remote(NodeNo::from_bits(42).unwrap()), null);
440            assert_eq!(null.node_no_group_no(), 0);
441        }
442    }
443
444    #[test]
445    fn addr_invalid() {
446        assert_eq!(Addr::from_bits(1), None);
447    }
448}