Skip to main content

dynomite/net/
server.rs

1//! SERVER-role connection driver.
2//!
3//! The SERVER role holds an outbound connection to the backend
4//! datastore (RESP or memcache). The driver pulls requests off
5//! the connection's in-queue, encodes them onto the wire, and
6//! parses response bytes back into [`Msg`]s that it dispatches to
7//! the originating client.
8//!
9//! The driver is transport-agnostic and is wired to the cluster's
10//! [`Dispatcher`] so client / server connections form a complete
11//! request-response pipeline.
12//!
13//! [`Dispatcher`]: crate::net::Dispatcher
14//! [`Msg`]: crate::msg::Msg
15
16use tokio::io::{AsyncReadExt, AsyncWriteExt};
17use tokio::sync::mpsc;
18use tracing::Instrument as _;
19
20use crate::conf::DataStore;
21use crate::core::types::MsgId;
22use crate::io::reactor::ConnRole;
23use crate::msg::{Msg, MsgParseResult, MsgType};
24use crate::net::conn::Conn;
25use crate::net::dispatcher::OutboundEnvelope;
26use crate::net::NetError;
27use crate::proto::dnode::DmsgType;
28
29/// Outbound server-side connection driver.
30///
31/// The struct owns the transport-bound [`Conn`] plus the receiving
32/// half of the request channel that feeds it.
33pub struct ServerConn {
34    conn: Conn,
35    requests: mpsc::Receiver<OutboundRequest>,
36    data_store: DataStore,
37    pending_responses: std::collections::VecDeque<(MsgId, tracing::Span, Option<u32>)>,
38}
39
40/// Envelope sent into the server driver.
41///
42/// The driver writes `bytes` to the transport then awaits a
43/// response, which it forwards as an [`OutboundEnvelope`] on
44/// `responder` along with `req_id`.
45///
46/// `span` carries the originating client request's
47/// [`tracing::Span`] across the mpsc channel boundary so the
48/// receiving task's work nests under the originating client
49/// span when distributed tracing is enabled. The default value
50/// is [`tracing::Span::none`], which has no overhead when no
51/// subscriber is installed.
52///
53/// `ty` selects the dnode message-type header emitted on the
54/// peer plane. Data-plane callers leave it at
55/// [`DmsgType::Req`]; the gossip task uses
56/// [`DmsgType::GossipSyn`] / [`DmsgType::GossipShutdown`] for
57/// fire-and-forget control frames whose `responder` is never
58/// signalled.
59#[derive(Debug)]
60pub struct OutboundRequest {
61    /// Wire bytes already encoded by the dispatcher.
62    pub bytes: Vec<u8>,
63    /// Request id for response tagging.
64    pub req_id: MsgId,
65    /// Channel the driver pushes the parsed response onto.
66    pub responder: mpsc::Sender<OutboundEnvelope>,
67    /// Originating client request span; entered by the receiver
68    /// to nest backend / peer work under the request tree.
69    pub span: tracing::Span,
70    /// dnode message-type header emitted by the peer driver.
71    /// Defaults to [`DmsgType::Req`] for data-plane requests.
72    pub ty: DmsgType,
73    /// Index of the target peer the dispatcher is forwarding the
74    /// request to. The local backend driver and dnode-peer driver
75    /// stamp this onto the [`OutboundEnvelope`] they produce so
76    /// the reply coalescer can identify the responding replica.
77    /// `None` is used for single-target paths where the responder
78    /// already implies the source.
79    pub target_peer_idx: Option<u32>,
80}
81
82impl ServerConn {
83    /// Wrap an outbound [`Conn`] with the given request-channel
84    /// receiver and data-store flavor.
85    ///
86    /// # Examples
87    ///
88    /// ```no_run
89    /// use dynomite::conf::DataStore;
90    /// use dynomite::io::reactor::{ConnRole, TcpTransport};
91    /// use dynomite::net::{Conn, ServerConn};
92    /// use tokio::sync::mpsc;
93    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
94    /// let s = tokio::net::TcpStream::connect("127.0.0.1:6379").await.unwrap();
95    /// let conn = Conn::new(Box::new(TcpTransport::new(s, ConnRole::Server)), ConnRole::Server);
96    /// let (_tx, rx) = mpsc::channel(8);
97    /// let _ = ServerConn::new(conn, rx, DataStore::Valkey);
98    /// # });
99    /// ```
100    #[must_use]
101    pub fn new(
102        conn: Conn,
103        requests: mpsc::Receiver<OutboundRequest>,
104        data_store: DataStore,
105    ) -> Self {
106        debug_assert!(matches!(
107            conn.role(),
108            ConnRole::Server | ConnRole::DnodePeerServer
109        ));
110        Self {
111            conn,
112            requests,
113            data_store,
114            pending_responses: std::collections::VecDeque::new(),
115        }
116    }
117
118    /// Borrow the underlying connection.
119    #[must_use]
120    pub fn conn(&self) -> &Conn {
121        &self.conn
122    }
123
124    /// Mutably borrow the underlying connection.
125    pub fn conn_mut(&mut self) -> &mut Conn {
126        &mut self.conn
127    }
128
129    /// Drive the server FSM until either the request channel is
130    /// closed or the transport hits EOF / error.
131    ///
132    /// # Errors
133    /// Surfaces transport- and protocol-level errors.
134    pub async fn run(mut self) -> Result<(), NetError> {
135        let mut read_buf = vec![0u8; 4096];
136        let mut accumulated = Vec::<u8>::new();
137        let mut pending_responder: Option<mpsc::Sender<OutboundEnvelope>> = None;
138
139        loop {
140            if self.conn.is_eof() && self.pending_responses.is_empty() {
141                self.conn.set_done();
142                return Ok(());
143            }
144
145            tokio::select! {
146                res = self.requests.recv() => {
147                    let Some(out_req) = res else {
148                        // Channel closed; drain pending responses and exit.
149                        if self.pending_responses.is_empty() {
150                            self.conn.set_done();
151                            return Ok(());
152                        }
153                        continue;
154                    };
155                    let send_span = tracing::info_span!(
156                        parent: &out_req.span,
157                        "backend.send",
158                        req_id = out_req.req_id,
159                        bytes = out_req.bytes.len(),
160                    );
161                    let req_span = out_req.span.clone();
162                    let req_bytes = out_req.bytes;
163                    let transport = self.conn.transport_mut().ok_or(NetError::Closed)?;
164                    let write_res = async { transport.write_all(&req_bytes).await }
165                        .instrument(send_span)
166                        .await;
167                    write_res?;
168                    self.conn.record_send(req_bytes.len());
169                    self.pending_responses
170                        .push_back((out_req.req_id, req_span, out_req.target_peer_idx));
171                    pending_responder = Some(out_req.responder);
172                }
173                read_res = async {
174                    if let Some(t) = self.conn.transport_mut() {
175                        t.read(&mut read_buf).await
176                    } else {
177                        Ok(0)
178                    }
179                } => {
180                    let n = read_res?;
181                    if n == 0 {
182                        self.conn.set_eof();
183                        continue;
184                    }
185                    self.conn.record_recv(n);
186                    accumulated.extend_from_slice(&read_buf[..n]);
187                    self.drive_response_parser(&mut accumulated, &mut pending_responder).await?;
188                }
189            }
190        }
191    }
192
193    async fn drive_response_parser(
194        &mut self,
195        accumulated: &mut Vec<u8>,
196        responder: &mut Option<mpsc::Sender<OutboundEnvelope>>,
197    ) -> Result<(), NetError> {
198        use crate::proto::memcache::memcache_parse_rsp;
199        use crate::proto::redis::redis_parse_rsp;
200
201        while !accumulated.is_empty() {
202            let head_span = self
203                .pending_responses
204                .front()
205                .map_or_else(tracing::Span::current, |(_, s, _)| s.clone());
206            let id = self.pending_responses.front().map_or(0, |(i, _, _)| *i);
207            let mut msg = Msg::new(id, MsgType::Unknown, false);
208            let result = match self.data_store {
209                DataStore::Valkey | DataStore::Dyniak => redis_parse_rsp(&mut msg, accumulated),
210                DataStore::Memcache => memcache_parse_rsp(&mut msg, accumulated),
211            };
212            match result {
213                MsgParseResult::Ok => {
214                    let consumed = msg.parser_pos();
215                    if consumed == 0 {
216                        return Err(NetError::Parse("server parser stalled".into()));
217                    }
218                    let bytes = accumulated[..consumed].to_vec();
219                    accumulated.drain(0..consumed);
220                    let (req_id, req_span, source_peer_idx) = self
221                        .pending_responses
222                        .pop_front()
223                        .unwrap_or((0, head_span, None));
224                    let parse_span = tracing::info_span!(
225                        parent: &req_span,
226                        "backend.parse",
227                        req_id,
228                        bytes = consumed,
229                    );
230                    let env = parse_span.in_scope(|| {
231                        let mut rsp = msg;
232                        let pool = self.conn.mbuf_pool().clone();
233                        let mut buf = pool.get();
234                        buf.recv(&bytes);
235                        rsp.mbufs_mut().push_back(buf);
236                        rsp.recompute_mlen();
237                        OutboundEnvelope {
238                            req_id,
239                            rsp,
240                            span: req_span,
241                            source_peer_idx,
242                        }
243                    });
244                    if let Some(sender) = responder.as_ref() {
245                        let _ = sender.send(env).await;
246                    }
247                }
248                MsgParseResult::Again => return Ok(()),
249                MsgParseResult::Repair | MsgParseResult::Noop | MsgParseResult::Fragment => {
250                    let consumed = msg.parser_pos();
251                    if consumed > 0 {
252                        accumulated.drain(0..consumed);
253                    } else {
254                        return Ok(());
255                    }
256                }
257                MsgParseResult::Error | MsgParseResult::OomError | MsgParseResult::DynoConfig => {
258                    return Err(NetError::Parse(format!("{result:?}")));
259                }
260            }
261        }
262        Ok(())
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269    use crate::io::reactor::TcpTransport;
270    use tokio::net::{TcpListener, TcpStream};
271
272    #[tokio::test]
273    async fn build_server_conn() {
274        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
275        let addr = listener.local_addr().unwrap();
276        let _accept = tokio::spawn(async move {
277            let (s, _) = listener.accept().await.unwrap();
278            drop(s);
279        });
280        let s = TcpStream::connect(addr).await.unwrap();
281        let conn = Conn::new(
282            Box::new(TcpTransport::new(s, ConnRole::Server)),
283            ConnRole::Server,
284        );
285        let (_tx, rx) = mpsc::channel(1);
286        let server = ServerConn::new(conn, rx, DataStore::Valkey);
287        assert_eq!(server.conn().role(), ConnRole::Server);
288    }
289}