Skip to main content

dynomite/msg/
mod.rs

1//! Message model: the in-memory shape every request and response
2//! takes inside the engine, plus the queues, indices, and per-DC
3//! response managers that thread them through the dispatcher.
4//!
5//! This module exposes the data layer: the message shape, queues,
6//! indices, and per-DC response managers, plus the data-only helpers
7//! (error response construction, fragment bookkeeping, quorum
8//! decisions). The connection-coupled lifecycle (recv/send, timeout
9//! queues, peer forwarding) lives in [`crate::net`].
10//!
11//! # Examples
12//!
13//! ```
14//! use dynomite::msg::{Msg, MsgQueue, MsgType, ResponseMgr};
15//!
16//! let mut q = MsgQueue::new();
17//! q.push_back(Msg::new(1, MsgType::ReqRedisGet, true));
18//!
19//! let req = q.front().unwrap();
20//! let mgr = ResponseMgr::new(req, 1, None);
21//! assert_eq!(mgr.quorum_responses(), 1);
22//! ```
23
24use std::sync::OnceLock;
25
26pub mod index;
27pub mod keypos;
28pub mod message;
29pub mod msg_type;
30pub mod queue;
31pub mod request;
32pub mod response;
33pub mod response_mgr;
34
35pub use self::index::MsgIndex;
36pub use self::keypos::{ArgPos, KeyPos};
37pub use self::message::{ConnId, Msg, MsgFlags, MsgParseResult, MsgRouting};
38pub use self::msg_type::MsgType;
39pub use self::queue::MsgQueue;
40pub use self::response_mgr::{QuorumOutcome, ResponseMgr, MAX_REPLICAS_PER_DC};
41
42/// Cluster consistency level applied to a single message.
43///
44/// The numeric values are stable wire codes for the consistency
45/// level.
46#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
47#[repr(u8)]
48pub enum ConsistencyLevel {
49    /// Wait for one replica to ack.
50    #[default]
51    DcOne = 0,
52    /// Wait for a per-DC majority.
53    DcQuorum = 1,
54    /// `DcQuorum` with body checksum agreement; mismatches trigger
55    /// read repair.
56    DcSafeQuorum = 2,
57    /// `DcSafeQuorum` evaluated independently per datacenter.
58    DcEachSafeQuorum = 3,
59}
60
61impl ConsistencyLevel {
62    /// Stable string name.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use dynomite::msg::ConsistencyLevel;
68    /// assert_eq!(ConsistencyLevel::DcQuorum.name(), "DC_QUORUM");
69    /// ```
70    #[must_use]
71    pub fn name(self) -> &'static str {
72        match self {
73            ConsistencyLevel::DcOne => "DC_ONE",
74            ConsistencyLevel::DcQuorum => "DC_QUORUM",
75            ConsistencyLevel::DcSafeQuorum => "DC_SAFE_QUORUM",
76            ConsistencyLevel::DcEachSafeQuorum => "DC_EACH_SAFE_QUORUM",
77        }
78    }
79
80    /// Recover the level from its uppercase name.
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use dynomite::msg::ConsistencyLevel;
86    /// assert_eq!(
87    ///     ConsistencyLevel::from_name("DC_SAFE_QUORUM"),
88    ///     Some(ConsistencyLevel::DcSafeQuorum),
89    /// );
90    /// ```
91    #[must_use]
92    pub fn from_name(name: &str) -> Option<Self> {
93        match name {
94            "DC_ONE" => Some(Self::DcOne),
95            "DC_QUORUM" => Some(Self::DcQuorum),
96            "DC_SAFE_QUORUM" => Some(Self::DcSafeQuorum),
97            "DC_EACH_SAFE_QUORUM" => Some(Self::DcEachSafeQuorum),
98            _ => None,
99        }
100    }
101}
102
103/// Dynomite-side error code carried in a message envelope.
104///
105/// The numeric values are stable wire codes for the error.
106#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
107#[repr(u8)]
108pub enum DynErrorCode {
109    /// No error.
110    #[default]
111    Ok = 0,
112    /// Unspecified error.
113    DynomiteUnknownError = 1,
114    /// Engine state forbids the request.
115    DynomiteInvalidState = 2,
116    /// Admin-only command issued in non-admin mode.
117    DynomiteInvalidAdminReq = 3,
118    /// Peer refused the connection.
119    PeerConnectionRefuse = 4,
120    /// Peer reachable but reported down.
121    PeerHostDown = 5,
122    /// Peer not yet connected.
123    PeerHostNotConnected = 6,
124    /// Datastore refused the connection.
125    StorageConnectionRefuse = 7,
126    /// Bad message framing.
127    BadFormat = 8,
128    /// Quorum not achieved.
129    DynomiteNoQuorumAchieved = 9,
130    /// Lua script keys span multiple nodes.
131    DynomiteScriptSpansNodes = 10,
132    /// Payload exceeds the configured limit.
133    DynomitePayloadTooLarge = 11,
134}
135
136impl DynErrorCode {
137    /// Human-readable label used in error responses.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use dynomite::msg::DynErrorCode;
143    /// assert_eq!(DynErrorCode::PeerHostDown.message(), "Peer Node is down");
144    /// ```
145    #[must_use]
146    pub fn message(self) -> &'static str {
147        match self {
148            DynErrorCode::Ok => "Success",
149            DynErrorCode::DynomiteUnknownError => "Unknown Error",
150            DynErrorCode::DynomiteInvalidState => {
151                "Dynomite's current state does not allow this request"
152            }
153            DynErrorCode::DynomiteInvalidAdminReq => "Invalid request in Dynomite's admin mode",
154            DynErrorCode::PeerConnectionRefuse => "Peer Node refused connection",
155            DynErrorCode::PeerHostDown => "Peer Node is down",
156            DynErrorCode::PeerHostNotConnected => "Peer Node is not connected",
157            DynErrorCode::StorageConnectionRefuse => "Datastore refused connection",
158            DynErrorCode::BadFormat => "Bad message format",
159            DynErrorCode::DynomiteNoQuorumAchieved => "Failed to achieve Quorum",
160            DynErrorCode::DynomiteScriptSpansNodes => {
161                "Keys in the script cannot span multiple nodes"
162            }
163            DynErrorCode::DynomitePayloadTooLarge => "MSET/MGET/SCAN payload too large",
164        }
165    }
166
167    /// Origin label (`Dynomite:`, `Peer:`, `Storage:`, `unknown:`)
168    /// used in error response prefixes, mirroring
169    /// `dyn_error_source`.
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// use dynomite::msg::DynErrorCode;
175    /// assert_eq!(DynErrorCode::PeerHostDown.source(), "Peer:");
176    /// ```
177    #[must_use]
178    pub fn source(self) -> &'static str {
179        match self {
180            DynErrorCode::DynomiteInvalidAdminReq
181            | DynErrorCode::DynomiteInvalidState
182            | DynErrorCode::DynomiteNoQuorumAchieved
183            | DynErrorCode::DynomiteScriptSpansNodes
184            | DynErrorCode::DynomitePayloadTooLarge => "Dynomite:",
185            DynErrorCode::PeerConnectionRefuse
186            | DynErrorCode::PeerHostDown
187            | DynErrorCode::PeerHostNotConnected => "Peer:",
188            DynErrorCode::StorageConnectionRefuse => "Storage:",
189            _ => "unknown:",
190        }
191    }
192}
193
194static READ_REPAIRS_ENABLED: OnceLock<bool> = OnceLock::new();
195
196/// Configure whether read repairs are globally enabled.
197///
198/// Called once during configuration validation; subsequent calls are
199/// silently ignored, so the cluster-wide read-repair flag is
200/// write-once. Returns `true` when the value was
201/// installed and `false` when an earlier call already pinned it.
202///
203/// # Examples
204///
205/// ```
206/// use dynomite::msg::set_read_repairs_enabled;
207/// // Calling twice from a single test wins or loses depending on
208/// // whether anyone else got there first; the API is idempotent.
209/// let _ = set_read_repairs_enabled(true);
210/// ```
211pub fn set_read_repairs_enabled(enabled: bool) -> bool {
212    READ_REPAIRS_ENABLED.set(enabled).is_ok()
213}
214
215/// True when read repairs are enabled cluster-wide.
216///
217/// Defaults to `false` until configuration validation enables it.
218///
219/// # Examples
220///
221/// ```
222/// use dynomite::msg::is_read_repairs_enabled;
223/// // Default state is "disabled".
224/// let _ = is_read_repairs_enabled();
225/// ```
226#[must_use]
227pub fn is_read_repairs_enabled() -> bool {
228    *READ_REPAIRS_ENABLED.get().unwrap_or(&false)
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn consistency_round_trip() {
237        for level in [
238            ConsistencyLevel::DcOne,
239            ConsistencyLevel::DcQuorum,
240            ConsistencyLevel::DcSafeQuorum,
241            ConsistencyLevel::DcEachSafeQuorum,
242        ] {
243            assert_eq!(ConsistencyLevel::from_name(level.name()), Some(level));
244        }
245        assert!(ConsistencyLevel::from_name("DC_BOGUS").is_none());
246    }
247
248    #[test]
249    fn dyn_error_code_strings_match_c() {
250        assert_eq!(DynErrorCode::Ok.message(), "Success");
251        assert_eq!(DynErrorCode::PeerHostDown.source(), "Peer:");
252        assert_eq!(DynErrorCode::DynomiteUnknownError.source(), "unknown:");
253        assert_eq!(DynErrorCode::StorageConnectionRefuse.source(), "Storage:");
254    }
255}