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}