1mod metrics;
24#[cfg(test)]
25mod tests;
26
27use std::convert::Infallible;
28use std::fmt::Debug;
29use std::task::{Context, Poll, ready};
30use std::time::Duration;
31
32use anyhow::{anyhow, bail};
33use bincode::Options;
34use futures::future;
35use mz_ore::cast::CastInto;
36use mz_ore::netio::{Listener, SocketAddr, Stream, TimedReader, TimedWriter};
37use mz_ore::task::{AbortOnDropHandle, JoinHandle};
38use semver::Version;
39use serde::de::DeserializeOwned;
40use serde::{Deserialize, Serialize};
41use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
42use tokio::sync::{mpsc, oneshot, watch};
43use tracing::{Instrument, info, info_span, trace, warn};
44
45use crate::client::{GenericClient, Partitionable, Partitioned};
46
47pub use metrics::{ClusterServerMetrics, Metrics, NoopMetrics, PerClusterServerMetrics};
48
49pub trait Message: Debug + Send + Sync + Serialize + DeserializeOwned + 'static {}
51impl<T: Debug + Send + Sync + Serialize + DeserializeOwned + 'static> Message for T {}
52
53#[derive(Debug)]
55pub struct Client<Out, In> {
56 conn: Connection<Out, In>,
57}
58
59impl<Out: Message, In: Message> Client<Out, In> {
60 pub async fn connect(
65 address: &str,
66 version: Version,
67 connect_timeout: Duration,
68 idle_timeout: Duration,
69 metrics: impl Metrics<Out, In>,
70 ) -> anyhow::Result<Self> {
71 let dest_host = host_from_address(address);
72 let stream = mz_ore::future::timeout(connect_timeout, Stream::connect(address)).await?;
73 info!(%address, "ctp: connected to server");
74
75 let span = info_span!("ctp", %address);
79 let conn = Connection::start(stream, version, dest_host, idle_timeout, metrics)
80 .instrument(span)
81 .await?;
82 Ok(Self { conn })
83 }
84}
85
86fn host_from_address(address: &str) -> Option<String> {
91 let mut p = address.split(':');
92 let (host, port) = match (p.next(), p.next(), p.next(), p.next()) {
93 (Some(host), Some(port), None, None) => (host, port),
94 (Some(_protocol), Some(host), Some(port), None) => (host, port),
95 _ => return None,
96 };
97
98 let _: u16 = port.parse().ok()?;
99 Some(host.into())
100}
101
102impl<Out, In> Client<Out, In>
103where
104 Out: Message,
105 In: Message,
106 (Out, In): Partitionable<Out, In>,
107{
108 pub async fn connect_partitioned(
110 addresses: Vec<String>,
111 version: Version,
112 connect_timeout: Duration,
113 idle_timeout: Duration,
114 metrics: impl Metrics<Out, In>,
115 ) -> anyhow::Result<Partitioned<Self, Out, In>> {
116 let connects = addresses.iter().map(|addr| {
117 Self::connect(
118 addr,
119 version.clone(),
120 connect_timeout,
121 idle_timeout,
122 metrics.clone(),
123 )
124 });
125 let clients = future::try_join_all(connects).await?;
126 Ok(Partitioned::new(clients))
127 }
128}
129
130impl<Out: Message, In: Message> GenericClient<Out, In> for Client<Out, In> {
131 async fn send(&mut self, cmd: Out) -> anyhow::Result<()> {
132 self.conn.send(cmd).await
133 }
134
135 fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<anyhow::Result<Option<In>>> {
136 self.conn.poll_recv(cx).map_ok(Some)
137 }
138}
139
140pub async fn serve<In, Out, H>(
142 address: SocketAddr,
143 version: Version,
144 server_fqdn: Option<String>,
145 idle_timeout: Duration,
146 handler_fn: impl Fn() -> H,
147 metrics: impl Metrics<Out, In>,
148) -> anyhow::Result<()>
149where
150 In: Message,
151 Out: Message,
152 H: GenericClient<In, Out> + 'static,
153{
154 let mut connection_task: Option<(JoinHandle<()>, oneshot::Sender<()>)> = None;
161
162 let listener = Listener::bind(&address).await?;
163 info!(%address, "ctp: listening for client connections");
164
165 loop {
166 let (stream, peer) = listener.accept().await?;
167 info!(%peer, "ctp: accepted client connection");
168
169 if let Some((task, token)) = connection_task.take() {
171 drop(token);
172 task.await;
173 }
174
175 let handler = handler_fn();
176 let version = version.clone();
177 let server_fqdn = server_fqdn.clone();
178 let metrics = metrics.clone();
179 let (cancel_tx, cancel_rx) = oneshot::channel();
180
181 let span = tracing::Span::current();
182 let handle = mz_ore::task::spawn(
183 || "ctp::connection",
184 async move {
185 let Err(error) = serve_connection(
186 stream,
187 handler,
188 version,
189 server_fqdn,
190 idle_timeout,
191 cancel_rx,
192 metrics,
193 )
194 .await;
195 info!("ctp: connection failed: {error}");
196 }
197 .instrument(span),
198 );
199
200 connection_task = Some((handle, cancel_tx));
201 }
202}
203
204async fn serve_connection<In, Out, H>(
206 stream: Stream,
207 mut handler: H,
208 version: Version,
209 server_fqdn: Option<String>,
210 timeout: Duration,
211 cancel_rx: oneshot::Receiver<()>,
212 metrics: impl Metrics<Out, In>,
213) -> anyhow::Result<Infallible>
214where
215 In: Message,
216 Out: Message,
217 H: GenericClient<In, Out>,
218{
219 let mut conn = Connection::start(stream, version, server_fqdn, timeout, metrics).await?;
220
221 let mut cancel_rx = cancel_rx;
222 loop {
223 tokio::select! {
224 inbound = conn.recv() => {
226 let msg = inbound?;
227 handler.send(msg).await?;
228 },
229 outbound = future::poll_fn(|cx| handler.poll_recv(cx)) => match outbound? {
232 Some(msg) => conn.send(msg).await?,
233 None => bail!("client disconnected"),
234 },
235 _ = &mut cancel_rx => bail!("connection canceled"),
236 }
237 }
238}
239
240#[derive(Debug)]
253struct Connection<Out, In> {
254 msg_tx: mpsc::UnboundedSender<Out>,
256 msg_rx: mpsc::UnboundedReceiver<In>,
258 error_rx: ErrorRx,
260
261 _tasks: [AbortOnDropHandle<()>; 2],
263}
264
265impl<Out: Message, In: Message> Connection<Out, In> {
266 const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(1);
268 const MIN_TIMEOUT: Duration = Duration::from_secs(2);
273
274 async fn start(
276 stream: Stream,
277 version: Version,
278 server_fqdn: Option<String>,
279 mut timeout: Duration,
280 metrics: impl Metrics<Out, In>,
281 ) -> anyhow::Result<Self> {
282 if timeout < Self::MIN_TIMEOUT {
283 warn!(
284 ?timeout,
285 "ctp: configured timeout is less than minimum timeout",
286 );
287 timeout = Self::MIN_TIMEOUT;
288 }
289
290 let (reader, writer) = stream.split();
291
292 let reader = TimedReader::new(reader, timeout);
294 let writer = TimedWriter::new(writer, timeout);
295 let mut reader = metrics::Reader::new(reader, metrics.clone());
297 let mut writer = metrics::Writer::new(writer, metrics.clone());
298
299 handshake(&mut reader, &mut writer, version, server_fqdn).await?;
300
301 let (out_tx, out_rx) = mpsc::unbounded_channel();
302 let (in_tx, in_rx) = mpsc::unbounded_channel();
303 let (error_tx, error_rx) = error_channel();
304
305 let span = tracing::Span::current();
306 let send_task = mz_ore::task::spawn(
307 || "ctp::send",
308 Self::run_send_task(writer, out_rx, error_tx.clone(), metrics.clone())
309 .instrument(span.clone()),
310 );
311 let recv_task = mz_ore::task::spawn(
312 || "ctp::recv",
313 Self::run_recv_task(reader, in_tx, error_tx, metrics).instrument(span),
314 );
315
316 Ok(Self {
317 msg_tx: out_tx,
318 msg_rx: in_rx,
319 error_rx,
320 _tasks: [send_task.abort_on_drop(), recv_task.abort_on_drop()],
321 })
322 }
323
324 async fn send(&mut self, msg: Out) -> anyhow::Result<()> {
326 match self.msg_tx.send(msg) {
327 Ok(()) => Ok(()),
328 Err(_) => bail!(self.error_rx.collect().await),
329 }
330 }
331
332 async fn recv(&mut self) -> anyhow::Result<In> {
338 future::poll_fn(|cx| self.poll_recv(cx)).await
339 }
340
341 fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<anyhow::Result<In>> {
343 match ready!(self.msg_rx.poll_recv(cx)) {
344 Some(msg) => Poll::Ready(Ok(msg)),
345 None => Poll::Ready(Err(anyhow!(self.error_rx.reported()))),
348 }
349 }
350
351 async fn run_send_task<W: AsyncWrite + Unpin>(
353 mut writer: W,
354 mut msg_rx: mpsc::UnboundedReceiver<Out>,
355 error_tx: ErrorTx,
356 mut metrics: impl Metrics<Out, In>,
357 ) {
358 loop {
359 let msg = tokio::select! {
360 msg = msg_rx.recv() => match msg {
362 Some(msg) => {
363 trace!(?msg, "ctp: sending message");
364 Some(msg)
365 }
366 None => break,
367 },
368 _ = tokio::time::sleep(Self::KEEPALIVE_INTERVAL) => {
370 trace!("ctp: sending keepalive");
371 None
372 },
373 };
374
375 if let Err(error) = write_message(&mut writer, msg.as_ref()).await {
376 warn!("ctp: send error: {error}");
377 error_tx.report(format!("send error: {error}"));
378 break;
379 };
380
381 if let Some(msg) = &msg {
382 metrics.message_sent(msg);
383 }
384 }
385 }
386
387 async fn run_recv_task<R: AsyncRead + Unpin>(
389 mut reader: R,
390 msg_tx: mpsc::UnboundedSender<In>,
391 error_tx: ErrorTx,
392 mut metrics: impl Metrics<Out, In>,
393 ) {
394 loop {
395 match read_message(&mut reader).await {
396 Ok(msg) => {
397 trace!(?msg, "ctp: received message");
398 metrics.message_received(&msg);
399
400 if msg_tx.send(msg).is_err() {
401 break;
402 }
403 }
404 Err(error) => {
405 warn!("ctp: recv error: {error}");
406 error_tx.report(format!("recv error: {error}"));
407 break;
408 }
409 };
410 }
411 }
412}
413
414const CONNECTION_CLOSED: &str = "connection closed";
416
417fn error_channel() -> (ErrorTx, ErrorRx) {
419 let (tx, rx) = watch::channel(None);
420 (ErrorTx(tx), ErrorRx(rx))
421}
422
423#[derive(Clone, Debug)]
425struct ErrorTx(watch::Sender<Option<String>>);
426
427impl ErrorTx {
428 fn report(&self, error: String) {
430 self.0.send_if_modified(|slot| match slot {
435 Some(_) => false,
436 None => {
437 *slot = Some(error);
438 true
439 }
440 });
441 }
442}
443
444#[derive(Debug)]
446struct ErrorRx(watch::Receiver<Option<String>>);
447
448impl ErrorRx {
449 async fn collect(&mut self) -> String {
454 let _ = self.0.changed().await;
456 self.0.mark_changed();
458
459 self.reported()
460 }
461
462 fn reported(&self) -> String {
465 let error = self.0.borrow().clone();
466 error.unwrap_or_else(|| CONNECTION_CLOSED.into())
467 }
468}
469
470async fn handshake<R, W>(
477 mut reader: R,
478 mut writer: W,
479 version: Version,
480 server_fqdn: Option<String>,
481) -> anyhow::Result<()>
482where
483 R: AsyncRead + Unpin,
484 W: AsyncWrite + Unpin,
485{
486 const MAGIC: u64 = 0x477574656e546167;
488
489 writer.write_u64(MAGIC).await?;
490
491 let hello = Hello {
492 version: version.clone(),
493 server_fqdn: server_fqdn.clone(),
494 };
495 write_message(&mut writer, Some(&hello)).await?;
496
497 let peer_magic = reader.read_u64().await?;
498 if peer_magic != MAGIC {
499 bail!("invalid protocol magic: {peer_magic:#x}");
500 }
501
502 let Hello {
503 version: peer_version,
504 server_fqdn: peer_server_fqdn,
505 } = read_message(&mut reader).await?;
506
507 if peer_version != version {
508 bail!("version mismatch: {peer_version} != {version}");
509 }
510 if let (Some(other), Some(mine)) = (&peer_server_fqdn, &server_fqdn) {
511 if other != mine {
512 bail!("server FQDN mismatch: {other} != {mine}");
513 }
514 }
515
516 Ok(())
517}
518
519#[derive(Debug, Serialize, Deserialize)]
521struct Hello {
522 version: Version,
524 server_fqdn: Option<String>,
526}
527
528async fn write_message<W, M>(mut writer: W, msg: Option<&M>) -> anyhow::Result<()>
533where
534 W: AsyncWrite + Unpin,
535 M: Message,
536{
537 let bytes = match msg {
538 Some(msg) => &*wire_encode(msg)?,
539 None => &[],
540 };
541
542 let len = bytes.len().cast_into();
543 writer.write_u64(len).await?;
544 writer.write_all(bytes).await?;
545
546 Ok(())
547}
548
549async fn read_message<R, M>(mut reader: R) -> anyhow::Result<M>
551where
552 R: AsyncRead + Unpin,
553 M: Message,
554{
555 let mut len = 0;
557 while len == 0 {
558 len = reader.read_u64().await?;
559 }
560
561 let mut bytes = vec![0; len.cast_into()];
562 reader.read_exact(&mut bytes).await?;
563
564 wire_decode(&bytes)
565}
566
567fn wire_encode<M: Message>(msg: &M) -> anyhow::Result<Vec<u8>> {
569 let bytes = bincode::DefaultOptions::new().serialize(msg)?;
570 Ok(bytes)
571}
572
573fn wire_decode<M: Message>(bytes: &[u8]) -> anyhow::Result<M> {
575 let msg = bincode::DefaultOptions::new().deserialize(bytes)?;
576 Ok(msg)
577}