Skip to main content

dynomite/msg/
index.rs

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