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}