Skip to main content

dynomite/msg/
index.rs

1//! Message-id to message lookup.
2//!
3//! A per-connection lookup keyed on `MsgId` pairs a response
4//! arriving on the wire with its outstanding request in constant
5//! time. [`crate::util::dict::MsgIndex`] (an `ahash`-backed
6//! [`std::collections::HashMap`]) keyed on [`MsgId`] provides the
7//! message-flavored alias plus a thin newtype that adds
8//! the verbs the message layer actually uses.
9//!
10//! [`MsgId`]: crate::core::types::MsgId
11
12use crate::core::types::MsgId;
13use crate::util::dict::DictMap;
14
15use super::message::Msg;
16
17/// Owning index of [`Msg`] values keyed on [`MsgId`].
18///
19/// The index owns each [`Msg`] value directly, so dropping the index
20/// releases every contained message. Lookups return references;
21/// transferring ownership out of the index requires
22/// [`MsgIndex::remove`].
23///
24/// # Thread safety
25///
26/// `MsgIndex` is per-connection and accessed only from the
27/// connection's owning event-loop thread. It honours that
28/// single-threaded contract: `MsgIndex` is `Send` (it can be moved
29/// to another task or thread, e.g. when a connection migrates) but
30/// is intentionally not exposed through any synchronisation
31/// primitive. The wrapped [`std::collections::HashMap`] inside
32/// [`DictMap`] is not `Sync`, so two
33/// tasks cannot share a `&MsgIndex` and call its mutating methods
34/// concurrently. Stages 9 and beyond keep the index private to the
35/// per-connection FSM; if a future caller ever needs cross-thread
36/// shared access, that caller is responsible for wrapping it in a
37/// `Mutex` (or equivalent), not the type itself.
38#[derive(Debug)]
39pub struct MsgIndex {
40    inner: DictMap<MsgId, Msg>,
41}
42
43impl Default for MsgIndex {
44    fn default() -> Self {
45        Self {
46            inner: DictMap::new(),
47        }
48    }
49}
50
51impl MsgIndex {
52    /// Build an empty index.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use dynomite::msg::MsgIndex;
58    /// let idx = MsgIndex::new();
59    /// assert!(idx.is_empty());
60    /// ```
61    #[must_use]
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// Insert `msg` under its own id. The previous value, if any, is
67    /// returned to the caller.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use dynomite::msg::{Msg, MsgIndex, MsgType};
73    ///
74    /// let mut idx = MsgIndex::new();
75    /// let m = Msg::new(7, MsgType::ReqRedisGet, true);
76    /// assert!(idx.insert(m).is_none());
77    /// assert!(idx.contains_key(7));
78    /// ```
79    pub fn insert(&mut self, msg: Msg) -> Option<Msg> {
80        self.inner.insert(msg.id(), msg)
81    }
82
83    /// Remove the message stored under `id` and transfer ownership
84    /// to the caller.
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use dynomite::msg::{Msg, MsgIndex, MsgType};
90    ///
91    /// let mut idx = MsgIndex::new();
92    /// idx.insert(Msg::new(7, MsgType::ReqRedisGet, true));
93    /// assert!(idx.remove(7).is_some());
94    /// assert!(idx.remove(7).is_none());
95    /// ```
96    pub fn remove(&mut self, id: MsgId) -> Option<Msg> {
97        self.inner.remove(&id)
98    }
99
100    /// Borrow the message stored under `id`, if any.
101    ///
102    /// # Examples
103    ///
104    /// ```
105    /// use dynomite::msg::{Msg, MsgIndex, MsgType};
106    ///
107    /// let mut idx = MsgIndex::new();
108    /// idx.insert(Msg::new(7, MsgType::ReqRedisGet, true));
109    /// assert_eq!(idx.get(7).unwrap().id(), 7);
110    /// ```
111    #[must_use]
112    pub fn get(&self, id: MsgId) -> Option<&Msg> {
113        self.inner.get(&id)
114    }
115
116    /// Mutably borrow the message stored under `id`.
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// use dynomite::msg::{Msg, MsgIndex, MsgType};
122    ///
123    /// let mut idx = MsgIndex::new();
124    /// idx.insert(Msg::new(7, MsgType::ReqRedisGet, true));
125    /// idx.get_mut(7).unwrap().set_done(true);
126    /// ```
127    pub fn get_mut(&mut self, id: MsgId) -> Option<&mut Msg> {
128        self.inner.get_mut(&id)
129    }
130
131    /// True when an entry exists for `id`.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use dynomite::msg::MsgIndex;
137    /// assert!(!MsgIndex::new().contains_key(0));
138    /// ```
139    #[must_use]
140    pub fn contains_key(&self, id: MsgId) -> bool {
141        self.inner.contains_key(&id)
142    }
143
144    /// Number of indexed messages.
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use dynomite::msg::MsgIndex;
150    /// assert_eq!(MsgIndex::new().len(), 0);
151    /// ```
152    #[must_use]
153    pub fn len(&self) -> usize {
154        self.inner.len()
155    }
156
157    /// True when the index has no entries.
158    ///
159    /// # Examples
160    ///
161    /// ```
162    /// use dynomite::msg::MsgIndex;
163    /// assert!(MsgIndex::new().is_empty());
164    /// ```
165    #[must_use]
166    pub fn is_empty(&self) -> bool {
167        self.inner.is_empty()
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use crate::msg::{Msg, MsgType};
175
176    #[test]
177    fn round_trip() {
178        let mut idx = MsgIndex::new();
179        let m = Msg::new(42, MsgType::ReqRedisGet, true);
180        assert!(idx.insert(m).is_none());
181        assert!(idx.contains_key(42));
182        let popped = idx.remove(42).expect("present");
183        assert_eq!(popped.id(), 42);
184        assert!(!idx.contains_key(42));
185    }
186
187    #[test]
188    fn get_and_get_mut_borrow_the_entry() {
189        // get borrows immutably; get_mut allows in-place mutation.
190        let mut idx = MsgIndex::new();
191        idx.insert(Msg::new(7, MsgType::ReqRedisGet, true));
192        assert_eq!(idx.get(7).unwrap().id(), 7);
193        assert!(idx.get(8).is_none());
194        idx.get_mut(7).unwrap().set_done(true);
195        assert!(idx.get(7).unwrap().flags().done);
196        assert!(idx.get_mut(8).is_none());
197    }
198
199    #[test]
200    fn len_and_is_empty_track_population() {
201        // len counts entries; is_empty flips once an entry exists.
202        let mut idx = MsgIndex::new();
203        assert!(idx.is_empty());
204        assert_eq!(idx.len(), 0);
205        idx.insert(Msg::new(1, MsgType::ReqRedisGet, true));
206        assert!(!idx.is_empty());
207        assert_eq!(idx.len(), 1);
208    }
209}