Skip to main content

dynomite/net/
conn.rs

1//! Per-connection state and the shared event loop.
2//!
3//! [`Conn`] holds the per-connection state. It owns:
4//!
5//! * the [`Transport`] the connection reads and writes through,
6//! * the recv mbuf chain ([`MbufQueue`]) the parser drains,
7//! * the send mbuf chain the writer drains,
8//! * the in-queue (`imsg_q`) of incoming requests waiting to be
9//!   forwarded,
10//! * the out-queue (`omsg_q`) of outstanding requests awaiting
11//!   responses,
12//! * the parser scratch state for partial messages,
13//! * counters and lifecycle bits the FSM sets and reads.
14//!
15//! The role-specific behavior (PROXY accepts, CLIENT parses
16//! datastore requests, SERVER pumps to the backend, etc.) lives in
17//! the sibling modules; [`Conn`] hosts the shared data shape and a
18//! small set of top-level lifecycle methods so multiple roles can
19//! reuse the same skeleton.
20//!
21//! # Examples
22//!
23//! ```no_run
24//! use dynomite::io::reactor::{ConnRole, TcpTransport};
25//! use dynomite::net::Conn;
26//! use tokio::net::TcpStream;
27//! # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
28//! let stream = TcpStream::connect("127.0.0.1:6379").await.unwrap();
29//! let transport = Box::new(TcpTransport::new(stream, ConnRole::Server));
30//! let mut conn = Conn::new(transport, ConnRole::Server);
31//! assert_eq!(conn.role(), ConnRole::Server);
32//! conn.close();
33//! # });
34//! ```
35
36use std::collections::HashMap;
37use std::net::SocketAddr;
38use std::sync::atomic::{AtomicU64, Ordering};
39
40use crate::core::types::MsgId;
41use crate::io::mbuf::{MbufPool, MbufQueue};
42use crate::io::reactor::{ConnRole, Transport};
43use crate::msg::{ConsistencyLevel, Msg, MsgQueue};
44
45use super::NetError;
46
47/// Maximum queue depth before back-pressure kicks in.
48///
49/// On overflow the queue logs and rejects: [`Conn::enqueue_in`] /
50/// [`Conn::enqueue_out`] return [`NetError::PoolExhausted`] when the
51/// cap is reached.
52pub const MAX_CONN_QUEUE_SIZE: usize = 20_000;
53
54/// Lightweight rolling counters carried by every [`Conn`].
55///
56/// Per-connection byte counters and event totals (`recv_bytes`,
57/// `send_bytes`, `events`).
58#[derive(Debug, Default, Clone)]
59pub struct ConnStats {
60    /// Bytes successfully read into the recv mbuf chain.
61    pub recv_bytes: u64,
62    /// Bytes successfully written from the send mbuf chain.
63    pub send_bytes: u64,
64    /// Number of messages enqueued onto `imsg_q`.
65    pub recv_msgs: u64,
66    /// Number of messages enqueued onto `omsg_q`.
67    pub send_msgs: u64,
68    /// Number of times the read path completed.
69    pub recv_events: u64,
70    /// Number of times the write path completed.
71    pub send_events: u64,
72}
73
74/// Stable, process-unique connection identifier.
75///
76/// Connections are identified by a monotonic 64-bit counter so the
77/// id stays unique across reconnects and across transports (TCP,
78/// QUIC).
79#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
80pub struct ConnHandle(u64);
81
82impl ConnHandle {
83    /// Borrow the raw 64-bit id.
84    ///
85    /// # Examples
86    /// ```
87    /// # use dynomite::io::reactor::{ConnRole, TcpTransport};
88    /// # use dynomite::net::Conn;
89    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
90    /// let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
91    /// let addr = listener.local_addr().unwrap();
92    /// let _accept = tokio::spawn(async move {
93    ///     let (s, _) = listener.accept().await.unwrap();
94    ///     drop(s);
95    /// });
96    /// let s = tokio::net::TcpStream::connect(addr).await.unwrap();
97    /// let transport = Box::new(TcpTransport::new(s, ConnRole::Server));
98    /// let conn = Conn::new(transport, ConnRole::Server);
99    /// assert!(conn.handle().raw() > 0);
100    /// # });
101    /// ```
102    #[must_use]
103    pub fn raw(self) -> u64 {
104        self.0
105    }
106}
107
108static NEXT_HANDLE: AtomicU64 = AtomicU64::new(1);
109
110fn next_handle() -> ConnHandle {
111    ConnHandle(NEXT_HANDLE.fetch_add(1, Ordering::Relaxed))
112}
113
114/// Connection state shared across role-specific FSMs.
115#[allow(clippy::struct_excessive_bools)]
116pub struct Conn {
117    handle: ConnHandle,
118    role: ConnRole,
119    transport: Option<Box<dyn Transport>>,
120    peer_addr: Option<SocketAddr>,
121    recv: MbufQueue,
122    send: MbufQueue,
123    imsg_q: MsgQueue,
124    omsg_q: MsgQueue,
125    rmsg: Option<Msg>,
126    smsg: Option<Msg>,
127    stats: ConnStats,
128    eof: bool,
129    done: bool,
130    err: Option<String>,
131    read_consistency: ConsistencyLevel,
132    write_consistency: ConsistencyLevel,
133    same_dc: bool,
134    dyn_mode: bool,
135    dnode_secured: bool,
136    crypto_key_sent: bool,
137    aes_key: Option<[u8; 32]>,
138    outstanding: HashMap<MsgId, MsgId>,
139    pool: MbufPool,
140}
141
142impl std::fmt::Debug for Conn {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        // Lifecycle bits and counters - the transport handle and
145        // the mbuf pool intentionally render as opaque.
146        let _ = (
147            &self.transport,
148            &self.read_consistency,
149            &self.write_consistency,
150            &self.same_dc,
151            &self.dyn_mode,
152            &self.dnode_secured,
153            &self.crypto_key_sent,
154            &self.aes_key,
155            &self.outstanding,
156            &self.pool,
157            &self.stats,
158            &self.rmsg,
159            &self.smsg,
160        );
161        f.debug_struct("Conn")
162            .field("handle", &self.handle)
163            .field("role", &self.role)
164            .field("peer_addr", &self.peer_addr)
165            .field("recv_chain", &self.recv.len())
166            .field("send_chain", &self.send.len())
167            .field("imsg_q", &self.imsg_q.len())
168            .field("omsg_q", &self.omsg_q.len())
169            .field("recv_bytes", &self.stats.recv_bytes)
170            .field("send_bytes", &self.stats.send_bytes)
171            .field("eof", &self.eof)
172            .field("done", &self.done)
173            .field("err", &self.err)
174            .field("read_consistency", &self.read_consistency)
175            .field("write_consistency", &self.write_consistency)
176            .field("same_dc", &self.same_dc)
177            .field("dyn_mode", &self.dyn_mode)
178            .field("dnode_secured", &self.dnode_secured)
179            .field("crypto_key_sent", &self.crypto_key_sent)
180            .field("aes_key_set", &self.aes_key.is_some())
181            .field("outstanding", &self.outstanding.len())
182            .finish()
183    }
184}
185
186impl Conn {
187    /// Build a new connection wrapping `transport` and tagged with
188    /// `role`.
189    ///
190    /// The connection starts with empty mbuf chains, no in-flight
191    /// messages, and the default consistency knobs (`DcOne`).
192    /// Role-specific drivers in this module set extra fields (the
193    /// dnode peer paths set `same_dc`, `dyn_mode`, etc.) before
194    /// invoking [`Conn::run`].
195    ///
196    /// # Examples
197    /// ```no_run
198    /// use dynomite::io::reactor::{ConnRole, TcpTransport};
199    /// use dynomite::net::Conn;
200    /// use tokio::net::TcpStream;
201    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
202    /// let s = TcpStream::connect("127.0.0.1:6379").await.unwrap();
203    /// let _ = Conn::new(Box::new(TcpTransport::new(s, ConnRole::Server)), ConnRole::Server);
204    /// # });
205    /// ```
206    pub fn new(transport: Box<dyn Transport>, role: ConnRole) -> Self {
207        let peer_addr = transport.peer_addr();
208        Self {
209            handle: next_handle(),
210            role,
211            transport: Some(transport),
212            peer_addr,
213            recv: MbufQueue::new(),
214            send: MbufQueue::new(),
215            imsg_q: MsgQueue::new(),
216            omsg_q: MsgQueue::new(),
217            rmsg: None,
218            smsg: None,
219            stats: ConnStats::default(),
220            eof: false,
221            done: false,
222            err: None,
223            read_consistency: ConsistencyLevel::DcOne,
224            write_consistency: ConsistencyLevel::DcOne,
225            same_dc: true,
226            dyn_mode: matches!(
227                role,
228                ConnRole::DnodePeerProxy | ConnRole::DnodePeerClient | ConnRole::DnodePeerServer
229            ),
230            dnode_secured: false,
231            crypto_key_sent: false,
232            aes_key: None,
233            outstanding: HashMap::new(),
234            pool: MbufPool::default(),
235        }
236    }
237
238    /// Stable connection handle.
239    #[must_use]
240    pub fn handle(&self) -> ConnHandle {
241        self.handle
242    }
243
244    /// Connection role.
245    #[must_use]
246    pub fn role(&self) -> ConnRole {
247        self.role
248    }
249
250    /// Remote address, if the transport could surface one.
251    #[must_use]
252    pub fn peer_addr(&self) -> Option<SocketAddr> {
253        self.peer_addr
254    }
255
256    /// Borrow the rolling counters.
257    #[must_use]
258    pub fn stats(&self) -> &ConnStats {
259        &self.stats
260    }
261
262    /// Borrow the recv mbuf chain.
263    #[must_use]
264    pub fn recv_chain(&self) -> &MbufQueue {
265        &self.recv
266    }
267
268    /// Mutably borrow the recv mbuf chain.
269    pub fn recv_chain_mut(&mut self) -> &mut MbufQueue {
270        &mut self.recv
271    }
272
273    /// Borrow the send mbuf chain.
274    #[must_use]
275    pub fn send_chain(&self) -> &MbufQueue {
276        &self.send
277    }
278
279    /// Mutably borrow the send mbuf chain.
280    pub fn send_chain_mut(&mut self) -> &mut MbufQueue {
281        &mut self.send
282    }
283
284    /// Borrow the in-queue.
285    #[must_use]
286    pub fn imsg_q(&self) -> &MsgQueue {
287        &self.imsg_q
288    }
289
290    /// Mutably borrow the in-queue.
291    pub fn imsg_q_mut(&mut self) -> &mut MsgQueue {
292        &mut self.imsg_q
293    }
294
295    /// Borrow the out-queue.
296    #[must_use]
297    pub fn omsg_q(&self) -> &MsgQueue {
298        &self.omsg_q
299    }
300
301    /// Mutably borrow the out-queue.
302    pub fn omsg_q_mut(&mut self) -> &mut MsgQueue {
303        &mut self.omsg_q
304    }
305
306    /// Borrow the partially-received message, if any.
307    #[must_use]
308    pub fn rmsg(&self) -> Option<&Msg> {
309        self.rmsg.as_ref()
310    }
311
312    /// Mutably borrow the partially-received message.
313    pub fn rmsg_mut(&mut self) -> Option<&mut Msg> {
314        self.rmsg.as_mut()
315    }
316
317    /// Take ownership of the partially-received message, leaving
318    /// the slot empty.
319    pub fn take_rmsg(&mut self) -> Option<Msg> {
320        self.rmsg.take()
321    }
322
323    /// Install the partially-received message slot.
324    pub fn set_rmsg(&mut self, msg: Option<Msg>) {
325        self.rmsg = msg;
326    }
327
328    /// Borrow the partially-sent message, if any.
329    #[must_use]
330    pub fn smsg(&self) -> Option<&Msg> {
331        self.smsg.as_ref()
332    }
333
334    /// Take the partially-sent message.
335    pub fn take_smsg(&mut self) -> Option<Msg> {
336        self.smsg.take()
337    }
338
339    /// Install the partially-sent message slot.
340    pub fn set_smsg(&mut self, msg: Option<Msg>) {
341        self.smsg = msg;
342    }
343
344    /// True when the peer has closed its half of the connection.
345    #[must_use]
346    pub fn is_eof(&self) -> bool {
347        self.eof
348    }
349
350    /// Mark the peer half as closed.
351    pub fn set_eof(&mut self) {
352        self.eof = true;
353    }
354
355    /// True when the connection has finished (either side closed
356    /// and all queued work drained).
357    #[must_use]
358    pub fn is_done(&self) -> bool {
359        self.done
360    }
361
362    /// Mark the connection as done.
363    pub fn set_done(&mut self) {
364        self.done = true;
365    }
366
367    /// Last recorded error, if any.
368    #[must_use]
369    pub fn err(&self) -> Option<&str> {
370        self.err.as_deref()
371    }
372
373    /// Record a transport-level error string.
374    pub fn set_err<S: Into<String>>(&mut self, msg: S) {
375        self.err = Some(msg.into());
376    }
377
378    /// Read consistency level applied to incoming reads on this
379    /// connection.
380    #[must_use]
381    pub fn read_consistency(&self) -> ConsistencyLevel {
382        self.read_consistency
383    }
384
385    /// Write consistency level applied to incoming writes on this
386    /// connection.
387    #[must_use]
388    pub fn write_consistency(&self) -> ConsistencyLevel {
389        self.write_consistency
390    }
391
392    /// Update the read consistency level.
393    pub fn set_read_consistency(&mut self, c: ConsistencyLevel) {
394        self.read_consistency = c;
395    }
396
397    /// Update the write consistency level.
398    pub fn set_write_consistency(&mut self, c: ConsistencyLevel) {
399        self.write_consistency = c;
400    }
401
402    /// True for peer connections inside the local datacenter.
403    #[must_use]
404    pub fn same_dc(&self) -> bool {
405        self.same_dc
406    }
407
408    /// Set the same-DC bit. The dnode-peer drivers update this
409    /// after consulting the snitch.
410    pub fn set_same_dc(&mut self, on: bool) {
411        self.same_dc = on;
412    }
413
414    /// True when the connection participates in the dnode peer
415    /// plane.
416    #[must_use]
417    pub fn dyn_mode(&self) -> bool {
418        self.dyn_mode
419    }
420
421    /// True when the dnode handshake exchanged a per-connection
422    /// AES key.
423    #[must_use]
424    pub fn dnode_secured(&self) -> bool {
425        self.dnode_secured
426    }
427
428    /// Mark the connection as secured (the handshake completed).
429    pub fn set_dnode_secured(&mut self, on: bool) {
430        self.dnode_secured = on;
431    }
432
433    /// True when the local end has emitted the encrypted AES key.
434    #[must_use]
435    pub fn crypto_key_sent(&self) -> bool {
436        self.crypto_key_sent
437    }
438
439    /// Mark the AES key as sent.
440    pub fn set_crypto_key_sent(&mut self, on: bool) {
441        self.crypto_key_sent = on;
442    }
443
444    /// Return the per-connection AES key, if one has been
445    /// installed.
446    #[must_use]
447    pub fn aes_key(&self) -> Option<&[u8; 32]> {
448        self.aes_key.as_ref()
449    }
450
451    /// Install (or replace) the per-connection AES key.
452    pub fn set_aes_key(&mut self, key: [u8; 32]) {
453        self.aes_key = Some(key);
454    }
455
456    /// Borrow the outstanding-msg map.
457    #[must_use]
458    pub fn outstanding(&self) -> &HashMap<MsgId, MsgId> {
459        &self.outstanding
460    }
461
462    /// Mutably borrow the outstanding-msg map.
463    pub fn outstanding_mut(&mut self) -> &mut HashMap<MsgId, MsgId> {
464        &mut self.outstanding
465    }
466
467    /// Borrow the pool used for fresh mbuf chunks.
468    #[must_use]
469    pub fn mbuf_pool(&self) -> &MbufPool {
470        &self.pool
471    }
472
473    /// Replace the mbuf pool. Useful when the embedding application
474    /// wants every connection to share a single pool.
475    pub fn set_mbuf_pool(&mut self, pool: MbufPool) {
476        self.pool = pool;
477    }
478
479    /// Take ownership of the underlying transport, leaving the
480    /// connection in a half-closed state. Returns `None` if the
481    /// transport has already been moved out (typically by
482    /// [`Conn::run`]).
483    pub fn take_transport(&mut self) -> Option<Box<dyn Transport>> {
484        self.transport.take()
485    }
486
487    /// Reinstall a transport. Used by reconnect logic in
488    /// [`crate::net::ConnPool`].
489    pub fn set_transport(&mut self, transport: Box<dyn Transport>) {
490        self.peer_addr = transport.peer_addr();
491        self.transport = Some(transport);
492    }
493
494    /// True when a transport is currently installed.
495    #[must_use]
496    pub fn has_transport(&self) -> bool {
497        self.transport.is_some()
498    }
499
500    /// Mutably borrow the transport. Driver loops use this to drive
501    /// `AsyncRead` / `AsyncWrite` directly.
502    pub fn transport_mut(&mut self) -> Option<&mut Box<dyn Transport>> {
503        self.transport.as_mut()
504    }
505
506    /// Enqueue a request onto the in-queue.
507    ///
508    /// # Errors
509    /// Returns [`NetError::PoolExhausted`] when the queue is at
510    /// [`MAX_CONN_QUEUE_SIZE`].
511    pub fn enqueue_in(&mut self, msg: Msg) -> Result<(), NetError> {
512        if self.imsg_q.len() >= MAX_CONN_QUEUE_SIZE {
513            return Err(NetError::PoolExhausted);
514        }
515        self.imsg_q.push_back(msg);
516        self.stats.recv_msgs += 1;
517        Ok(())
518    }
519
520    /// Enqueue a message onto the out-queue.
521    ///
522    /// # Errors
523    /// Returns [`NetError::PoolExhausted`] when the queue is at
524    /// [`MAX_CONN_QUEUE_SIZE`].
525    pub fn enqueue_out(&mut self, msg: Msg) -> Result<(), NetError> {
526        if self.omsg_q.len() >= MAX_CONN_QUEUE_SIZE {
527            return Err(NetError::PoolExhausted);
528        }
529        self.omsg_q.push_back(msg);
530        self.stats.send_msgs += 1;
531        Ok(())
532    }
533
534    /// Drop the transport and mark the connection as done.
535    pub fn close(&mut self) {
536        self.transport = None;
537        self.done = true;
538    }
539
540    /// Idle no-op driver hook.
541    ///
542    /// The PROXY / CLIENT / SERVER / DNODE_PEER_* roles are driven by
543    /// dedicated drivers in the sibling modules. Calling
544    /// `run` on a [`Conn`] without first installing a driver does
545    /// nothing: the connection idles until [`Conn::close`] is
546    /// invoked. Real drivers (for example [`super::client::client_loop`])
547    /// take a `&mut Conn` directly.
548    ///
549    /// # Errors
550    ///
551    /// Returns [`NetError::Closed`] when the transport has already
552    /// been moved out, e.g. by an earlier driver.
553    pub fn run(&mut self) -> Result<(), NetError> {
554        if self.transport.is_none() {
555            return Err(NetError::Closed);
556        }
557        if self.done {
558            return Ok(());
559        }
560        Ok(())
561    }
562
563    /// Bump the recv-bytes counter.
564    pub fn record_recv(&mut self, bytes: usize) {
565        self.stats.recv_bytes += bytes as u64;
566        self.stats.recv_events += 1;
567    }
568
569    /// Bump the send-bytes counter.
570    pub fn record_send(&mut self, bytes: usize) {
571        self.stats.send_bytes += bytes as u64;
572        self.stats.send_events += 1;
573    }
574}
575
576#[cfg(test)]
577mod tests {
578    use super::*;
579    use crate::io::reactor::TcpTransport;
580    use crate::msg::MsgType;
581    use tokio::net::{TcpListener, TcpStream};
582
583    async fn pair() -> (Conn, Conn) {
584        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
585        let addr = listener.local_addr().unwrap();
586        let accept = tokio::spawn(async move {
587            let (s, _) = listener.accept().await.unwrap();
588            s
589        });
590        let client = TcpStream::connect(addr).await.unwrap();
591        let server = accept.await.unwrap();
592        let c = Conn::new(
593            Box::new(TcpTransport::new(client, ConnRole::Client)),
594            ConnRole::Client,
595        );
596        let s = Conn::new(
597            Box::new(TcpTransport::new(server, ConnRole::Server)),
598            ConnRole::Server,
599        );
600        (c, s)
601    }
602
603    #[tokio::test]
604    async fn enqueue_in_and_out() {
605        let (mut c, _s) = pair().await;
606        c.enqueue_in(Msg::new(1, MsgType::ReqRedisGet, true))
607            .unwrap();
608        c.enqueue_out(Msg::new(2, MsgType::RspRedisStatus, false))
609            .unwrap();
610        assert_eq!(c.imsg_q().len(), 1);
611        assert_eq!(c.omsg_q().len(), 1);
612        assert_eq!(c.stats().recv_msgs, 1);
613        assert_eq!(c.stats().send_msgs, 1);
614    }
615
616    #[tokio::test]
617    async fn close_drops_transport() {
618        let (mut c, _s) = pair().await;
619        assert!(c.has_transport());
620        c.close();
621        assert!(!c.has_transport());
622        assert!(c.is_done());
623    }
624
625    #[tokio::test]
626    async fn handle_is_unique() {
627        let (a, b) = pair().await;
628        assert_ne!(a.handle(), b.handle());
629    }
630
631    #[tokio::test]
632    async fn role_seed_drives_dyn_mode() {
633        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
634        let addr = listener.local_addr().unwrap();
635        let _accept = tokio::spawn(async move {
636            let (s, _) = listener.accept().await.unwrap();
637            drop(s);
638        });
639        let s = TcpStream::connect(addr).await.unwrap();
640        let c = Conn::new(
641            Box::new(TcpTransport::new(s, ConnRole::DnodePeerServer)),
642            ConnRole::DnodePeerServer,
643        );
644        assert!(c.dyn_mode());
645        assert!(c.same_dc());
646    }
647}