1use std::fmt;
51use std::fmt::Debug;
52use std::net::ToSocketAddrs;
53use std::time::Duration;
54
55use backoff::ExponentialBackoffBuilder;
56use backoff::backoff::Backoff;
57use bytes::Bytes;
58use enum_as_inner::EnumAsInner;
59use serde::Deserialize;
60use serde::Serialize;
61use serde::de::Error;
62use tokio::io::AsyncRead;
63use tokio::io::AsyncReadExt;
64use tokio::io::AsyncWrite;
65use tokio::io::AsyncWriteExt;
66use tokio::sync::watch;
67use tracing::Instrument;
68
69use super::*;
70use crate::RemoteMessage;
71
72pub mod duplex;
73mod framed;
74pub(super) mod mux;
75pub(crate) mod quic;
76pub(super) mod server;
77pub(super) mod session;
78pub use server::ServerHandle;
79
80pub(crate) trait Stream:
81 AsyncRead + AsyncWrite + Unpin + Send + Sync + Debug + 'static
82{
83}
84impl<S: AsyncRead + AsyncWrite + Unpin + Send + Sync + Debug + 'static> Stream for S {}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
90pub(crate) struct SessionId(u64);
91
92impl SessionId {
93 pub fn random() -> Self {
95 Self(rand::random())
96 }
97}
98
99impl fmt::Display for SessionId {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 write!(f, "{:016x}", self.0)
102 }
103}
104
105pub(crate) const INITIATOR_TO_ACCEPTOR: u8 = 0;
107
108pub(crate) const ACCEPTOR_TO_INITIATOR: u8 = 1;
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub(crate) enum ProtocolKind {
119 Simplex,
120 Duplex,
121}
122
123const SIMPLEX_MAGIC: [u8; 4] = *b"SMP\0";
124const DUPLEX_MAGIC: [u8; 4] = *b"DPX\0";
125
126const LINK_INIT_SIZE: usize = 4 + 8 + 1;
135
136#[derive(Debug, Clone, Copy)]
138pub(crate) struct LinkInit {
139 pub session_id: SessionId,
140 pub stream_id: u8,
141 pub kind: ProtocolKind,
142}
143
144pub(crate) async fn write_link_init<S: AsyncWrite + Unpin>(
146 stream: &mut S,
147 session_id: SessionId,
148 stream_id: u8,
149 kind: ProtocolKind,
150) -> Result<(), std::io::Error> {
151 let mut buf = [0u8; LINK_INIT_SIZE];
152 let magic = match kind {
153 ProtocolKind::Simplex => &SIMPLEX_MAGIC,
154 ProtocolKind::Duplex => &DUPLEX_MAGIC,
155 };
156 buf[0..4].copy_from_slice(magic);
157 buf[4..12].copy_from_slice(&session_id.0.to_be_bytes());
158 buf[12] = stream_id;
159 stream.write_all(&buf).await
160}
161
162async fn read_link_init<S: AsyncRead + Unpin>(stream: &mut S) -> Result<LinkInit, std::io::Error> {
165 let mut buf = [0u8; LINK_INIT_SIZE];
166 stream.read_exact(&mut buf).await?;
167 let kind = match &buf[0..4] {
168 m if m == SIMPLEX_MAGIC => ProtocolKind::Simplex,
169 m if m == DUPLEX_MAGIC => ProtocolKind::Duplex,
170 other => {
171 return Err(std::io::Error::new(
172 std::io::ErrorKind::InvalidData,
173 format!(
174 "invalid LinkInit magic: expected {:?} or {:?}, got {:?}",
175 SIMPLEX_MAGIC, DUPLEX_MAGIC, other
176 ),
177 ));
178 }
179 };
180 let session_id = SessionId(u64::from_be_bytes(buf[4..12].try_into().unwrap()));
181 let stream_id = buf[12];
182 Ok(LinkInit {
183 session_id,
184 stream_id,
185 kind,
186 })
187}
188
189pub(super) async fn prepare_accepted_stream(
198 stream: Box<dyn Stream>,
199 source: ChannelAddr,
200 dest: ChannelAddr,
201 expected_kind: Option<ProtocolKind>,
202) -> Result<(LinkInit, Box<dyn Stream>), anyhow::Error> {
203 let is_tls = dest.transport().is_tls();
204 let (link_init, stream): (LinkInit, Box<dyn Stream>) = if is_tls {
205 let tls_acceptor = match dest.transport() {
206 ChannelTransport::Tls => tls::tls_acceptor()?,
207 _ => meta::tls_acceptor(true)?,
208 };
209 let mut tls_stream = tls_acceptor.accept(stream).await?;
210 let link_init = read_link_init(&mut tls_stream)
211 .await
212 .map_err(|e| anyhow::anyhow!("LinkInit read failed from {}: {}", source, e))?;
213 (link_init, Box::new(tls_stream))
214 } else {
215 let mut stream = stream;
216 let link_init = read_link_init(&mut stream)
217 .await
218 .map_err(|e| anyhow::anyhow!("LinkInit read failed from {}: {}", source, e))?;
219 (link_init, stream)
220 };
221 if let Some(expected) = expected_kind
222 && link_init.kind != expected
223 {
224 return Err(anyhow::anyhow!(
225 "{:?} server received {:?} client from {}",
226 expected,
227 link_init.kind,
228 source
229 ));
230 }
231 Ok((link_init, stream))
232}
233
234type PreparedStreamFut = std::pin::Pin<
236 Box<
237 dyn std::future::Future<Output = Result<(LinkInit, Box<dyn Stream>), anyhow::Error>> + Send,
238 >,
239>;
240
241pub(super) fn preparer_for(
247 dest: ChannelAddr,
248 expected_kind: Option<ProtocolKind>,
249) -> impl Fn(Box<dyn Stream>, ChannelAddr) -> PreparedStreamFut + Clone + Send + 'static {
250 move |stream, source| {
251 let dest = dest.clone();
252 Box::pin(prepare_accepted_stream(stream, source, dest, expected_kind))
253 }
254}
255
256#[async_trait]
260pub(crate) trait Link: Send + Sync + Debug + 'static {
261 type Stream: Stream;
263
264 fn dest(&self) -> ChannelAddr;
266
267 fn link_id(&self) -> SessionId;
269
270 async fn next(&mut self) -> Result<Self::Stream, ClientError>;
273}
274
275use session::Session;
276
277use crate::config;
278use crate::metrics;
279
280const TCP_KEEPALIVE_INTERVAL: Duration = Duration::from_secs(5);
282
283const TCP_KEEPALIVE_RETRIES: u32 = 3;
286
287fn set_tcp_keepalive(stream: &tokio::net::TcpStream) {
296 let idle = hyperactor_config::global::get(config::CHANNEL_TCP_KEEPALIVE_IDLE);
297
298 let ka = socket2::TcpKeepalive::new()
299 .with_time(idle)
300 .with_interval(TCP_KEEPALIVE_INTERVAL)
301 .with_retries(TCP_KEEPALIVE_RETRIES);
302
303 let sock = socket2::SockRef::from(stream);
304 if let Err(err) = sock.set_tcp_keepalive(&ka) {
305 tracing::warn!(?err, "failed to set TCP keepalive on stream");
306 }
307}
308
309pub(crate) enum LinkStatus {
310 NeverConnected,
311 Connected(tokio::time::Instant),
312 Disconnected {
313 last_connected: tokio::time::Instant,
314 since: tokio::time::Instant,
315 },
316}
317
318impl LinkStatus {
319 fn connected(&mut self) {
320 *self = LinkStatus::Connected(tokio::time::Instant::now());
321 }
322
323 fn disconnected(&mut self) {
324 match *self {
325 LinkStatus::Connected(at) => {
326 *self = LinkStatus::Disconnected {
327 last_connected: at,
328 since: tokio::time::Instant::now(),
329 };
330 }
331 _ => {}
333 }
334 }
335}
336
337impl std::fmt::Display for LinkStatus {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 match self {
340 LinkStatus::NeverConnected => write!(f, "never connected"),
341 LinkStatus::Connected(at) => {
342 write!(f, "connected for {:.1}s", at.elapsed().as_secs_f64())
343 }
344 LinkStatus::Disconnected {
345 last_connected,
346 since,
347 } => {
348 write!(
349 f,
350 "last connected {:.1}s ago, disconnected for {:.1}s",
351 last_connected.elapsed().as_secs_f64(),
352 since.elapsed().as_secs_f64(),
353 )
354 }
355 }
356 }
357}
358
359fn log_send_error(
362 error: &session::SendLoopError,
363 dest: &ChannelAddr,
364 session_id: u64,
365 mode: &str,
366 link_status: &LinkStatus,
367) -> bool {
368 match error {
369 session::SendLoopError::Io(err) => {
370 tracing::info!(dest = %dest, session_id, error = %err, mode, "send error; {link_status}");
371 metrics::CHANNEL_ERRORS.add(
372 1,
373 hyperactor_telemetry::kv_pairs!(
374 "dest" => dest.to_string(),
375 "session_id" => session_id.to_string(),
376 "error_type" => metrics::ChannelErrorType::SendError.as_str(),
377 "mode" => mode.to_string(),
378 ),
379 );
380 false
381 }
382 session::SendLoopError::AppClosed => true,
383 session::SendLoopError::Rejected(reason) => {
384 tracing::error!(dest = %dest, session_id, mode, "server rejected connection: {reason}; {link_status}");
385 true
386 }
387 session::SendLoopError::ServerClosed => {
388 tracing::info!(dest = %dest, session_id, mode, "server closed the channel; {link_status}");
389 true
390 }
391 session::SendLoopError::DeliveryTimeout => {
392 let timeout = hyperactor_config::global::get(config::MESSAGE_DELIVERY_TIMEOUT);
393 tracing::error!(
394 dest = %dest, session_id, mode,
395 "failed to receive ack within timeout {timeout:?}; link is currently connected; {link_status}"
396 );
397 true
398 }
399 session::SendLoopError::OversizedFrame { size, max } => {
400 tracing::error!(
401 dest = %dest,
402 session_id,
403 mode,
404 "oversized frame: len={size} > max={max}; {link_status}"
405 );
406 true
407 }
408 }
409}
410
411fn classify_send_loop_error(error: &session::SendLoopError, log_id: &str) -> CloseReason {
417 match error {
418 session::SendLoopError::Rejected(reason) if reason.contains("out-of-sequence message") => {
419 CloseReason::SequenceMismatch(reason.clone())
420 }
421 session::SendLoopError::OversizedFrame { size, max } => CloseReason::OversizedFrame {
422 size: *size,
423 max: *max,
424 },
425 _ => CloseReason::Other(format!("{log_id}: {error}")),
426 }
427}
428
429pub(crate) fn spawn<M: RemoteMessage>(link: impl Link) -> super::ChannelTx<M> {
431 spawn_inner::<M>(link)
432}
433
434pub(crate) fn spawn_unordered<M: RemoteMessage>(
438 links: Vec<impl Link + 'static>,
439) -> super::ChannelTx<M> {
440 assert!(!links.is_empty());
441 if links.len() == 1 {
442 return spawn(links.into_iter().next().unwrap());
443 }
444
445 let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
446 let dest = links[0].dest();
447 let session_id = links[0].link_id();
448 let (notify, status) = watch::channel(TxStatus::Active);
449 let tx = super::ChannelTx {
450 sender,
451 dest: dest.clone(),
452 status,
453 };
454
455 let num_streams = links.len();
456
457 crate::init::get_runtime().spawn(async move {
458 let (queue_tx, queue_rx) =
463 async_channel::bounded::<session::PendingMessage<M>>(num_streams * 8);
464
465 let unacked: Arc<
469 tokio::sync::Mutex<std::collections::BTreeMap<u64, session::QueuedMessage<M>>>,
470 > = Arc::new(tokio::sync::Mutex::new(std::collections::BTreeMap::new()));
471
472 let mut writer_handles: Vec<tokio::task::JoinHandle<()>> = Vec::with_capacity(num_streams);
473 let log_id = format!("session {}.{:016x}", dest, session_id.0);
474
475 for (i, link) in links.into_iter().enumerate() {
476 let dest = dest.clone();
477 let unacked = unacked.clone();
478 let queue_rx = queue_rx.clone();
479 let log_id = log_id.clone();
480
481 writer_handles.push(tokio::spawn(async move {
482 let mut session = Session::new(link);
483 let mut reconnect_backoff = ExponentialBackoffBuilder::new()
484 .with_initial_interval(Duration::from_millis(10))
485 .with_multiplier(2.0)
486 .with_randomization_factor(0.1)
487 .with_max_interval(Duration::from_secs(5))
488 .with_max_elapsed_time(None)
489 .build();
490
491 loop {
492 let connected = match session.connect().await {
493 Ok(s) => s,
494 Err(_) => {
495 tracing::info!(
496 dest = %dest, stream = i,
497 "multi-stream writer {} connect failed", i
498 );
499 break;
500 }
501 };
502 tracing::info!(
503 dest = %dest, stream = i, "multi-stream writer {} connected", i
504 );
505
506 let stream = connected.stream(INITIATOR_TO_ACCEPTOR);
507 let connected_at = tokio::time::Instant::now();
508
509 let result: Result<(), session::SendLoopError> = async {
511 loop {
512 tokio::select! {
513 biased;
514
515 ack_result = stream.next() => {
516 match ack_result {
517 Ok(Some(buffer)) => {
518 let response = deserialize_response(buffer)
519 .map_err(|e| session::SendLoopError::Io(e.into()))?;
520 match response {
521 NetRxResponse::Ack(ack) => {
522 let mut guard = unacked.lock().await;
523 let retain: std::collections::BTreeMap<u64, session::QueuedMessage<M>> = guard.split_off(&(ack + 1));
525 let accepted = std::mem::replace(&mut *guard, retain);
526 drop(guard);
527 accepted.into_values().for_each(|queued| {
528 queued.completion.accept();
529 });
530 }
531 NetRxResponse::Reject(reason) => {
532 return Err(session::SendLoopError::Rejected(reason));
533 }
534 NetRxResponse::Closed => {
535 return Err(session::SendLoopError::ServerClosed);
536 }
537 }
538 }
539 Ok(None) => return Ok(()),
540 Err(e) => return Err(session::SendLoopError::Io(e.into())),
541 }
542 }
543
544 msg = queue_rx.recv() => {
545 let pending = match msg {
546 Ok(m) => m,
547 Err(_) => return Ok(()),
549 };
550 let session::PendingMessage {
551 seq,
552 message,
553 received_at,
554 completion,
555 } = pending;
556 let frame = Frame::Message(seq, message);
557 let serialized = match serde_multipart::serialize_bincode(&frame) {
558 Ok(m) => m,
559 Err(e) => {
560 tracing::error!(
561 "{log_id}: serialization error: {e}"
562 );
563 completion.accept();
564 continue;
565 }
566 };
567 let mut queued = session::QueuedMessage {
568 seq,
569 message: serialized,
570 received_at,
571 sent_at: None,
572 completion,
573 };
574 let framed = queued.message.clone().framed();
575 stream.write(framed).drive().await.map_err(|e| {
576 session::SendLoopError::Io(e.into())
577 })?;
578 queued.sent_at = Some(tokio::time::Instant::now());
579 unacked.lock().await.insert(queued.seq, queued);
580 }
581 }
582 }
583 }
584 .await;
585
586 session = connected.release();
587
588 if connected_at.elapsed() > Duration::from_secs(1) {
589 reconnect_backoff.reset();
590 }
591
592 match result {
593 Ok(()) => {
594 if queue_rx.is_closed() {
595 break;
597 }
598 if let Some(delay) = reconnect_backoff.next_backoff() {
599 tokio::time::sleep(delay).await;
600 }
601 }
602 Err(ref e) => {
603 if log_send_error(e, &dest, session_id.0, "multi-stream", &LinkStatus::NeverConnected) {
604 break;
605 }
606 if let Some(delay) = reconnect_backoff.next_backoff() {
607 tokio::time::sleep(delay).await;
608 }
609 }
610 }
611 }
612
613 tracing::info!(
614 dest = %dest,
615 stream = i,
616 "multi-stream writer {} shutting down",
617 i,
618 );
619 }));
620 }
621
622 let cleanup_rx = queue_rx.clone();
623 drop(queue_rx);
626
627 let mut next_seq = 0u64;
631
632 tracing::info!(
633 %dest, session = %log_id, num_streams,
634 "multi-stream dispatcher started"
635 );
636
637 while let Some((message, completion, received_at)) = receiver.recv().await {
638 let pending = session::PendingMessage {
639 seq: next_seq,
640 message,
641 received_at,
642 completion,
643 };
644 next_seq += 1;
645
646 if let Err(async_channel::SendError(pending)) = queue_tx.send(pending).await {
647 pending.completion.accept();
648 break;
650 }
651 }
652
653 drop(queue_tx);
655 for handle in writer_handles {
656 let _ = handle.await;
657 }
658 while let Ok(pending) = cleanup_rx.try_recv() {
659 pending.completion.accept();
660 }
661 for queued in Arc::into_inner(unacked)
662 .expect("writer handles should drop their unacked clones")
663 .into_inner()
664 .into_values()
665 {
666 queued.completion.accept();
667 }
668
669 let reason = format!("{log_id}: dispatcher closed");
670 let _ = notify.send(TxStatus::Closed(CloseReason::Other(reason)));
671 });
672
673 tx
674}
675
676fn spawn_inner<M: RemoteMessage>(link: impl Link) -> super::ChannelTx<M> {
677 let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
678 let dest = link.dest();
679 let session_id = link.link_id();
680 let (notify, status) = watch::channel(TxStatus::Active);
681 let tx = super::ChannelTx {
682 sender,
683 dest: dest.clone(),
684 status,
685 };
686 crate::init::get_runtime().spawn(async move {
687 let mut session = Session::new(link);
688 let log_id = format!("session {}.{:016x}", dest, session_id.0);
689 let mut deliveries = session::Deliveries {
690 outbox: session::Outbox::new(log_id.clone(), dest.clone(), session_id.0),
691 unacked: session::Unacked::new(None, log_id.clone()),
692 };
693 let mut receiver = receiver;
694
695 match receiver.recv().await {
697 Some(msg) => {
698 if let Err(err) = deliveries.outbox.push_back(msg) {
699 tracing::error!(
700 dest = %dest,
701 session_id = session_id.0,
702 error = %err,
703 "failed to push message to outbox"
704 );
705 let _ = notify.send(TxStatus::Closed(CloseReason::Other(
706 "failed to push to outbox".into(),
707 )));
708 return;
709 }
710 }
711 None => {
712 let _ = notify.send(TxStatus::Closed(CloseReason::Other(
713 "sender dropped".into(),
714 )));
715 return;
716 }
717 }
718
719 let mut reconnect_backoff = ExponentialBackoffBuilder::new()
720 .with_initial_interval(Duration::from_millis(10))
721 .with_multiplier(2.0)
722 .with_randomization_factor(0.1)
723 .with_max_interval(Duration::from_secs(5))
724 .with_max_elapsed_time(None)
725 .build();
726
727 let mut link_status = LinkStatus::NeverConnected;
728
729 let reason: CloseReason = 'outer: loop {
730 let connected = match deliveries.expiry_time() {
731 Some(deadline) => match session.connect_by(deadline).await {
732 Ok(s) => s,
733 Err(_) => {
734 let timeout =
735 hyperactor_config::global::get(config::MESSAGE_DELIVERY_TIMEOUT);
736 let error_msg = if deliveries.outbox.is_expired(timeout) {
737 format!("failed to deliver message within timeout {timeout:?}; {link_status}")
738 } else {
739 format!(
740 "failed to receive ack within timeout {timeout:?}; \
741 link is currently broken; {link_status}",
742 )
743 };
744 tracing::error!(
745 dest = %dest, session_id = session_id.0, "{}", error_msg
746 );
747 break 'outer CloseReason::Other(format!("{log_id}: {error_msg}"));
748 }
749 },
750 None => match session.connect().await {
751 Ok(s) => s,
752 Err(_) => break 'outer CloseReason::Other("session shut down".into()),
753 },
754 };
755
756 metrics::CHANNEL_CONNECTIONS.add(
757 1,
758 hyperactor_telemetry::kv_pairs!(
759 "transport" => dest.transport().to_string(),
760 "mode" => "simplex",
761 "reason" => "link connected",
762 ),
763 );
764
765 if !deliveries.unacked.is_empty() {
766 metrics::CHANNEL_RECONNECTIONS.add(
767 1,
768 hyperactor_telemetry::kv_pairs!(
769 "dest" => dest.to_string(),
770 "transport" => dest.transport().to_string(),
771 "mode" => "simplex",
772 "reason" => "reconnect_with_unacked",
773 ),
774 );
775 }
776 deliveries.requeue_unacked();
777
778 link_status.connected();
779 let connected_at = tokio::time::Instant::now();
780
781 let result = {
782 let stream = connected.stream(INITIATOR_TO_ACCEPTOR);
783 session::send_connected(&stream, &mut deliveries, &mut receiver).await
784 };
785 session = connected.release();
786
787 link_status.disconnected();
788
789 if connected_at.elapsed() > Duration::from_secs(1) {
792 reconnect_backoff.reset();
793 }
794
795 match result {
796 Ok(()) => {
797 if let Some(delay) = reconnect_backoff.next_backoff() {
799 tracing::info!(
800 dest = %dest,
801 session_id = session_id.0,
802 delay_ms = delay.as_millis() as u64,
803 "send_connected returned EOF, reconnecting after backoff; {link_status}"
804 );
805 tokio::time::sleep(delay).await;
806 }
807 }
808 Err(ref e) => {
809 if log_send_error(e, &dest, session_id.0, "simplex", &link_status) {
810 break 'outer classify_send_loop_error(e, &log_id);
811 }
812 if let Some(delay) = reconnect_backoff.next_backoff() {
814 tracing::info!(
815 dest = %dest,
816 session_id = session_id.0,
817 delay_ms = delay.as_millis() as u64,
818 error = %e,
819 "send_connected returned recoverable error, reconnecting after backoff; {link_status}"
820 );
821 tokio::time::sleep(delay).await;
822 }
823 }
824 }
825 };
826
827 tracing::info!(
828 dest = %dest, session_id = session_id.0, "ChannelTx closing: {reason}"
829 );
830
831 let send_error_reason = match &reason {
832 CloseReason::OversizedFrame { size, max } => Some(SendErrorReason::OversizedFrame {
833 len: *size,
834 max: *max,
835 }),
836 _ => Some(SendErrorReason::Other(reason.to_string())),
837 };
838 receiver.close();
839 deliveries
840 .unacked
841 .deque
842 .drain(..)
843 .chain(deliveries.outbox.deque.drain(..))
844 .for_each(|queued| queued.try_return(send_error_reason.clone()));
845 while let Ok((msg, completion, _)) = receiver.try_recv() {
846 completion.reject(SendError {
847 error: ChannelError::Closed,
848 message: msg,
849 reason: send_error_reason.clone(),
850 });
851 }
852
853 let _ = notify.send(TxStatus::Closed(reason));
854 }.instrument(tracing::debug_span!("net tx loop")));
855 tx
856}
857
858#[derive(Debug)]
861pub(crate) enum NetLink {
862 Tcp(tcp::TcpLink),
863 Unix(unix::UnixLink),
864 Tls(tls::TlsLink),
865 Quic(quic::QuicLink),
866 Local(local::stream::LocalLink),
867}
868
869pub(crate) fn link(
874 addr: ChannelAddr,
875 session_id: SessionId,
876 stream_id: u8,
877 kind: ProtocolKind,
878) -> Result<NetLink, ClientError> {
879 match addr {
880 ChannelAddr::Tcp(socket_addr) => Ok(NetLink::Tcp(tcp::link(
881 socket_addr,
882 session_id,
883 stream_id,
884 kind,
885 ))),
886 ChannelAddr::Unix(unix_addr) => Ok(NetLink::Unix(unix::link(
887 unix_addr, session_id, stream_id, kind,
888 ))),
889 ChannelAddr::Local(port) => {
890 local::stream::check(port)?;
891 Ok(NetLink::Local(local::stream::link(
892 port, session_id, stream_id, kind,
893 )))
894 }
895 ChannelAddr::Tls(tls_addr) => Ok(NetLink::Tls(tls::link(
896 tls_addr, session_id, stream_id, kind,
897 )?)),
898 ChannelAddr::MetaTls(meta_addr) => Ok(NetLink::Tls(meta::link(
899 meta_addr, session_id, stream_id, kind,
900 )?)),
901 ChannelAddr::Quic(quic_addr) => Ok(NetLink::Quic(quic::link(
902 quic_addr,
903 quic::QuicAddrType::Quic,
904 session_id,
905 stream_id,
906 kind,
907 )?)),
908 ChannelAddr::MetaQuic(meta_addr) => Ok(NetLink::Quic(quic::link(
909 meta_addr,
910 quic::QuicAddrType::MetaQuic,
911 session_id,
912 stream_id,
913 kind,
914 )?)),
915 other => Err(ClientError::Connect(
916 other,
917 std::io::Error::other("unsupported transport"),
918 "unsupported transport".into(),
919 )),
920 }
921}
922
923#[async_trait]
924impl Link for NetLink {
925 type Stream = Box<dyn Stream>;
926
927 fn dest(&self) -> ChannelAddr {
928 match self {
929 Self::Tcp(l) => l.dest(),
930 Self::Unix(l) => l.dest(),
931 Self::Tls(l) => l.dest(),
932 Self::Quic(l) => l.dest(),
933 Self::Local(l) => l.dest(),
934 }
935 }
936
937 fn link_id(&self) -> SessionId {
938 match self {
939 Self::Tcp(l) => l.link_id(),
940 Self::Unix(l) => l.link_id(),
941 Self::Tls(l) => l.link_id(),
942 Self::Quic(l) => l.link_id(),
943 Self::Local(l) => l.link_id(),
944 }
945 }
946
947 async fn next(&mut self) -> Result<Box<dyn Stream>, ClientError> {
948 match self {
949 Self::Tcp(l) => Ok(Box::new(l.next().await?)),
950 Self::Unix(l) => Ok(Box::new(l.next().await?)),
951 Self::Tls(l) => Ok(Box::new(l.next().await?)),
952 Self::Quic(l) => Ok(Box::new(l.next().await?)),
953 Self::Local(l) => Ok(Box::new(l.next().await?)),
954 }
955 }
956}
957
958#[async_trait]
963pub(crate) trait Listener: Send + Unpin + 'static {
964 type Stream: Stream;
966
967 async fn accept(&mut self) -> Result<(Self::Stream, ChannelAddr), ServerError>;
969}
970
971#[derive(Debug)]
976pub(crate) enum NetListener {
977 Tcp(tcp::TcpSocketListener),
978 Unix(unix::UnixSocketListener),
979 Quic(quic::QuicSocketListener),
980 Local(local::stream::LocalListener),
981}
982
983#[async_trait]
984impl Listener for NetListener {
985 type Stream = Box<dyn Stream>;
986
987 async fn accept(&mut self) -> Result<(Box<dyn Stream>, ChannelAddr), ServerError> {
988 match self {
989 Self::Tcp(l) => {
990 let (stream, addr) = l.accept().await?;
991 Ok((Box::new(stream), addr))
992 }
993 Self::Unix(l) => {
994 let (stream, addr) = l.accept().await?;
995 Ok((Box::new(stream), addr))
996 }
997 Self::Quic(l) => {
998 let (stream, addr) = l.accept().await?;
999 Ok((Box::new(stream), addr))
1000 }
1001 Self::Local(l) => {
1002 let (stream, addr) = l.accept().await?;
1003 Ok((Box::new(stream), addr))
1004 }
1005 }
1006 }
1007}
1008
1009pub(crate) fn listen_with_prebound(
1013 addr: ChannelAddr,
1014 prebound: Option<std::net::TcpListener>,
1015) -> Result<(NetListener, ChannelAddr), ServerError> {
1016 match addr {
1017 ChannelAddr::Tcp(socket_addr) => {
1018 let std_listener = match prebound {
1019 Some(l) => l,
1020 None => std::net::TcpListener::bind(socket_addr)
1021 .map_err(|err| ServerError::Listen(ChannelAddr::Tcp(socket_addr), err))?,
1022 };
1023 std_listener
1024 .set_nonblocking(true)
1025 .map_err(|e| ServerError::Listen(ChannelAddr::Tcp(socket_addr), e))?;
1026 let tokio_listener = tokio::net::TcpListener::from_std(std_listener)
1027 .map_err(|e| ServerError::Listen(ChannelAddr::Tcp(socket_addr), e))?;
1028 let local_addr = tokio_listener
1029 .local_addr()
1030 .map_err(|err| ServerError::Resolve(ChannelAddr::Tcp(socket_addr), err))?;
1031 let listener = tcp::TcpSocketListener {
1032 inner: tokio_listener,
1033 addr: local_addr,
1034 };
1035 Ok((NetListener::Tcp(listener), ChannelAddr::Tcp(local_addr)))
1036 }
1037 ChannelAddr::Unix(ref unix_addr) => {
1038 use std::os::unix::net::UnixDatagram as StdUnixDatagram;
1039 use std::os::unix::net::UnixListener as StdUnixListener;
1040
1041 let caddr = addr.clone();
1042 let maybe_listener = match unix_addr {
1043 unix::SocketAddr::Bound(sock_addr) => StdUnixListener::bind_addr(sock_addr),
1044 unix::SocketAddr::Unbound => StdUnixDatagram::unbound()
1045 .and_then(|u| u.local_addr())
1046 .and_then(|uaddr| StdUnixListener::bind_addr(&uaddr)),
1047 };
1048 let std_listener =
1049 maybe_listener.map_err(|err| ServerError::Listen(caddr.clone(), err))?;
1050 std_listener
1051 .set_nonblocking(true)
1052 .map_err(|err| ServerError::Listen(caddr.clone(), err))?;
1053 let local_addr = std_listener
1054 .local_addr()
1055 .map_err(|err| ServerError::Resolve(caddr.clone(), err))?;
1056 let tokio_listener = tokio::net::UnixListener::from_std(std_listener)
1057 .map_err(|err| ServerError::Io(caddr, err))?;
1058 let bound_addr = unix::SocketAddr::new(local_addr);
1059 let listener = unix::UnixSocketListener {
1060 inner: tokio_listener,
1061 addr: bound_addr.clone(),
1062 };
1063 Ok((NetListener::Unix(listener), ChannelAddr::Unix(bound_addr)))
1064 }
1065 ChannelAddr::Local(_) => {
1066 let (listener, addr) = local::stream::listen(addr, prebound)?;
1067 Ok((NetListener::Local(listener), addr))
1068 }
1069 addr @ (ChannelAddr::Tls(_) | ChannelAddr::MetaTls(_)) => {
1070 let is_meta = matches!(addr, ChannelAddr::MetaTls(_));
1071 let tls_addr = match addr {
1072 ChannelAddr::Tls(a) | ChannelAddr::MetaTls(a) => a,
1073 _ => unreachable!(),
1074 };
1075 let TlsAddr { hostname, port } = tls_addr;
1076 let make_channel_addr = |h: &str, p: Port| {
1077 if is_meta {
1078 ChannelAddr::MetaTls(TlsAddr::new(h, p))
1079 } else {
1080 ChannelAddr::Tls(TlsAddr::new(h, p))
1081 }
1082 };
1083
1084 let addrs: Vec<core::net::SocketAddr> = (hostname.as_ref(), port)
1085 .to_socket_addrs()
1086 .map_err(|err| ServerError::Resolve(make_channel_addr(&hostname, port), err))?
1087 .collect();
1088
1089 if addrs.is_empty() {
1090 return Err(ServerError::Resolve(
1091 make_channel_addr(&hostname, port),
1092 std::io::Error::other("no available socket addr"),
1093 ));
1094 }
1095
1096 let channel_addr = make_channel_addr(&hostname, port);
1097 let std_listener = match prebound {
1098 Some(l) => l,
1099 None => std::net::TcpListener::bind(&addrs[..])
1100 .map_err(|err| ServerError::Listen(channel_addr.clone(), err))?,
1101 };
1102 std_listener
1103 .set_nonblocking(true)
1104 .map_err(|e| ServerError::Listen(channel_addr.clone(), e))?;
1105 let tokio_listener = tokio::net::TcpListener::from_std(std_listener)
1106 .map_err(|e| ServerError::Listen(channel_addr.clone(), e))?;
1107 let local_addr = tokio_listener
1108 .local_addr()
1109 .map_err(|err| ServerError::Resolve(channel_addr, err))?;
1110 let listener = tcp::TcpSocketListener {
1111 inner: tokio_listener,
1112 addr: local_addr,
1113 };
1114 Ok((
1115 NetListener::Tcp(listener),
1116 make_channel_addr(&hostname, local_addr.port()),
1117 ))
1118 }
1119 addr @ (ChannelAddr::Quic(_) | ChannelAddr::MetaQuic(_)) => {
1120 if prebound.is_some() {
1121 return Err(ServerError::Listen(
1122 addr,
1123 std::io::Error::other("pre-opened listener not supported for QUIC transport"),
1124 ));
1125 }
1126 let addr_type = match addr {
1127 ChannelAddr::Quic(_) => quic::QuicAddrType::Quic,
1128 ChannelAddr::MetaQuic(_) => quic::QuicAddrType::MetaQuic,
1129 _ => unreachable!(),
1130 };
1131 let tls_addr = match addr {
1132 ChannelAddr::Quic(a) | ChannelAddr::MetaQuic(a) => a,
1133 _ => unreachable!(),
1134 };
1135 let (listener, bound_addr) = quic::listen(tls_addr, addr_type)?;
1136 Ok((NetListener::Quic(listener), bound_addr))
1137 }
1138 ChannelAddr::Alias { dial_to, bind_to } => {
1139 let (listener, _bound_addr) = listen_with_prebound(*bind_to, prebound)?;
1147 Ok((listener, *dial_to))
1148 }
1149 }
1150}
1151
1152#[derive(Debug, Serialize, Deserialize, EnumAsInner, PartialEq)]
1154pub(super) enum Frame<M> {
1155 Message(u64, M),
1157}
1158
1159#[derive(Debug, Serialize, Deserialize, EnumAsInner)]
1160pub(super) enum NetRxResponse {
1161 Ack(u64),
1162 Reject(String),
1164 Closed,
1166}
1167
1168pub(super) fn serialize_response(
1169 response: NetRxResponse,
1170) -> Result<Bytes, bincode::error::EncodeError> {
1171 bincode::serde::encode_to_vec(&response, bincode::config::legacy()).map(|bytes| bytes.into())
1172}
1173
1174pub(super) fn deserialize_response(
1175 data: Bytes,
1176) -> Result<NetRxResponse, bincode::error::DecodeError> {
1177 bincode::serde::decode_from_slice(&data, bincode::config::legacy()).map(|(v, _)| v)
1178}
1179
1180#[derive(Debug, thiserror::Error)]
1182pub enum ServerError {
1183 #[error("io: {1}")]
1185 Io(ChannelAddr, #[source] std::io::Error),
1186 #[error("listen: {0} {1}")]
1188 Listen(ChannelAddr, #[source] std::io::Error),
1189 #[error("resolve: {0} {1}")]
1191 Resolve(ChannelAddr, #[source] std::io::Error),
1192 #[error("internal: {0} {1}")]
1194 Internal(ChannelAddr, #[source] anyhow::Error),
1195}
1196
1197#[derive(thiserror::Error, Debug)]
1198pub enum ClientError {
1199 #[error("connection to {0} failed: {1}: {2}")]
1200 Connect(ChannelAddr, std::io::Error, String),
1201 #[error("connection to {0} failed after {1:?} of retries: {2}")]
1202 ConnectTimeout(ChannelAddr, Duration, #[source] std::io::Error),
1203 #[error("unable to resolve address: {0}")]
1204 Resolve(ChannelAddr),
1205 #[error("io: {0} {1}")]
1206 Io(ChannelAddr, std::io::Error),
1207 #[error("send {0}: serialize: {1}")]
1208 Serialize(ChannelAddr, bincode::error::EncodeError),
1209 #[error("invalid address: {0}")]
1210 InvalidAddress(String),
1211}
1212
1213#[cfg(test)]
1216pub(super) fn is_net_addr(addr: &ChannelAddr) -> bool {
1217 matches!(
1218 addr.transport(),
1219 ChannelTransport::Tcp(_)
1220 | ChannelTransport::MetaTls(_)
1221 | ChannelTransport::Tls
1222 | ChannelTransport::Quic
1223 | ChannelTransport::MetaQuic(_)
1224 | ChannelTransport::Unix
1225 | ChannelTransport::Local
1226 )
1227}
1228
1229pub(crate) mod unix {
1230
1231 use core::str;
1232 use std::os::unix::net::SocketAddr as StdSocketAddr;
1233 use std::os::unix::net::UnixStream as StdUnixStream;
1234
1235 use rand::RngExt as _;
1236 use rand::distr::Alphanumeric;
1237 use tokio::net::UnixListener;
1238 use tokio::net::UnixStream;
1239
1240 use super::*;
1241
1242 #[derive(Debug)]
1243 pub(crate) struct UnixLink {
1244 pub(super) addr: SocketAddr,
1245 pub(super) session_id: SessionId,
1246 pub(super) stream_id: u8,
1247 pub(super) kind: ProtocolKind,
1248 }
1249
1250 #[async_trait]
1251 impl Link for UnixLink {
1252 type Stream = UnixStream;
1253
1254 fn dest(&self) -> ChannelAddr {
1255 ChannelAddr::Unix(self.addr.clone())
1256 }
1257
1258 fn link_id(&self) -> SessionId {
1259 self.session_id
1260 }
1261
1262 async fn next(&mut self) -> Result<Self::Stream, ClientError> {
1263 let session_id = self.session_id;
1264 let sock_addr = match &self.addr {
1265 SocketAddr::Bound(a) => a,
1266 SocketAddr::Unbound => return Err(ClientError::Resolve(self.dest())),
1267 };
1268 let mut backoff = ExponentialBackoffBuilder::new()
1269 .with_initial_interval(Duration::from_millis(1))
1270 .with_multiplier(2.0)
1271 .with_randomization_factor(0.1)
1272 .with_max_interval(Duration::from_millis(1000))
1273 .with_max_elapsed_time(None)
1274 .build();
1275 loop {
1276 match StdUnixStream::connect_addr(sock_addr) {
1277 Ok(std_stream) => {
1278 std_stream
1279 .set_nonblocking(true)
1280 .map_err(|err| ClientError::Io(self.dest(), err))?;
1281 let mut stream = UnixStream::from_std(std_stream)
1282 .map_err(|err| ClientError::Io(self.dest(), err))?;
1283 write_link_init(&mut stream, session_id, self.stream_id, self.kind)
1284 .await
1285 .map_err(|err| ClientError::Io(self.dest(), err))?;
1286 return Ok(stream);
1287 }
1288 Err(err) => {
1289 tracing::debug!(error = %err, "unix connect failed, backing off");
1290 if let Some(delay) = backoff.next_backoff() {
1291 tokio::time::sleep(delay).await;
1292 }
1293 }
1294 }
1295 }
1296 }
1297 }
1298
1299 #[derive(Debug)]
1301 pub(crate) struct UnixSocketListener {
1302 pub(super) inner: UnixListener,
1303 pub(super) addr: SocketAddr,
1304 }
1305
1306 #[async_trait]
1307 impl super::Listener for UnixSocketListener {
1308 type Stream = UnixStream;
1309
1310 async fn accept(&mut self) -> Result<(Self::Stream, ChannelAddr), ServerError> {
1311 let (stream, peer_addr) = self
1312 .inner
1313 .accept()
1314 .await
1315 .map_err(|err| ServerError::Io(ChannelAddr::Unix(self.addr.clone()), err))?;
1316 let std_addr: StdSocketAddr = peer_addr.into();
1318 Ok((stream, ChannelAddr::Unix(SocketAddr::new(std_addr))))
1319 }
1320 }
1321
1322 pub(crate) fn link(
1324 addr: SocketAddr,
1325 session_id: SessionId,
1326 stream_id: u8,
1327 kind: ProtocolKind,
1328 ) -> UnixLink {
1329 UnixLink {
1330 addr,
1331 session_id,
1332 stream_id,
1333 kind,
1334 }
1335 }
1336
1337 #[derive(Clone, Debug)]
1339 pub enum SocketAddr {
1340 Bound(Box<StdSocketAddr>),
1341 Unbound,
1342 }
1343
1344 impl PartialOrd for SocketAddr {
1345 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1346 Some(self.cmp(other))
1347 }
1348 }
1349
1350 impl Ord for SocketAddr {
1351 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1352 self.to_string().cmp(&other.to_string())
1353 }
1354 }
1355
1356 impl<'de> Deserialize<'de> for SocketAddr {
1357 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1358 where
1359 D: serde::Deserializer<'de>,
1360 {
1361 let s = String::deserialize(deserializer)?;
1362 Self::from_str(&s).map_err(D::Error::custom)
1363 }
1364 }
1365
1366 impl Serialize for SocketAddr {
1367 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1368 where
1369 S: serde::Serializer,
1370 {
1371 serializer.serialize_str(String::from(self).as_str())
1372 }
1373 }
1374
1375 impl From<&SocketAddr> for String {
1376 fn from(value: &SocketAddr) -> Self {
1377 match value {
1378 SocketAddr::Bound(addr) => match addr.as_pathname() {
1379 Some(path) => path
1380 .to_str()
1381 .expect("unable to get str for path")
1382 .to_string(),
1383 #[cfg(target_os = "linux")]
1384 _ => match addr.as_abstract_name() {
1385 Some(name) => format!("@{}", String::from_utf8_lossy(name)),
1386 _ => String::from("(unnamed)"),
1387 },
1388 #[cfg(not(target_os = "linux"))]
1389 _ => String::from("(unnamed)"),
1390 },
1391 SocketAddr::Unbound => String::from("(unbound)"),
1392 }
1393 }
1394 }
1395
1396 impl FromStr for SocketAddr {
1397 type Err = anyhow::Error;
1398
1399 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1400 match s {
1401 "" => {
1402 let random_string = rand::rng()
1405 .sample_iter(&Alphanumeric)
1406 .take(24)
1407 .map(char::from)
1408 .collect::<String>();
1409 SocketAddr::from_abstract_name(&random_string)
1410 }
1411 name if name.starts_with("@") => {
1413 SocketAddr::from_abstract_name(name.strip_prefix("@").unwrap())
1414 }
1415 path => SocketAddr::from_pathname(path),
1416 }
1417 }
1418 }
1419
1420 impl Eq for SocketAddr {}
1421 impl std::hash::Hash for SocketAddr {
1422 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1423 String::from(self).hash(state);
1424 }
1425 }
1426 impl PartialEq for SocketAddr {
1427 fn eq(&self, other: &Self) -> bool {
1428 match (self, other) {
1429 (Self::Bound(saddr), Self::Bound(oaddr)) => {
1430 if saddr.is_unnamed() || oaddr.is_unnamed() {
1431 return false;
1432 }
1433
1434 #[cfg(target_os = "linux")]
1435 {
1436 saddr.as_pathname() == oaddr.as_pathname()
1437 && saddr.as_abstract_name() == oaddr.as_abstract_name()
1438 }
1439 #[cfg(not(target_os = "linux"))]
1440 {
1441 saddr.as_pathname() == oaddr.as_pathname()
1443 }
1444 }
1445 (Self::Unbound, _) | (_, Self::Unbound) => false,
1446 }
1447 }
1448 }
1449
1450 impl fmt::Display for SocketAddr {
1451 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1452 match self {
1453 Self::Bound(addr) => match addr.as_pathname() {
1454 Some(path) => {
1455 write!(f, "{}", path.to_string_lossy())
1456 }
1457 #[cfg(target_os = "linux")]
1458 _ => match addr.as_abstract_name() {
1459 Some(name) => {
1460 if name.starts_with(b"@") {
1461 return write!(f, "{}", String::from_utf8_lossy(name));
1462 }
1463 write!(f, "@{}", String::from_utf8_lossy(name))
1464 }
1465 _ => write!(f, "(unnamed)"),
1466 },
1467 #[cfg(not(target_os = "linux"))]
1468 _ => write!(f, "(unnamed)"),
1469 },
1470 Self::Unbound => write!(f, "(unbound)"),
1471 }
1472 }
1473 }
1474
1475 impl SocketAddr {
1476 pub fn new(addr: StdSocketAddr) -> Self {
1478 Self::Bound(Box::new(addr))
1479 }
1480
1481 #[cfg(target_os = "linux")]
1484 pub fn from_abstract_name(name: &str) -> anyhow::Result<Self> {
1485 Ok(Self::new(StdSocketAddr::from_abstract_name(
1486 name.strip_prefix("@").unwrap_or(name),
1487 )?))
1488 }
1489
1490 #[cfg(not(target_os = "linux"))]
1491 pub fn from_abstract_name(name: &str) -> anyhow::Result<Self> {
1492 let name = name.strip_prefix("@").unwrap_or(name);
1494 let path = Self::abstract_to_filesystem_path(name);
1495 Self::from_pathname(&path.to_string_lossy())
1496 }
1497
1498 #[cfg(not(target_os = "linux"))]
1499 fn abstract_to_filesystem_path(abstract_name: &str) -> std::path::PathBuf {
1500 use std::collections::hash_map::DefaultHasher;
1501 use std::hash::Hash;
1502 use std::hash::Hasher;
1503
1504 let mut hasher = DefaultHasher::new();
1506 abstract_name.hash(&mut hasher);
1507 let hash = hasher.finish();
1508
1509 let process_id = std::process::id();
1511
1512 std::path::PathBuf::from(format!("/tmp/hyperactor_{}_{:x}", process_id, hash))
1514 }
1515
1516 pub fn from_pathname(name: &str) -> anyhow::Result<Self> {
1518 Ok(Self::new(StdSocketAddr::from_pathname(name)?))
1519 }
1520 }
1521
1522 impl TryFrom<SocketAddr> for StdSocketAddr {
1523 type Error = anyhow::Error;
1524
1525 fn try_from(value: SocketAddr) -> Result<Self, Self::Error> {
1526 match value {
1527 SocketAddr::Bound(addr) => Ok(*addr),
1528 SocketAddr::Unbound => Err(anyhow::anyhow!(
1529 "std::os::unix::SocketAddr must be a bound address"
1530 )),
1531 }
1532 }
1533 }
1534}
1535
1536pub(crate) mod tcp {
1537 use tokio::net::TcpListener;
1538 use tokio::net::TcpStream;
1539
1540 use super::*;
1541
1542 #[derive(Debug)]
1543 pub(crate) struct TcpLink {
1544 pub(super) addr: SocketAddr,
1545 pub(super) session_id: SessionId,
1546 pub(super) stream_id: u8,
1547 pub(super) kind: ProtocolKind,
1548 }
1549
1550 #[async_trait]
1551 impl Link for TcpLink {
1552 type Stream = TcpStream;
1553
1554 fn dest(&self) -> ChannelAddr {
1555 ChannelAddr::Tcp(self.addr)
1556 }
1557
1558 fn link_id(&self) -> SessionId {
1559 self.session_id
1560 }
1561
1562 async fn next(&mut self) -> Result<Self::Stream, ClientError> {
1563 let session_id = self.session_id;
1564 let reconnect_timeout =
1565 hyperactor_config::global::get(config::CHANNEL_RECONNECT_TIMEOUT);
1566 let mut backoff = ExponentialBackoffBuilder::new()
1567 .with_initial_interval(Duration::from_millis(1))
1568 .with_multiplier(2.0)
1569 .with_randomization_factor(0.1)
1570 .with_max_interval(Duration::from_millis(1000))
1571 .with_max_elapsed_time(Some(reconnect_timeout))
1572 .build();
1573 loop {
1574 match TcpStream::connect(&self.addr).await {
1575 Ok(mut stream) => {
1576 stream.set_nodelay(true).map_err(|err| {
1577 ClientError::Connect(
1578 self.dest(),
1579 err,
1580 "cannot disable Nagle algorithm".to_string(),
1581 )
1582 })?;
1583 set_tcp_keepalive(&stream);
1584 write_link_init(&mut stream, session_id, self.stream_id, self.kind)
1585 .await
1586 .map_err(|err| ClientError::Io(self.dest(), err))?;
1587 return Ok(stream);
1588 }
1589 Err(err) => {
1590 tracing::debug!(error = %err, "tcp connect failed, backing off");
1591 match backoff.next_backoff() {
1592 Some(delay) => tokio::time::sleep(delay).await,
1593 None => {
1594 return Err(ClientError::ConnectTimeout(
1595 self.dest(),
1596 reconnect_timeout,
1597 err,
1598 ));
1599 }
1600 }
1601 }
1602 }
1603 }
1604 }
1605 }
1606
1607 #[derive(Debug)]
1609 pub(crate) struct TcpSocketListener {
1610 pub(super) inner: TcpListener,
1611 pub(super) addr: SocketAddr,
1612 }
1613
1614 #[async_trait]
1615 impl super::Listener for TcpSocketListener {
1616 type Stream = TcpStream;
1617
1618 async fn accept(&mut self) -> Result<(Self::Stream, ChannelAddr), ServerError> {
1619 let (stream, peer_addr) = self
1620 .inner
1621 .accept()
1622 .await
1623 .map_err(|err| ServerError::Io(ChannelAddr::Tcp(self.addr), err))?;
1624 stream
1625 .set_nodelay(true)
1626 .map_err(|err| ServerError::Io(ChannelAddr::Tcp(self.addr), err))?;
1627 set_tcp_keepalive(&stream);
1628 Ok((stream, ChannelAddr::Tcp(peer_addr)))
1629 }
1630 }
1631
1632 pub(crate) fn link(
1634 addr: SocketAddr,
1635 session_id: SessionId,
1636 stream_id: u8,
1637 kind: ProtocolKind,
1638 ) -> TcpLink {
1639 TcpLink {
1640 addr,
1641 session_id,
1642 stream_id,
1643 kind,
1644 }
1645 }
1646}
1647
1648pub(crate) mod meta {
1650 use std::io;
1651 use std::path::PathBuf;
1652 use std::sync::Arc;
1653
1654 use anyhow::Result;
1655 use tokio_rustls::TlsAcceptor;
1656 use tokio_rustls::TlsConnector;
1657
1658 use super::*;
1659 use crate::config::Pem;
1660 use crate::config::PemBundle;
1661
1662 const THRIFT_TLS_SRV_CA_PATH_ENV: &str = "THRIFT_TLS_SRV_CA_PATH";
1663 const DEFAULT_SRV_CA_PATH: &str = "/var/facebook/rootcanal/ca.pem";
1664 const THRIFT_TLS_CL_CERT_PATH_ENV: &str = "THRIFT_TLS_CL_CERT_PATH";
1665 const THRIFT_TLS_CL_KEY_PATH_ENV: &str = "THRIFT_TLS_CL_KEY_PATH";
1666 const DEFAULT_SERVER_PEM_PATH: &str = "/var/facebook/x509_identities/server.pem";
1667
1668 #[allow(clippy::result_large_err)] pub(crate) fn parse(addr_string: &str) -> Result<ChannelAddr, ChannelError> {
1670 let parts = addr_string.rsplit_once(":");
1672 match parts {
1673 Some((hostname, port_str)) => {
1674 let Ok(port) = port_str.parse() else {
1675 return Err(ChannelError::InvalidAddress(addr_string.to_string()));
1676 };
1677 Ok(ChannelAddr::MetaTls(TlsAddr::new(hostname, port)))
1678 }
1679 _ => Err(ChannelError::InvalidAddress(addr_string.to_string())),
1680 }
1681 }
1682
1683 pub(super) fn get_server_pem_bundle() -> PemBundle {
1686 let ca_path = std::env::var_os(THRIFT_TLS_SRV_CA_PATH_ENV)
1687 .map(PathBuf::from)
1688 .unwrap_or_else(|| PathBuf::from(DEFAULT_SRV_CA_PATH));
1689 let server_pem_path = PathBuf::from(DEFAULT_SERVER_PEM_PATH);
1690 PemBundle {
1691 ca: Pem::File(ca_path),
1692 cert: Pem::File(server_pem_path.clone()),
1693 key: Pem::File(server_pem_path),
1694 }
1695 }
1696
1697 fn get_client_pem_bundle() -> Option<PemBundle> {
1700 let cert_path = std::env::var_os(THRIFT_TLS_CL_CERT_PATH_ENV)?;
1701 let key_path = std::env::var_os(THRIFT_TLS_CL_KEY_PATH_ENV)?;
1702 let ca_path = std::env::var_os(THRIFT_TLS_SRV_CA_PATH_ENV)
1703 .map(PathBuf::from)
1704 .unwrap_or_else(|| PathBuf::from(DEFAULT_SRV_CA_PATH));
1705 Some(PemBundle {
1706 ca: Pem::File(ca_path),
1707 cert: Pem::File(PathBuf::from(cert_path)),
1708 key: Pem::File(PathBuf::from(key_path)),
1709 })
1710 }
1711
1712 pub(crate) fn tls_acceptor(enforce_client_tls: bool) -> Result<TlsAcceptor> {
1714 Ok(TlsAcceptor::from(Arc::new(server_config(
1715 enforce_client_tls,
1716 )?)))
1717 }
1718
1719 pub(super) fn try_tls_connector() -> Result<TlsConnector> {
1725 tls_connector()
1726 }
1727
1728 fn tls_connector() -> Result<TlsConnector> {
1731 Ok(TlsConnector::from(Arc::new(client_config()?)))
1732 }
1733
1734 pub(super) fn server_config(enforce_client_tls: bool) -> Result<rustls::ServerConfig> {
1735 let bundle = get_server_pem_bundle();
1736 tls::server_config_from_bundle(&bundle, enforce_client_tls)
1737 }
1738
1739 pub(super) fn client_config() -> Result<rustls::ClientConfig> {
1740 Ok(if let Some(bundle) = get_client_pem_bundle() {
1741 tls::client_config_from_bundle(&bundle)?
1742 } else {
1743 let ca_path = std::env::var_os(THRIFT_TLS_SRV_CA_PATH_ENV)
1744 .map(PathBuf::from)
1745 .unwrap_or_else(|| PathBuf::from(DEFAULT_SRV_CA_PATH));
1746 let ca_pem = Pem::File(ca_path);
1747 tls::client_config_from_ca(&ca_pem)?
1748 })
1749 }
1750
1751 pub fn link(
1753 addr: TlsAddr,
1754 session_id: SessionId,
1755 stream_id: u8,
1756 kind: ProtocolKind,
1757 ) -> Result<tls::TlsLink, ClientError> {
1758 let connector = tls_connector().map_err(|e| {
1759 ClientError::Connect(
1760 ChannelAddr::MetaTls(addr.clone()),
1761 io::Error::other(e.to_string()),
1762 "failed to create TLS connector".to_string(),
1763 )
1764 })?;
1765 let TlsAddr { hostname, port } = addr;
1766 Ok(tls::TlsLink {
1767 hostname,
1768 port,
1769 connector,
1770 addr_type: tls::TlsAddrType::MetaTls,
1771 session_id,
1772 stream_id,
1773 kind,
1774 })
1775 }
1776}
1777
1778pub(crate) mod tls {
1780 use std::io;
1781 use std::io::BufReader;
1782 use std::sync::Arc;
1783
1784 use anyhow::Context;
1785 use anyhow::Result;
1786 use rustls::ClientConfig;
1787 use rustls::RootCertStore;
1788 use rustls::ServerConfig;
1789 use rustls::pki_types::CertificateDer;
1790 use rustls::pki_types::PrivateKeyDer;
1791 use rustls::pki_types::ServerName;
1792 use tokio::net::TcpStream;
1793 use tokio_rustls::TlsAcceptor;
1794 use tokio_rustls::TlsConnector;
1795 use tokio_rustls::client::TlsStream;
1796
1797 use super::*;
1798 use crate::channel::TlsAddr;
1799 use crate::config::Pem;
1800 use crate::config::PemBundle;
1801 use crate::config::TLS_CA;
1802 use crate::config::TLS_CERT;
1803 use crate::config::TLS_KEY;
1804
1805 #[derive(Debug, Clone, Copy)]
1807 pub(crate) enum TlsAddrType {
1808 Tls,
1809 MetaTls,
1810 }
1811
1812 #[allow(clippy::result_large_err)]
1814 pub(crate) fn parse(addr_string: &str) -> Result<ChannelAddr, ChannelError> {
1815 let parts = addr_string.rsplit_once(":");
1817 match parts {
1818 Some((hostname, port_str)) => {
1819 let Ok(port) = port_str.parse() else {
1820 return Err(ChannelError::InvalidAddress(addr_string.to_string()));
1821 };
1822 Ok(ChannelAddr::Tls(TlsAddr::new(hostname, port)))
1823 }
1824 _ => Err(ChannelError::InvalidAddress(addr_string.to_string())),
1825 }
1826 }
1827
1828 pub(super) fn load_certs(pem: &Pem) -> Result<Vec<CertificateDer<'static>>> {
1830 let mut reader = BufReader::new(pem.reader()?);
1831 let certs = rustls_pemfile::certs(&mut reader)
1832 .filter_map(Result::ok)
1833 .collect();
1834 Ok(certs)
1835 }
1836
1837 pub(super) fn load_key(pem: &Pem) -> Result<PrivateKeyDer<'static>> {
1839 let mut reader = BufReader::new(pem.reader()?);
1840 loop {
1841 break match rustls_pemfile::read_one(&mut reader)? {
1842 Some(rustls_pemfile::Item::Pkcs1Key(key)) => Ok(PrivateKeyDer::Pkcs1(key)),
1843 Some(rustls_pemfile::Item::Pkcs8Key(key)) => Ok(PrivateKeyDer::Pkcs8(key)),
1844 Some(rustls_pemfile::Item::Sec1Key(key)) => Ok(PrivateKeyDer::Sec1(key)),
1845 Some(_) => continue,
1846 None => anyhow::bail!("no private key found in TLS key file"),
1847 };
1848 }
1849 }
1850
1851 pub(super) fn build_root_store(ca_pem: &Pem) -> Result<RootCertStore> {
1853 let mut root_store = RootCertStore::empty();
1854 let certs = load_certs(ca_pem)?;
1855 root_store.add_parsable_certificates(certs);
1856 Ok(root_store)
1857 }
1858
1859 pub(super) fn get_pem_bundle() -> PemBundle {
1861 PemBundle {
1862 ca: hyperactor_config::global::get_cloned(TLS_CA),
1863 cert: hyperactor_config::global::get_cloned(TLS_CERT),
1864 key: hyperactor_config::global::get_cloned(TLS_KEY),
1865 }
1866 }
1867
1868 fn install_default_crypto_provider() {
1869 let _ = rustls::crypto::ring::default_provider().install_default();
1872 }
1873
1874 pub(super) fn server_config_from_bundle(
1877 bundle: &PemBundle,
1878 enforce_client_tls: bool,
1879 ) -> Result<ServerConfig> {
1880 install_default_crypto_provider();
1881
1882 let certs = load_certs(&bundle.cert).context("load TLS certificate")?;
1883 let key = load_key(&bundle.key).context("load TLS key")?;
1884 let root_store = build_root_store(&bundle.ca).context("build root cert store")?;
1885
1886 let config = ServerConfig::builder();
1887 let config = if enforce_client_tls {
1888 let client_verifier =
1890 rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
1891 .build()
1892 .map_err(|e| anyhow::anyhow!("failed to build client verifier: {}", e))?;
1893 config.with_client_cert_verifier(client_verifier)
1894 } else {
1895 config.with_no_client_auth()
1896 }
1897 .with_single_cert(certs, key)?;
1898
1899 Ok(config)
1900 }
1901
1902 pub(super) fn tls_acceptor_from_bundle(
1905 bundle: &PemBundle,
1906 enforce_client_tls: bool,
1907 ) -> Result<TlsAcceptor> {
1908 let config = server_config_from_bundle(bundle, enforce_client_tls)?;
1909 Ok(TlsAcceptor::from(Arc::new(config)))
1910 }
1911
1912 pub(crate) fn tls_acceptor() -> Result<TlsAcceptor> {
1914 tls_acceptor_from_bundle(&get_pem_bundle(), true)
1915 }
1916
1917 pub(super) fn client_config_from_ca(ca_pem: &Pem) -> Result<ClientConfig> {
1919 install_default_crypto_provider();
1920
1921 let root_store = build_root_store(ca_pem).context("build root cert store")?;
1922 Ok(ClientConfig::builder()
1923 .with_root_certificates(Arc::new(root_store))
1924 .with_no_client_auth())
1925 }
1926
1927 pub(super) fn client_config_from_bundle(bundle: &PemBundle) -> Result<ClientConfig> {
1929 install_default_crypto_provider();
1930
1931 let certs = load_certs(&bundle.cert).context("load TLS certificate")?;
1932 let key = load_key(&bundle.key).context("load TLS key")?;
1933 let root_store = build_root_store(&bundle.ca).context("build root cert store")?;
1934
1935 let config = ClientConfig::builder()
1936 .with_root_certificates(Arc::new(root_store))
1937 .with_client_auth_cert(certs, key)
1938 .context("configure client auth")?;
1939
1940 Ok(config)
1941 }
1942
1943 pub(super) fn tls_connector_from_bundle(bundle: &PemBundle) -> Result<TlsConnector> {
1945 let config = client_config_from_bundle(bundle)?;
1946 Ok(TlsConnector::from(Arc::new(config)))
1947 }
1948
1949 fn tls_connector() -> Result<TlsConnector> {
1951 tls_connector_from_bundle(&get_pem_bundle())
1952 }
1953
1954 pub(crate) struct TlsLink {
1956 pub(crate) hostname: Hostname,
1957 pub(crate) port: Port,
1958 pub(crate) connector: TlsConnector,
1959 pub(crate) addr_type: TlsAddrType,
1960 pub(crate) session_id: SessionId,
1961 pub(crate) stream_id: u8,
1962 pub(crate) kind: ProtocolKind,
1963 }
1964
1965 impl std::fmt::Debug for TlsLink {
1966 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1967 f.debug_struct("TlsLink")
1968 .field("hostname", &self.hostname)
1969 .field("port", &self.port)
1970 .field("addr_type", &self.addr_type)
1971 .finish()
1972 }
1973 }
1974
1975 #[async_trait]
1976 impl Link for TlsLink {
1977 type Stream = TlsStream<TcpStream>;
1978
1979 fn dest(&self) -> ChannelAddr {
1980 let addr = TlsAddr::new(self.hostname.clone(), self.port);
1981 match self.addr_type {
1982 TlsAddrType::Tls => ChannelAddr::Tls(addr),
1983 TlsAddrType::MetaTls => ChannelAddr::MetaTls(addr),
1984 }
1985 }
1986
1987 fn link_id(&self) -> SessionId {
1988 self.session_id
1989 }
1990
1991 async fn next(&mut self) -> Result<Self::Stream, ClientError> {
1992 let session_id = self.session_id;
1993 let server_name = ServerName::try_from(self.hostname.clone()).map_err(|e| {
1994 ClientError::Connect(
1995 self.dest(),
1996 io::Error::other(e.to_string()),
1997 "invalid server name".to_string(),
1998 )
1999 })?;
2000 let reconnect_timeout =
2001 hyperactor_config::global::get(config::CHANNEL_RECONNECT_TIMEOUT);
2002 let mut backoff = ExponentialBackoffBuilder::new()
2003 .with_initial_interval(Duration::from_millis(1))
2004 .with_multiplier(2.0)
2005 .with_randomization_factor(0.1)
2006 .with_max_interval(Duration::from_millis(1000))
2007 .with_max_elapsed_time(Some(reconnect_timeout))
2008 .build();
2009 loop {
2010 let mut addrs = (self.hostname.as_ref(), self.port)
2011 .to_socket_addrs()
2012 .map_err(|_| ClientError::Resolve(self.dest()))?;
2013 let addr = addrs.next().ok_or(ClientError::Resolve(self.dest()))?;
2014 match TcpStream::connect(&addr).await {
2015 Ok(stream) => {
2016 stream.set_nodelay(true).map_err(|err| {
2017 ClientError::Connect(
2018 self.dest(),
2019 err,
2020 "cannot disable Nagle algorithm".to_string(),
2021 )
2022 })?;
2023 set_tcp_keepalive(&stream);
2024 let mut tls_stream = self
2025 .connector
2026 .connect(server_name.clone(), stream)
2027 .await
2028 .map_err(|err| {
2029 tracing::info!(
2030 dest = %self.dest(),
2031 error = %err,
2032 "TLS handshake failed"
2033 );
2034 ClientError::Connect(
2035 self.dest(),
2036 err,
2037 format!("cannot establish TLS connection to {:?}", server_name),
2038 )
2039 })?;
2040 write_link_init(&mut tls_stream, session_id, self.stream_id, self.kind)
2041 .await
2042 .map_err(|err| ClientError::Io(self.dest(), err))?;
2043 return Ok(tls_stream);
2044 }
2045 Err(err) => {
2046 tracing::debug!(error = %err, "tls connect failed, backing off");
2047 match backoff.next_backoff() {
2048 Some(delay) => tokio::time::sleep(delay).await,
2049 None => {
2050 return Err(ClientError::ConnectTimeout(
2051 self.dest(),
2052 reconnect_timeout,
2053 err,
2054 ));
2055 }
2056 }
2057 }
2058 }
2059 }
2060 }
2061 }
2062
2063 pub fn link(
2065 addr: TlsAddr,
2066 session_id: SessionId,
2067 stream_id: u8,
2068 kind: ProtocolKind,
2069 ) -> Result<TlsLink, ClientError> {
2070 let connector = tls_connector().map_err(|e| {
2071 ClientError::Connect(
2072 ChannelAddr::Tls(addr.clone()),
2073 io::Error::other(e.to_string()),
2074 "failed to create TLS connector".to_string(),
2075 )
2076 })?;
2077 let TlsAddr { hostname, port } = addr;
2078 Ok(TlsLink {
2079 hostname,
2080 port,
2081 connector,
2082 addr_type: TlsAddrType::Tls,
2083 session_id,
2084 stream_id,
2085 kind,
2086 })
2087 }
2088
2089 #[cfg(test)]
2090 mod tests {
2091 use timed_test::async_timed_test;
2092
2093 use super::*;
2094 use crate::channel::ChannelTx;
2095 use crate::channel::Rx;
2096 use crate::channel::Tx;
2097 use crate::channel::dial;
2098 use crate::channel::net::server;
2099 use crate::channel::serve;
2100 use crate::channel::unordered;
2101 use crate::config::Pem;
2102 use crate::config::TLS_CA;
2103 use crate::config::TLS_CERT;
2104 use crate::config::TLS_KEY;
2105
2106 const TEST_CA_CERT: &str = r#"-----BEGIN CERTIFICATE-----
2110MIIDBTCCAe2gAwIBAgIUaGNmboiIosG+8Up0vgDr/+cg+2IwDQYJKoZIhvcNAQEL
2111BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNjAxMjgxNzA4MzlaFw0yNzAxMjgx
2112NzA4MzlaMBIxEDAOBgNVBAMMB1Rlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
2113DwAwggEKAoIBAQC9RBoMYXCajklswt8Vi1JI1lEYzic0WNOmz45vG/7H6jTWkgL3
2114K5Ri+Seg3MobDNc48YHWXYm4hP9wCzkx8ih3ntT5XiY1My/G3jLUuoIEE9pF/BoJ
2115YQwZVoPNFhA9WhXNRsINf1cXFf8NzRfXpxBfKWtQJxYXU4JiDBQ6rLnQQABo8JmQ
2116vYFhJbBaYip5jTSiVNn7mB1zNr5jsVxuoSF53Pb7xQ76bwBdOq4zd6PSxL5/lr4G
2117cHSoxwZQdZMG7PL6hbxDQ2S2YI2lYVET1zwc2WPKCfjbEXBC/jzx828CInQtuksk
211818gJt6xHkTFEA8CSA29GM3lejnwYWf51xyyBAgMBAAGjUzBRMB0GA1UdDgQWBBRX
2119cbxSZ70NsUkAS3Hhy6irugywJDAfBgNVHSMEGDAWgBRXcbxSZ70NsUkAS3Hhy6ir
2120ugywJDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA7aAFfyW67
2121Z+uGSVYhpsT/uH/3Z3nr7X1smTz5CGEfq2czEcTC7gbYI2l8GZ47GPfnAvHTBZVm
2122V/XncBCsj7/thOh2jYEHFyCbPckoaSCRyCOnK7LPUlr4HN5uP9EFe45qBLCJDEoY
2123GTTw7MtzwdovfjchNfKQCTtkBJCXQ95WLCf6UOh02Sn28UTlgfXzF0X0FrcWqWa3
2124uJZd4XOo4O6hKKlHaBaQPiEr++1xc3SWPV7jZHbckI/vKBnDdEZ9JQX5fFZuypUI
2125sgomYHxvxrU2hWx+7k53CRdjfaIvT9Ie44z9sSdsU/+blw2S8f/ZTmuECoIAAXYO
21260qpzlxZMdr7T
2127-----END CERTIFICATE-----"#;
2128
2129 const TEST_SERVER_CERT: &str = r#"-----BEGIN CERTIFICATE-----
2131MIIDJDCCAgygAwIBAgIUaz66DsWaH5ZXM4hCFnbVbMsyN1cwDQYJKoZIhvcNAQEL
2132BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNjAxMjgxNzA4MzlaFw0yNzAxMjgx
2133NzA4MzlaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQAD
2134ggEPADCCAQoCggEBAKCbp++qNyTn5LOsV0h9gLKJALBcjg2A14I3804N9UyDhPW2
2135QKQ2W424u2P1MfKrw/2C+CErGlrADlnco2RQVDAarAIuGdFvBOt5UezqOS7Mk4OS
21369MlS7NZnMbc37KuM9UIG5ScJjXR/Z5z9dxeR0I9y3n0Ix6khbV7tOSHobiweI0FI
21378LftBS+CQnXr6vbWPcHcW6Z0FHUv7IWhqMWmv9PlZRGe9Y6VzXrRp0PBnZMOnAYf
2138aMQUwYRswWdm9j9Z1sMdTJ14G+KVmO3Vj6XI6Sm9uIcYhlwG/kORwogJFWlVuP9o
2139rloFRCjyHJ1d7GZqqnRyHHDDCBms8ed+3YfEYQECAwEAAaNwMG4wLAYDVR0RBCUw
2140I4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQWBBQl
2141J4vxUoCzqqeTwQAiLqE8wYezKzAfBgNVHSMEGDAWgBRXcbxSZ70NsUkAS3Hhy6ir
2142ugywJDANBgkqhkiG9w0BAQsFAAOCAQEAnXHIBDQ4AHAMV71piTOuI41ShASQed6L
2143bi7XUMZgZDslLkfU1vnP3BlwpliraBsAytSYQC6kbytOuz1uQ4K7yLb2tAAmUgEO
2144EdIVt9SXr5tCcIPeLmInF0pysPqjZO8n7vtJyd9gryKqdhm1uzA7WQWq/Az8a9Sk
2145uW2J6Oc5p6P7Mf3/ixqXzvGRo8rzu0CUJOJ67UTE/HhbJuplQ5dep5CEEOAIsAtH
2146zn9O4rW92ueBkoBJM++YILS1vQ7jKc2N3RNrnHm7FeootBrtR9mBi0TH97K73ZPZ
21472Cdhnym0CsCJggrllFGH32cYo7+K2PO7/4oj5XbBCSWcssicvd8ovg==
2148-----END CERTIFICATE-----"#;
2149
2150 const TEST_SERVER_KEY: &str = r#"-----BEGIN PRIVATE KEY-----
2154MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCgm6fvqjck5+Sz
2155rFdIfYCyiQCwXI4NgNeCN/NODfVMg4T1tkCkNluNuLtj9THyq8P9gvghKxpawA5Z
21563KNkUFQwGqwCLhnRbwTreVHs6jkuzJODkvTJUuzWZzG3N+yrjPVCBuUnCY10f2ec
2157/XcXkdCPct59CMepIW1e7Tkh6G4sHiNBSPC37QUvgkJ16+r21j3B3FumdBR1L+yF
2158oajFpr/T5WURnvWOlc160adDwZ2TDpwGH2jEFMGEbMFnZvY/WdbDHUydeBvilZjt
21591Y+lyOkpvbiHGIZcBv5DkcKICRVpVbj/aK5aBUQo8hydXexmaqp0chxwwwgZrPHn
2160ft2HxGEBAgMBAAECgf8G5qlQov+7ljs9fSpC8yGUik59RXzVF7Qq5DyQHglsQDp2
2161VF5yr+M/M7DZmq+KvdauDfKbej6np5j2Q4TByrHTX1IExfZWCW8srwnWJDpQyHmO
2162LcJW5DlI/SYluUFyHZxsOd+ezcpGNzM8i6eSW7GaeFUXCkmJ+uW4LnlF+7bALnnd
2163D6sak/58EsII+IJyd4lFn+voszlPn3CZGR0jkp21rvpaKgrMIsKVWWQO/sLDU5pr
2164VbpBThcLU5gRcnQouQX12e2VTCIlFu75WTsJ8V/KnEaOZUVlU/B/Bs+WQF3U+/Jo
2165eX4N+D6OsEcNQjERAFyWujxsl1WpD4uSsbFMN0ECgYEA2b7AdL+oKPQHku2KcBhr
2166Zw8K4tMDlr2VPPNwZcBTLo+O71vv/xXjMcXrXmowzkgEQckUmt1VB46riyydhwdP
2167/n9ciWcz0Va/nwHR6Y9F9unBiyUBP7PRhRyjQyRZZRGDSJvP+Xmc5UJFpRr07VLU
2168nfgMXDj37vXzKDpfhdEB2nkCgYEAvNMfA8P8w3+6246x5YHflvTkPdw+2oyge+LD
2169mphB/w7SF8mlyNGloj3+KBZmd9SkvT57wCvO96Y9/n+mBAVisRggc0hK4ymOVYhb
2170+im/JvqGQMbVeg6iCOHnWdaZf9tL8uVsegQy3kVTN7vAa+CMFgX1dt65cGBX6XkB
217144pYmMkCgYALhbiRdQLlB+TOtZs5y1EDpxwgXKI3+9hF3Wv5NnAwapBZwje0++eF
21723r9Rw7TJda4j/QwGFehF+hrBxp6fYpetE/hFnRx0225Qb7w368j8A+ql/lNOl6li
2173rd1F1EqWupKD6RrcTL8sspEU55RGaretlE6zIqCcGI/BdTVQ03qRoQKBgHDC3zWf
2174d7XD9HGjQGdfbIe4jQjIGxzmd/wjik4q+NZ5IkukVwWa9P/zZ3DHF8Ad05dT1hEH
21752FwaAdGWpyyljq9VSiOuG1KXAXHgsZSuE4ISf9P1KYzvaiJFzaPfvOEWs79E9MfU
21769A+6dJzG2X1SpjWMr26iSTlrv3QkmFUqzAfJAoGASBkn4wls+oC5rv/Mch43pBv5
2177UmKru4ltnEHJZdbSi2DJ+AnDLD222JCasb1VT1tm2XgW6DBqrdVRPPP6GOlB0MHU
2178+3ULtZxAczt7I+ST2bo0/DV2Hse89Cm63w4wLOiVZs7+1wrAzJZLokWF7Q5gesra
2179u19txmtkiMEH+aNmekk=
2180-----END PRIVATE KEY-----"#;
2181
2182 #[async_timed_test(timeout_secs = 30)]
2183 async fn test_tls_basic() {
2184 let _ = rustls::crypto::ring::default_provider().install_default();
2187
2188 let config = hyperactor_config::global::lock();
2190 let _guard_cert =
2191 config.override_key(TLS_CERT, Pem::Value(TEST_SERVER_CERT.as_bytes().to_vec()));
2192 let _guard_key =
2193 config.override_key(TLS_KEY, Pem::Value(TEST_SERVER_KEY.as_bytes().to_vec()));
2194 let _guard_ca =
2195 config.override_key(TLS_CA, Pem::Value(TEST_CA_CERT.as_bytes().to_vec()));
2196
2197 let addr = TlsAddr::new("localhost", 0);
2199
2200 let (local_addr, mut rx) =
2201 server::serve::<u64>(ChannelAddr::Tls(addr), None).expect("failed to serve");
2202
2203 let tx: ChannelTx<u64> = super::spawn(
2205 link(
2206 match &local_addr {
2207 ChannelAddr::Tls(addr) => addr.clone(),
2208 _ => panic!("unexpected address type"),
2209 },
2210 SessionId::random(),
2211 0,
2212 super::ProtocolKind::Simplex,
2213 )
2214 .expect("failed to create link"),
2215 );
2216
2217 tx.post(42u64);
2219
2220 let received = rx.recv().await.expect("failed to receive");
2222 assert_eq!(received, 42u64);
2223 }
2224
2225 #[async_timed_test(timeout_secs = 30)]
2226 async fn test_quic_basic() {
2227 let _ = rustls::crypto::ring::default_provider().install_default();
2228
2229 let config = hyperactor_config::global::lock();
2230 let _guard_cert =
2231 config.override_key(TLS_CERT, Pem::Value(TEST_SERVER_CERT.as_bytes().to_vec()));
2232 let _guard_key =
2233 config.override_key(TLS_KEY, Pem::Value(TEST_SERVER_KEY.as_bytes().to_vec()));
2234 let _guard_ca =
2235 config.override_key(TLS_CA, Pem::Value(TEST_CA_CERT.as_bytes().to_vec()));
2236
2237 let (addr, mut rx) =
2238 serve::<u64>(ChannelAddr::Quic(TlsAddr::new("localhost", 0))).unwrap();
2239 let tx = dial::<u64>(addr).unwrap();
2240
2241 tx.post(42u64);
2242
2243 let received = rx.recv().await.expect("failed to receive");
2244 assert_eq!(received, 42u64);
2245 }
2246
2247 #[async_timed_test(timeout_secs = 30)]
2248 async fn test_quic_unordered_basic() {
2249 let _ = rustls::crypto::ring::default_provider().install_default();
2250
2251 let config = hyperactor_config::global::lock();
2252 let _guard_cert =
2253 config.override_key(TLS_CERT, Pem::Value(TEST_SERVER_CERT.as_bytes().to_vec()));
2254 let _guard_key =
2255 config.override_key(TLS_KEY, Pem::Value(TEST_SERVER_KEY.as_bytes().to_vec()));
2256 let _guard_ca =
2257 config.override_key(TLS_CA, Pem::Value(TEST_CA_CERT.as_bytes().to_vec()));
2258
2259 let (addr, mut rx) =
2260 unordered::serve::<u64>(ChannelAddr::Quic(TlsAddr::new("localhost", 0))).unwrap();
2261 let tx = unordered::dial::<u64>(addr, 2).unwrap();
2262
2263 for i in 0..8 {
2264 tx.post(i);
2265 }
2266
2267 let mut received = Vec::new();
2268 for _ in 0..8 {
2269 received.push(rx.recv().await.expect("failed to receive"));
2270 }
2271 received.sort();
2272 assert_eq!(received, (0..8).collect::<Vec<_>>());
2273 }
2274
2275 #[async_timed_test(timeout_secs = 30)]
2276 async fn test_tls_multiple_messages() {
2277 let _ = rustls::crypto::ring::default_provider().install_default();
2278
2279 let config = hyperactor_config::global::lock();
2281 let _guard_cert =
2282 config.override_key(TLS_CERT, Pem::Value(TEST_SERVER_CERT.as_bytes().to_vec()));
2283 let _guard_key =
2284 config.override_key(TLS_KEY, Pem::Value(TEST_SERVER_KEY.as_bytes().to_vec()));
2285 let _guard_ca =
2286 config.override_key(TLS_CA, Pem::Value(TEST_CA_CERT.as_bytes().to_vec()));
2287
2288 let addr = TlsAddr::new("localhost", 0);
2289
2290 let (local_addr, mut rx) =
2291 server::serve::<String>(ChannelAddr::Tls(addr), None).expect("failed to serve");
2292 let tx: ChannelTx<String> = super::spawn(
2293 link(
2294 match &local_addr {
2295 ChannelAddr::Tls(addr) => addr.clone(),
2296 _ => panic!("unexpected address type"),
2297 },
2298 SessionId::random(),
2299 0,
2300 super::ProtocolKind::Simplex,
2301 )
2302 .expect("failed to create link"),
2303 );
2304
2305 for i in 0..10 {
2307 tx.post(format!("message {}", i));
2308 }
2309
2310 for i in 0..10 {
2312 let received = rx.recv().await.expect("failed to receive");
2313 assert_eq!(received, format!("message {}", i));
2314 }
2315 }
2316
2317 #[test]
2318 fn test_tls_parse_hostname_port() {
2319 let addr = parse("localhost:8080").expect("failed to parse");
2320 assert!(matches!(
2321 addr,
2322 ChannelAddr::Tls(TlsAddr { hostname, port })
2323 if hostname == "localhost" && port == 8080
2324 ));
2325 }
2326
2327 #[test]
2328 fn test_tls_parse_socket_addr() {
2329 let addr = parse("127.0.0.1:8080").expect("failed to parse");
2330 assert!(matches!(
2331 addr,
2332 ChannelAddr::Tls(TlsAddr { hostname, port })
2333 if hostname == "127.0.0.1" && port == 8080
2334 ));
2335 }
2336
2337 #[test]
2338 fn test_tls_certs_parsing() {
2339 let cert_pem = Pem::Value(TEST_SERVER_CERT.as_bytes().to_vec());
2341 let key_pem = Pem::Value(TEST_SERVER_KEY.as_bytes().to_vec());
2342 let ca_pem = Pem::Value(TEST_CA_CERT.as_bytes().to_vec());
2343
2344 let certs = super::load_certs(&cert_pem).expect("failed to load certs");
2345 assert!(!certs.is_empty(), "expected at least one certificate");
2346
2347 let _key = super::load_key(&key_pem).expect("failed to load key");
2348
2349 let root_store = super::build_root_store(&ca_pem).expect("failed to build root store");
2350 assert!(!root_store.is_empty(), "expected at least one CA cert");
2351 }
2352
2353 #[test]
2354 fn test_tls_acceptor_creation() {
2355 let _ = rustls::crypto::ring::default_provider().install_default();
2358
2359 let config = hyperactor_config::global::lock();
2361 let _guard_cert =
2362 config.override_key(TLS_CERT, Pem::Value(TEST_SERVER_CERT.as_bytes().to_vec()));
2363 let _guard_key =
2364 config.override_key(TLS_KEY, Pem::Value(TEST_SERVER_KEY.as_bytes().to_vec()));
2365 let _guard_ca =
2366 config.override_key(TLS_CA, Pem::Value(TEST_CA_CERT.as_bytes().to_vec()));
2367
2368 let _acceptor = super::tls_acceptor().expect("failed to create TLS acceptor");
2370 }
2371
2372 #[test]
2373 fn test_tls_connector_creation() {
2374 let _ = rustls::crypto::ring::default_provider().install_default();
2377
2378 let config = hyperactor_config::global::lock();
2380 let _guard_cert =
2381 config.override_key(TLS_CERT, Pem::Value(TEST_SERVER_CERT.as_bytes().to_vec()));
2382 let _guard_key =
2383 config.override_key(TLS_KEY, Pem::Value(TEST_SERVER_KEY.as_bytes().to_vec()));
2384 let _guard_ca =
2385 config.override_key(TLS_CA, Pem::Value(TEST_CA_CERT.as_bytes().to_vec()));
2386
2387 let _connector = super::tls_connector().expect("failed to create TLS connector");
2389 }
2390 }
2391}
2392
2393fn oss_pem_bundle() -> crate::config::PemBundle {
2395 crate::config::PemBundle {
2396 ca: hyperactor_config::global::get_cloned(crate::config::TLS_CA),
2397 cert: hyperactor_config::global::get_cloned(crate::config::TLS_CERT),
2398 key: hyperactor_config::global::get_cloned(crate::config::TLS_KEY),
2399 }
2400}
2401
2402pub fn try_tls_pem_bundle() -> Option<crate::config::PemBundle> {
2412 let oss_bundle = oss_pem_bundle();
2413 if oss_bundle.ca.reader().is_ok() {
2414 return Some(oss_bundle);
2415 }
2416 tracing::debug!("OSS TLS bundle: CA not readable, trying Meta paths");
2417
2418 let meta_bundle = meta::get_server_pem_bundle();
2419 if meta_bundle.ca.reader().is_ok() {
2420 return Some(meta_bundle);
2421 }
2422 tracing::debug!("Meta TLS bundle: CA not readable, no TLS available");
2423
2424 None
2425}
2426
2427pub fn try_tls_acceptor(enforce_client_tls: bool) -> Option<tokio_rustls::TlsAcceptor> {
2447 let oss_bundle = oss_pem_bundle();
2448 if let Ok(acceptor) = tls::tls_acceptor_from_bundle(&oss_bundle, enforce_client_tls) {
2449 return Some(acceptor);
2450 }
2451 tracing::debug!("OSS TLS acceptor failed, trying Meta paths");
2452
2453 let meta_bundle = meta::get_server_pem_bundle();
2454 if let Ok(acceptor) = tls::tls_acceptor_from_bundle(&meta_bundle, enforce_client_tls) {
2455 return Some(acceptor);
2456 }
2457 tracing::debug!("Meta TLS acceptor failed, no TLS available");
2458
2459 None
2460}
2461
2462pub fn try_tls_connector() -> Option<tokio_rustls::TlsConnector> {
2474 let oss_bundle = oss_pem_bundle();
2475 if let Ok(connector) = tls::tls_connector_from_bundle(&oss_bundle) {
2476 return Some(connector);
2477 }
2478 if let Ok(config) = tls::client_config_from_ca(&oss_bundle.ca) {
2479 return Some(tokio_rustls::TlsConnector::from(Arc::new(config)));
2480 }
2481 tracing::debug!("OSS TLS connector failed, trying Meta paths");
2482
2483 if let Ok(connector) = meta::try_tls_connector() {
2484 return Some(connector);
2485 }
2486 tracing::debug!("Meta TLS connector failed, no TLS available");
2487
2488 None
2489}
2490
2491#[cfg(test)]
2492mod tests {
2493
2494 #![expect(
2495 clippy::await_holding_invalid_type,
2496 reason = "tracing_test::traced_test macro expansion holds tracing::span::Entered across awaits; can't be fixed in our code"
2497 )]
2498
2499 use std::assert_matches;
2500 use std::collections::VecDeque;
2501 use std::marker::PhantomData;
2502 use std::sync::Arc;
2503 use std::sync::RwLock;
2504 use std::sync::atomic::AtomicBool;
2505 use std::sync::atomic::AtomicU64;
2506 use std::sync::atomic::Ordering;
2507 use std::time::Duration;
2508 #[cfg(target_os = "linux")] use std::time::UNIX_EPOCH;
2510
2511 #[cfg(target_os = "linux")] use anyhow::Result;
2513 use bytes::Bytes;
2514 use rand::RngExt as _;
2515 use rand::SeedableRng as _;
2516 use rand::distr::Alphanumeric;
2517 use rand::rngs::SysRng;
2518 use timed_test::async_timed_test;
2519 use tokio::io::AsyncRead;
2520 use tokio::io::AsyncWrite;
2521 use tokio::io::DuplexStream;
2522 use tokio::io::ReadHalf;
2523 use tokio::io::WriteHalf;
2524 use tokio::task::JoinHandle;
2525 use tokio::time::Instant;
2526 use tokio_util::sync::CancellationToken;
2527
2528 use super::server;
2529 use super::*;
2530 use crate::channel;
2531 use crate::channel::ChannelRx;
2532 use crate::channel::ChannelTx;
2533 use crate::channel::net::framed::FrameReader;
2534 use crate::channel::net::framed::FrameWrite;
2535 use crate::channel::net::server::AcceptorLink;
2536 use crate::config;
2537 use crate::metrics;
2538 use crate::sync::mvar::MVar;
2539
2540 fn logs_assert_unscoped(f: impl Fn(&[&str]) -> Result<(), String>) {
2544 let buf = tracing_test::internal::global_buf().lock().unwrap();
2545 let logs_str = std::str::from_utf8(&buf).expect("Logs contain invalid UTF8");
2546 let lines: Vec<&str> = logs_str.lines().collect();
2547 match f(&lines) {
2548 Ok(()) => {}
2549 Err(msg) => panic!("{}", msg),
2550 }
2551 }
2552
2553 #[cfg(target_os = "linux")] #[tracing_test::traced_test]
2555 #[tokio::test]
2556 async fn test_unix_basic() -> Result<()> {
2557 let timestamp = std::time::SystemTime::now()
2558 .duration_since(UNIX_EPOCH)
2559 .unwrap()
2560 .as_nanos();
2561 let unique_address = format!("test_unix_basic_{}", timestamp);
2562
2563 let (addr, mut rx) = server::serve::<u64>(
2564 ChannelAddr::Unix(unix::SocketAddr::from_abstract_name(&unique_address)?),
2565 None,
2566 )
2567 .unwrap();
2568
2569 {
2576 let tx: ChannelTx<u64> = channel::dial::<u64>(addr.clone()).unwrap();
2577 tx.post(123);
2578 assert_eq!(rx.recv().await.unwrap(), 123);
2579 }
2580
2581 {
2582 let tx = channel::dial::<u64>(addr.clone()).unwrap();
2583 tx.post(321);
2584 tx.post(111);
2585 tx.post(444);
2586
2587 assert_eq!(rx.recv().await.unwrap(), 321);
2588 assert_eq!(rx.recv().await.unwrap(), 111);
2589 assert_eq!(rx.recv().await.unwrap(), 444);
2590 }
2591
2592 {
2593 let tx = channel::dial::<u64>(addr).unwrap();
2594 drop(rx);
2595
2596 assert_matches!(
2597 tx.try_post(123).await,
2598 Err(SendError {
2599 error: ChannelError::Closed,
2600 message: 123,
2601 ..
2602 })
2603 );
2604 }
2605
2606 Ok(())
2607 }
2608
2609 #[cfg(target_os = "linux")] #[tracing_test::traced_test]
2611 #[tokio::test]
2612 async fn test_unix_basic_client_before_server() -> Result<()> {
2613 let timestamp = std::time::SystemTime::now()
2615 .duration_since(UNIX_EPOCH)
2616 .unwrap()
2617 .as_nanos();
2618 let socket_addr =
2619 unix::SocketAddr::from_abstract_name(&format!("test_unix_basic_{}", timestamp))
2620 .unwrap();
2621
2622 let addr = ChannelAddr::Unix(socket_addr.clone());
2624 let tx = crate::channel::dial::<u64>(addr.clone()).unwrap();
2625 tx.post(123);
2626
2627 let (_, mut rx) = server::serve::<u64>(ChannelAddr::Unix(socket_addr), None).unwrap();
2628 assert_eq!(rx.recv().await.unwrap(), 123);
2629
2630 tx.post(321);
2631 tx.post(111);
2632 tx.post(444);
2633
2634 assert_eq!(rx.recv().await.unwrap(), 321);
2635 assert_eq!(rx.recv().await.unwrap(), 111);
2636 assert_eq!(rx.recv().await.unwrap(), 444);
2637
2638 Ok(())
2639 }
2640
2641 #[tracing_test::traced_test]
2642 #[async_timed_test(timeout_secs = 60)]
2643 #[cfg_attr(not(fbcode_build), ignore)]
2645 async fn test_tcp_basic() {
2646 let (addr, mut rx) =
2647 server::serve::<u64>(ChannelAddr::Tcp("[::1]:0".parse().unwrap()), None).unwrap();
2648 {
2649 let tx = channel::dial::<u64>(addr.clone()).unwrap();
2650 tx.post(123);
2651 assert_eq!(rx.recv().await.unwrap(), 123);
2652 }
2653
2654 {
2655 let tx = channel::dial::<u64>(addr.clone()).unwrap();
2656 tx.post(321);
2657 tx.post(111);
2658 tx.post(444);
2659
2660 assert_eq!(rx.recv().await.unwrap(), 321);
2661 assert_eq!(rx.recv().await.unwrap(), 111);
2662 assert_eq!(rx.recv().await.unwrap(), 444);
2663 }
2664
2665 {
2666 let tx = channel::dial::<u64>(addr).unwrap();
2667 drop(rx);
2668
2669 assert_matches!(
2670 tx.try_post(123).await,
2671 Err(SendError {
2672 error: ChannelError::Closed,
2673 message: 123,
2674 ..
2675 })
2676 );
2677 }
2678 }
2679
2680 #[async_timed_test(timeout_secs = 20)]
2681 #[cfg_attr(not(fbcode_build), ignore)]
2682 async fn test_tcp_unreachable_peer_surfaces_closed() {
2683 let config = hyperactor_config::global::lock();
2688 let _g1 = config.override_key(config::CHANNEL_RECONNECT_TIMEOUT, Duration::from_secs(1));
2692 let _g2 = config.override_key(config::MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(3));
2693
2694 let (addr, rx) =
2697 server::serve::<u64>(ChannelAddr::Tcp("[::1]:0".parse().unwrap()), None).unwrap();
2698 drop(rx);
2699
2700 let tx = channel::dial::<u64>(addr.clone()).unwrap();
2701 tx.post(123); let mut status = tx.status().clone();
2704 while !status.borrow_and_update().is_closed() {
2705 status.changed().await.unwrap();
2706 }
2707 }
2708
2709 #[async_timed_test(timeout_secs = 30)]
2716 #[cfg_attr(not(fbcode_build), ignore)]
2718 async fn test_tcp_message_size() {
2719 let default_size_in_bytes = 100 * 1024 * 1024;
2720 let config = hyperactor_config::global::lock();
2722 let _guard1 = config.override_key(config::MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(1));
2723 let _guard2 = config.override_key(config::CODEC_MAX_FRAME_LENGTH, default_size_in_bytes);
2724
2725 let (addr, mut rx) =
2726 server::serve::<String>(ChannelAddr::Tcp("[::1]:0".parse().unwrap()), None).unwrap();
2727
2728 let tx = channel::dial::<String>(addr.clone()).unwrap();
2729 {
2731 let message = "a".repeat(default_size_in_bytes - 1024);
2733 tx.post(message.clone());
2734 assert_eq!(rx.recv().await.unwrap(), message);
2735 }
2736 {
2738 let message = "a".repeat(default_size_in_bytes + 1024);
2739 let returned = tx.try_post(message.clone()).await.unwrap_err();
2740 assert_eq!(message, returned.message);
2741 }
2742 }
2743
2744 #[async_timed_test(timeout_secs = 30)]
2745 #[cfg_attr(not(fbcode_build), ignore)]
2747 async fn test_ack_flush() {
2748 let config = hyperactor_config::global::lock();
2749 let _guard_message_ack =
2752 config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 100000000);
2753 let _guard_delivery_timeout =
2754 config.override_key(config::MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(5));
2755
2756 let (addr, mut net_rx) =
2757 server::serve::<u64>(ChannelAddr::Tcp("[::1]:0".parse().unwrap()), None).unwrap();
2758 let net_tx = channel::dial::<u64>(addr.clone()).unwrap();
2759 let receipt = net_tx.try_post(1);
2760 assert_eq!(net_rx.recv().await.unwrap(), 1);
2761 drop(net_rx);
2762 assert!(receipt.await.is_ok());
2763 }
2764
2765 #[async_timed_test(timeout_secs = 60)]
2766 #[cfg_attr(not(fbcode_build), ignore)]
2768 async fn test_meta_tls_basic() {
2769 hyperactor_telemetry::initialize_logging_for_test();
2770
2771 let addr = ChannelAddr::any(ChannelTransport::MetaTls(TlsMode::IpV6));
2772 let meta_addr = match addr {
2773 ChannelAddr::MetaTls(meta_addr) => meta_addr,
2774 _ => panic!("expected MetaTls address"),
2775 };
2776 let (local_addr, mut rx) =
2777 server::serve::<u64>(ChannelAddr::MetaTls(meta_addr), None).unwrap();
2778 {
2779 let tx = channel::dial::<u64>(local_addr.clone()).unwrap();
2780 tx.post(123);
2781 }
2782 assert_eq!(rx.recv().await.unwrap(), 123);
2783
2784 {
2785 let tx = channel::dial::<u64>(local_addr.clone()).unwrap();
2786 tx.post(321);
2787 tx.post(111);
2788 tx.post(444);
2789 assert_eq!(rx.recv().await.unwrap(), 321);
2790 assert_eq!(rx.recv().await.unwrap(), 111);
2791 assert_eq!(rx.recv().await.unwrap(), 444);
2792 }
2793
2794 {
2795 let tx = channel::dial::<u64>(local_addr).unwrap();
2796 drop(rx);
2797
2798 assert_matches!(
2799 tx.try_post(123).await,
2800 Err(SendError {
2801 error: ChannelError::Closed,
2802 message: 123,
2803 ..
2804 })
2805 );
2806 }
2807 }
2808
2809 #[derive(Clone, Debug, Default)]
2810 struct NetworkFlakiness {
2811 disconnect_params: Option<(f64, u64, Duration)>,
2819 latency_range: Option<(Duration, Duration)>,
2822 }
2823
2824 impl NetworkFlakiness {
2825 async fn should_disconnect(
2827 &self,
2828 rng: &mut impl rand::Rng,
2829 disconnected_count: u64,
2830 prev_disconnected_at: &RwLock<Instant>,
2831 ) -> bool {
2832 let Some((prob, max_disconnects, duration)) = &self.disconnect_params else {
2833 return false;
2834 };
2835
2836 let disconnected_at = prev_disconnected_at.read().unwrap();
2837 if disconnected_at.elapsed() > *duration && disconnected_count < *max_disconnects {
2838 rng.random_bool(*prob)
2839 } else {
2840 false
2841 }
2842 }
2843 }
2844
2845 struct MockLink<M> {
2846 buffer_size: usize,
2847 session_id: SessionId,
2848 receiver_storage: Arc<MVar<DuplexStream>>,
2849 fail_connects: Arc<AtomicBool>,
2851 disconnect_signal: watch::Sender<()>,
2854 network_flakiness: NetworkFlakiness,
2855 disconnected_count: Arc<AtomicU64>,
2856 prev_disconnected_at: Arc<RwLock<Instant>>,
2857 debug_log_sampling_rate: Option<u64>,
2860 _message_type: PhantomData<M>,
2861 }
2862
2863 impl<M> fmt::Debug for MockLink<M> {
2864 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2865 f.debug_struct("MockLink")
2866 .field("buffer_size", &self.buffer_size)
2867 .field("receiver_storage", &"<MVar<DuplexStream>>")
2868 .field("fail_connects", &self.fail_connects)
2869 .field("disconnect_signal", &"<watch::Sender>")
2870 .field("network_flakiness", &self.network_flakiness)
2871 .field("disconnected_count", &self.disconnected_count)
2872 .field("prev_disconnected_at", &"<RwLock<Instant>>")
2873 .field("debug_log_sampling_rate", &self.debug_log_sampling_rate)
2874 .finish()
2875 }
2876 }
2877
2878 impl<M: RemoteMessage> MockLink<M> {
2879 fn new() -> Self {
2880 let (sender, _) = watch::channel(());
2881 Self {
2882 buffer_size: 64,
2883 session_id: SessionId::random(),
2884 receiver_storage: Arc::new(MVar::empty()),
2885 fail_connects: Arc::new(AtomicBool::new(false)),
2886 disconnect_signal: sender,
2887 network_flakiness: NetworkFlakiness::default(),
2888 disconnected_count: Arc::new(AtomicU64::new(0)),
2889 prev_disconnected_at: Arc::new(RwLock::new(tokio::time::Instant::now())),
2890 debug_log_sampling_rate: None,
2891 _message_type: PhantomData,
2892 }
2893 }
2894
2895 fn fail_connects() -> Self {
2898 Self {
2899 fail_connects: Arc::new(AtomicBool::new(true)),
2900 ..Self::new()
2901 }
2902 }
2903
2904 fn with_network_flakiness(network_flakiness: NetworkFlakiness) -> Self {
2905 if let Some((min, max)) = network_flakiness.latency_range {
2906 assert!(min < max);
2907 }
2908
2909 Self {
2910 network_flakiness,
2911 ..Self::new()
2912 }
2913 }
2914
2915 fn receiver_storage(&self) -> Arc<MVar<DuplexStream>> {
2916 self.receiver_storage.clone()
2917 }
2918
2919 fn disconnected_count(&self) -> Arc<AtomicU64> {
2920 self.disconnected_count.clone()
2921 }
2922
2923 fn disconnect_signal(&self) -> &watch::Sender<()> {
2924 &self.disconnect_signal
2925 }
2926
2927 fn fail_connects_switch(&self) -> Arc<AtomicBool> {
2928 self.fail_connects.clone()
2929 }
2930
2931 fn set_buffer_size(&mut self, size: usize) {
2932 self.buffer_size = size;
2933 }
2934
2935 fn set_sampling_rate(&mut self, sampling_rate: u64) {
2936 self.debug_log_sampling_rate = Some(sampling_rate);
2937 }
2938 }
2939
2940 #[async_trait]
2941 impl<M: RemoteMessage> Link for MockLink<M> {
2942 type Stream = DuplexStream;
2943
2944 fn dest(&self) -> ChannelAddr {
2945 ChannelAddr::Local(u64::MAX)
2946 }
2947
2948 fn link_id(&self) -> SessionId {
2949 self.session_id
2950 }
2951
2952 async fn next(&mut self) -> Result<Self::Stream, ClientError> {
2953 let session_id = self.session_id;
2954 tracing::debug!("MockLink starts to connect.");
2955 if self.fail_connects.load(Ordering::Acquire) {
2956 return Err(ClientError::Connect(
2957 self.dest(),
2958 std::io::Error::other("intentional error"),
2959 "expected failure injected by the mock".to_string(),
2960 ));
2961 }
2962
2963 async fn relay_message<M: RemoteMessage>(
2969 mut disconnect_signal: watch::Receiver<()>,
2970 network_flakiness: NetworkFlakiness,
2971 disconnected_count: Arc<AtomicU64>,
2972 prev_disconnected_at: Arc<RwLock<Instant>>,
2973 mut reader: FrameReader<ReadHalf<DuplexStream>>,
2974 mut writer: WriteHalf<DuplexStream>,
2975 task_coordination_token: CancellationToken,
2978 debug_log_sampling_rate: Option<u64>,
2979 is_from_client: bool,
2982 ) {
2983 async fn wait_for_latency_elapse(
2987 queue: &VecDeque<(Bytes, Instant)>,
2988 network_flakiness: &NetworkFlakiness,
2989 rng: &mut impl rand::Rng,
2990 ) {
2991 if let Some((min, max)) = network_flakiness.latency_range {
2992 let diff = max.abs_diff(min);
2993 let factor = rng.random_range(0.0..=1.0);
2994 let latency = min + diff.mul_f64(factor);
2995 tokio::time::sleep_until(queue.front().unwrap().1 + latency).await;
2996 }
2997 }
2998
2999 let mut rng = rand::rngs::SmallRng::try_from_rng(&mut SysRng).unwrap();
3000 let mut queue: VecDeque<(Bytes, Instant)> = VecDeque::new();
3001 let mut send_count = 0u64;
3002
3003 loop {
3004 tokio::select! {
3005 read_res = reader.next() => {
3006 match read_res {
3007 Ok(Some((_, data))) => {
3008 queue.push_back((data, tokio::time::Instant::now()));
3009 }
3010 Ok(None) | Err(_) => {
3011 tracing::debug!("The upstream is closed or dropped. MockLink disconnects");
3012 break;
3013 }
3014 }
3015 }
3016 _ = wait_for_latency_elapse(&queue, &network_flakiness, &mut rng), if !queue.is_empty() => {
3017 let count = disconnected_count.load(Ordering::Relaxed);
3018 if network_flakiness.should_disconnect(&mut rng, count, &prev_disconnected_at).await {
3019 tracing::debug!("MockLink disconnects");
3020 disconnected_count.fetch_add(1, Ordering::Relaxed);
3021
3022 metrics::CHANNEL_RECONNECTIONS.add(
3023 1,
3024 hyperactor_telemetry::kv_pairs!(
3025 "transport" => "mock",
3026 "reason" => "network_flakiness",
3027 ),
3028 );
3029
3030 let mut w = prev_disconnected_at.write().unwrap();
3031 *w = tokio::time::Instant::now();
3032 break;
3033 }
3034 let data = queue.pop_front().unwrap().0;
3035 let is_sampled = debug_log_sampling_rate.is_some_and(|sample_rate| send_count % sample_rate == 1);
3036 if is_sampled {
3037 if is_from_client {
3038 if let Ok((Frame::Message(_seq, _msg), _)) = bincode::serde::decode_from_slice::<Frame<M>, _>(&data, bincode::config::legacy()) {
3039 tracing::debug!("MockLink relays a msg from client. msg type: {}", std::any::type_name::<M>());
3040 }
3041 } else {
3042 let result = deserialize_response(data.clone());
3043 if let Ok(NetRxResponse::Ack(seq)) = result {
3044 tracing::debug!("MockLink relays an ack from server. seq: {}", seq);
3045 }
3046 }
3047 }
3048 let mut fw = FrameWrite::new(writer, data, hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH), 0).unwrap();
3049 if fw.send().await.is_err() {
3050 break;
3051 }
3052 writer = fw.complete();
3053 send_count += 1;
3054 }
3055 _ = task_coordination_token.cancelled() => break,
3056
3057 changed = disconnect_signal.changed() => {
3058 tracing::debug!("MockLink disconnects per disconnect_signal {:?}", changed);
3059 break;
3060 }
3061 }
3062 }
3063
3064 task_coordination_token.cancel();
3065 }
3066
3067 let (server, mut server_relay) = tokio::io::duplex(self.buffer_size);
3068 let (client, client_relay) = tokio::io::duplex(self.buffer_size);
3069
3070 write_link_init(&mut server_relay, session_id, 0, ProtocolKind::Simplex)
3074 .await
3075 .map_err(|err| ClientError::Io(self.dest(), err))?;
3076
3077 let (server_r, server_writer) = tokio::io::split(server_relay);
3078 let (client_r, client_writer) = tokio::io::split(client_relay);
3079
3080 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
3081 let server_reader = FrameReader::new(server_r, max_len);
3082 let client_reader = FrameReader::new(client_r, max_len);
3083
3084 let task_coordination_token = CancellationToken::new();
3085 let _server_relay_task_handle = tokio::spawn(relay_message::<M>(
3086 self.disconnect_signal.subscribe(),
3087 self.network_flakiness.clone(),
3088 self.disconnected_count.clone(),
3089 self.prev_disconnected_at.clone(),
3090 server_reader,
3091 client_writer,
3092 task_coordination_token.clone(),
3093 self.debug_log_sampling_rate,
3094 false,
3095 ));
3096 let _client_relay_task_handle = tokio::spawn(relay_message::<M>(
3097 self.disconnect_signal.subscribe(),
3098 self.network_flakiness.clone(),
3099 self.disconnected_count.clone(),
3100 self.prev_disconnected_at.clone(),
3101 client_reader,
3102 server_writer,
3103 task_coordination_token,
3104 self.debug_log_sampling_rate,
3105 true,
3106 ));
3107
3108 self.receiver_storage.put(server).await;
3109 Ok(client)
3110 }
3111 }
3112
3113 struct MockLinkListener {
3114 receiver_storage: Arc<MVar<DuplexStream>>,
3115 channel_addr: ChannelAddr,
3116 }
3117
3118 impl MockLinkListener {
3119 fn new(receiver_storage: Arc<MVar<DuplexStream>>, channel_addr: ChannelAddr) -> Self {
3120 Self {
3121 receiver_storage,
3122 channel_addr,
3123 }
3124 }
3125 }
3126
3127 impl fmt::Debug for MockLinkListener {
3128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3129 f.debug_struct("MockLinkListener")
3130 .field("channel_addr", &self.channel_addr)
3131 .finish()
3132 }
3133 }
3134
3135 #[async_trait]
3136 impl super::Listener for MockLinkListener {
3137 type Stream = DuplexStream;
3138
3139 async fn accept(&mut self) -> Result<(Self::Stream, ChannelAddr), ServerError> {
3140 let stream = self.receiver_storage.take().await;
3141 Ok((stream, self.channel_addr.clone()))
3142 }
3143 }
3144
3145 fn serve_acceptor_test<M: RemoteMessage>(
3149 session_id: SessionId,
3150 ) -> (
3151 JoinHandle<()>,
3152 mpsc::UnboundedSender<DuplexStream>,
3153 mpsc::Receiver<M>,
3154 CancellationToken,
3155 ) {
3156 let (acceptor_tx, acceptor_rx) = mpsc::unbounded_channel::<DuplexStream>();
3157 let cancel_token = CancellationToken::new();
3158 let link = AcceptorLink {
3159 dest: ChannelAddr::Local(u64::MAX),
3160 session_id,
3161 stream: acceptor_rx,
3162 cancel: cancel_token.clone(),
3163 };
3164 let (tx, rx) = mpsc::channel::<M>(1024);
3165 let ct = cancel_token.clone();
3166 let handle = tokio::spawn(async move {
3167 let mut session = Session::new(link);
3168 let mut next = session::Next { seq: 0, ack: 0 };
3169
3170 loop {
3171 let connected = match session.connect().await {
3172 Ok(s) => s,
3173 Err(_) => break,
3174 };
3175
3176 let result = {
3177 let stream = connected.stream(INITIATOR_TO_ACCEPTOR);
3178 tokio::select! {
3179 r = session::recv_connected::<M, _, _>(&stream, &tx, &mut next) => r,
3180 _ = ct.cancelled() => Err(session::RecvLoopError::Cancelled),
3181 }
3182 };
3183
3184 if next.ack < next.seq {
3186 let ack = serialize_response(NetRxResponse::Ack(next.seq - 1)).unwrap();
3187 let stream = connected.stream(INITIATOR_TO_ACCEPTOR);
3188 let mut completion = stream.write(ack);
3189 match completion.drive().await {
3190 Ok(()) => {
3191 next.ack = next.seq;
3192 }
3193 Err(e) => {
3194 tracing::debug!(
3195 error = %e,
3196 "failed to flush acks during cleanup"
3197 );
3198 }
3199 }
3200 }
3201
3202 let terminal_response = match &result {
3204 Err(session::RecvLoopError::SequenceError(reason)) => {
3205 Some(NetRxResponse::Reject(reason.clone()))
3206 }
3207 Err(session::RecvLoopError::Cancelled) => Some(NetRxResponse::Closed),
3208 _ => None,
3209 };
3210 if let Some(rsp) = terminal_response {
3211 let data = serialize_response(rsp).unwrap();
3212 let stream = connected.stream(INITIATOR_TO_ACCEPTOR);
3213 let mut completion = stream.write(data);
3214 let _ = completion.drive().await;
3215 }
3216
3217 let recoverable = matches!(&result, Ok(()) | Err(session::RecvLoopError::Io(_)));
3218 session = connected.release();
3219 if recoverable {
3220 continue;
3221 }
3222 break;
3223 }
3224 });
3225 (handle, acceptor_tx, rx, cancel_token)
3226 }
3227
3228 async fn write_stream<M, W>(
3229 mut writer: W,
3230 _session_id: u64,
3231 messages: &[(u64, M)],
3232 _init: bool,
3233 ) -> W
3234 where
3235 M: RemoteMessage + PartialEq + Clone,
3236 W: AsyncWrite + Unpin,
3237 {
3238 for (seq, message) in messages {
3239 let message =
3240 serde_multipart::serialize_bincode(&Frame::<M>::Message(*seq, message.clone()))
3241 .unwrap();
3242 let mut fw = FrameWrite::new(
3243 writer,
3244 message.framed(),
3245 hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH),
3246 0,
3247 )
3248 .map_err(|(_w, e)| e)
3249 .unwrap();
3250 fw.send().await.unwrap();
3251 writer = fw.complete();
3252 }
3253
3254 writer
3255 }
3256
3257 #[async_timed_test(timeout_secs = 60)]
3258 async fn test_persistent_server_session() {
3259 let config = hyperactor_config::global::lock();
3260 let _guard = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1);
3261
3262 async fn verify_ack(reader: &mut FrameReader<ReadHalf<DuplexStream>>, expected_last: u64) {
3263 let mut last_acked: i128 = -1;
3264 loop {
3265 let (_, bytes) = reader.next().await.unwrap().unwrap();
3266 let acked = deserialize_response(bytes).unwrap().into_ack().unwrap();
3267 assert!(
3268 acked as i128 > last_acked,
3269 "acks should be delivered in ascending order"
3270 );
3271 last_acked = acked as i128;
3272 assert!(acked <= expected_last);
3273 if acked == expected_last {
3274 break;
3275 }
3276 }
3277 }
3278
3279 let session_id = SessionId(123);
3280 let (_handle, acceptor_tx, mut rx, cancel_token) = serve_acceptor_test::<u64>(session_id);
3281
3282 {
3284 let (sender, receiver) = tokio::io::duplex(5000);
3285 acceptor_tx.send(receiver).unwrap();
3286
3287 let (r, writer) = tokio::io::split(sender);
3288 let mut reader = FrameReader::new(
3289 r,
3290 hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH),
3291 );
3292
3293 let _writer = write_stream(
3294 writer,
3295 123,
3296 &[
3297 (0u64, 100u64),
3298 (1u64, 101u64),
3299 (2u64, 102u64),
3300 (3u64, 103u64),
3301 ],
3302 true,
3303 )
3304 .await;
3305
3306 assert_eq!(rx.recv().await, Some(100));
3307 assert_eq!(rx.recv().await, Some(101));
3308 assert_eq!(rx.recv().await, Some(102));
3309 assert_eq!(rx.recv().await, Some(103));
3310
3311 verify_ack(&mut reader, 3).await;
3312 }
3314
3315 {
3317 let (sender2, receiver2) = tokio::io::duplex(5000);
3318 acceptor_tx.send(receiver2).unwrap();
3319
3320 let (r2, writer2) = tokio::io::split(sender2);
3321 let mut reader2 = FrameReader::new(
3322 r2,
3323 hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH),
3324 );
3325
3326 let _ = write_stream(
3327 writer2,
3328 123,
3329 &[
3330 (2u64, 102u64),
3331 (3u64, 103u64),
3332 (4u64, 104u64),
3333 (5u64, 105u64),
3334 ],
3335 true,
3336 )
3337 .await;
3338
3339 assert_eq!(rx.recv().await, Some(104));
3341 assert_eq!(rx.recv().await, Some(105));
3342
3343 verify_ack(&mut reader2, 5).await;
3344
3345 cancel_token.cancel();
3346 }
3347 }
3348
3349 #[async_timed_test(timeout_secs = 60)]
3350 async fn test_ack_from_server_session() {
3351 let config = hyperactor_config::global::lock();
3352 let _guard = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1);
3353 let session_id = SessionId(123);
3354 let (_handle, acceptor_tx, mut rx, cancel_token) = serve_acceptor_test::<u64>(session_id);
3355
3356 let (sender, receiver) = tokio::io::duplex(5000);
3357 acceptor_tx.send(receiver).unwrap();
3358 let (r, mut writer) = tokio::io::split(sender);
3359 let mut reader = FrameReader::new(
3360 r,
3361 hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH),
3362 );
3363
3364 for i in 0u64..100u64 {
3365 writer = write_stream(writer, 123, &[(i, 100u64 + i)], i == 0u64).await;
3366 assert_eq!(rx.recv().await, Some(100u64 + i));
3367 let (_, bytes) = reader.next().await.unwrap().unwrap();
3368 let acked = deserialize_response(bytes).unwrap().into_ack().unwrap();
3369 assert_eq!(acked, i);
3370 }
3371
3372 tokio::time::sleep(Duration::from_secs(5)).await;
3374
3375 cancel_token.cancel();
3376
3377 let (_, bytes) = reader.next().await.unwrap().unwrap();
3379 assert!(deserialize_response(bytes).unwrap().is_closed());
3380 }
3381
3382 #[tracing_test::traced_test]
3383 async fn verify_tx_closed(tx_status: &mut watch::Receiver<TxStatus>, expected_log: &str) {
3384 match tokio::time::timeout(Duration::from_secs(5), tx_status.changed()).await {
3385 Ok(Ok(())) => {
3386 let current_status = tx_status.borrow().clone();
3387 assert!(current_status.is_closed());
3388 logs_assert_unscoped(|logs| {
3389 if logs.iter().any(|log| log.contains(expected_log)) {
3390 Ok(())
3391 } else {
3392 Err("expected log not found".to_string())
3393 }
3394 });
3395 }
3396 Ok(Err(_)) => panic!("watch::Receiver::changed() failed because sender is dropped."),
3397 Err(_) => panic!("timeout before tx_status changed"),
3398 }
3399 }
3400
3401 #[tracing_test::traced_test]
3402 #[tokio::test]
3403 #[cfg_attr(not(fbcode_build), ignore)]
3405 async fn test_tcp_tx_delivery_timeout() {
3406 let link = MockLink::<u64>::fail_connects();
3408 let tx = spawn::<u64>(link);
3409 let config = hyperactor_config::global::lock();
3411 let _guard = config.override_key(config::MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(1));
3412 let mut tx_receiver = tx.status().clone();
3413 let _receipt = tx.try_post(123);
3414 verify_tx_closed(&mut tx_receiver, "failed to deliver message within timeout").await;
3415 }
3416
3417 async fn take_receiver(
3418 receiver_storage: &MVar<DuplexStream>,
3419 ) -> (FrameReader<ReadHalf<DuplexStream>>, WriteHalf<DuplexStream>) {
3420 let mut receiver = receiver_storage.take().await;
3421 let _link_init = read_link_init(&mut receiver).await.expect("read LinkInit");
3423 let (r, writer) = tokio::io::split(receiver);
3424 let reader = FrameReader::new(
3425 r,
3426 hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH),
3427 );
3428 (reader, writer)
3429 }
3430
3431 async fn verify_message<M: RemoteMessage + PartialEq + std::fmt::Debug>(
3432 reader: &mut FrameReader<ReadHalf<DuplexStream>>,
3433 expect: (u64, M),
3434 loc: u32,
3435 ) {
3436 let expected = Frame::Message(expect.0, expect.1);
3437 let (_, bytes) = reader.next().await.unwrap().expect("unexpected EOF");
3438 let message = serde_multipart::Message::from_framed(bytes).unwrap();
3439 let frame: Frame<M> = serde_multipart::deserialize_bincode(message).unwrap();
3440
3441 assert_eq!(frame, expected, "from ln={loc}");
3442 }
3443
3444 async fn verify_stream<M: RemoteMessage + PartialEq + std::fmt::Debug + Clone>(
3445 reader: &mut FrameReader<ReadHalf<DuplexStream>>,
3446 expects: &[(u64, M)],
3447 _expect_session_id: Option<u64>,
3448 loc: u32,
3449 ) {
3450 for expect in expects {
3451 verify_message(reader, expect.clone(), loc).await;
3452 }
3453 }
3454
3455 async fn net_tx_send(tx: &ChannelTx<u64>, msgs: &[u64]) {
3456 for msg in msgs {
3457 tx.post(*msg);
3458 }
3459 }
3460
3461 #[async_timed_test(timeout_secs = 30)]
3463 async fn test_ack_in_net_tx_basic() {
3464 let link = MockLink::<u64>::new();
3465 let receiver_storage = link.receiver_storage();
3466 let tx = spawn::<u64>(link);
3467
3468 net_tx_send(&tx, &[100, 101, 102, 103, 104]).await;
3470 {
3471 let (mut reader, mut writer) = take_receiver(&receiver_storage).await;
3472 verify_stream(
3473 &mut reader,
3474 &[
3475 (0u64, 100u64),
3476 (1u64, 101u64),
3477 (2u64, 102u64),
3478 (3u64, 103u64),
3479 (4u64, 104u64),
3480 ],
3481 None,
3482 line!(),
3483 )
3484 .await;
3485
3486 for i in 0u64..5u64 {
3487 writer = FrameWrite::write_frame(
3488 writer,
3489 serialize_response(NetRxResponse::Ack(i)).unwrap(),
3490 1024,
3491 0,
3492 )
3493 .await
3494 .map_err(|(_, e)| e)
3495 .unwrap();
3496 }
3497 tokio::time::sleep(Duration::from_secs(3)).await;
3499 drop(reader);
3501 drop(writer);
3502 };
3503
3504 net_tx_send(&tx, &[105u64]).await;
3506 {
3507 let (mut reader, _writer) = take_receiver(&receiver_storage).await;
3508 verify_stream(&mut reader, &[(5u64, 105u64)], None, line!()).await;
3509 };
3511 }
3512
3513 #[async_timed_test(timeout_secs = 60)]
3515 async fn test_persistent_net_tx() {
3516 let link = MockLink::<u64>::new();
3517 let receiver_storage = link.receiver_storage();
3518
3519 let tx = spawn::<u64>(link);
3520
3521 net_tx_send(&tx, &[100, 101, 102, 103, 104]).await;
3523
3524 let n = 3;
3528
3529 for i in 0..n {
3532 {
3533 let (mut reader, mut writer) = take_receiver(&receiver_storage).await;
3534 verify_stream(
3535 &mut reader,
3536 &[
3537 (0u64, 100u64),
3538 (1u64, 101u64),
3539 (2u64, 102u64),
3540 (3u64, 103u64),
3541 (4u64, 104u64),
3542 ],
3543 None,
3544 line!(),
3545 )
3546 .await;
3547
3548 if i == n - 1 {
3551 writer = FrameWrite::write_frame(
3552 writer,
3553 serialize_response(NetRxResponse::Ack(1)).unwrap(),
3554 1024,
3555 0,
3556 )
3557 .await
3558 .map_err(|(_, e)| e)
3559 .unwrap();
3560 tokio::time::sleep(Duration::from_secs(3)).await;
3562 }
3563 drop(reader);
3565 drop(writer);
3566 };
3567 }
3568
3569 for _ in 0..n {
3571 {
3572 let (mut reader, mut _writer) = take_receiver(&receiver_storage).await;
3573 verify_stream(
3574 &mut reader,
3575 &[(2u64, 102u64), (3u64, 103u64), (4u64, 104u64)],
3576 None,
3577 line!(),
3578 )
3579 .await;
3580 };
3582 }
3583
3584 net_tx_send(&tx, &[105u64, 106u64, 107u64, 108u64, 109u64]).await;
3586 for i in 0..n {
3589 {
3590 let (mut reader, mut writer) = take_receiver(&receiver_storage).await;
3591 verify_stream(
3592 &mut reader,
3593 &[
3594 (2u64, 102u64),
3596 (3u64, 103u64),
3597 (4u64, 104u64),
3598 (5u64, 105u64),
3600 (6u64, 106u64),
3601 (7u64, 107u64),
3602 (8u64, 108u64),
3603 (9u64, 109u64),
3604 ],
3605 None,
3606 line!(),
3607 )
3608 .await;
3609
3610 if i == n - 1 {
3613 writer = FrameWrite::write_frame(
3616 writer,
3617 serialize_response(NetRxResponse::Ack(1)).unwrap(),
3618 1024,
3619 0,
3620 )
3621 .await
3622 .map_err(|(_, e)| e)
3623 .unwrap();
3624 writer = FrameWrite::write_frame(
3625 writer,
3626 serialize_response(NetRxResponse::Ack(2)).unwrap(),
3627 1024,
3628 0,
3629 )
3630 .await
3631 .map_err(|(_, e)| e)
3632 .unwrap();
3633 writer = FrameWrite::write_frame(
3634 writer,
3635 serialize_response(NetRxResponse::Ack(3)).unwrap(),
3636 1024,
3637 0,
3638 )
3639 .await
3640 .map_err(|(_, e)| e)
3641 .unwrap();
3642 tokio::time::sleep(Duration::from_secs(3)).await;
3644 }
3645 drop(reader);
3647 drop(writer);
3648 };
3649 }
3650
3651 for i in 0..n {
3652 {
3653 let (mut reader, mut writer) = take_receiver(&receiver_storage).await;
3654 verify_stream(
3655 &mut reader,
3656 &[
3657 (4u64, 104),
3659 (5u64, 105u64),
3661 (6u64, 106u64),
3662 (7u64, 107u64),
3663 (8u64, 108u64),
3664 (9u64, 109u64),
3665 ],
3666 None,
3667 line!(),
3668 )
3669 .await;
3670
3671 if i == n - 1 {
3673 writer = FrameWrite::write_frame(
3674 writer,
3675 serialize_response(NetRxResponse::Ack(7)).unwrap(),
3676 1024,
3677 0,
3678 )
3679 .await
3680 .map_err(|(_, e)| e)
3681 .unwrap();
3682 tokio::time::sleep(Duration::from_secs(3)).await;
3684 }
3685 drop(reader);
3687 drop(writer);
3688 };
3689 }
3690
3691 for _ in 0..n {
3692 {
3693 let (mut reader, writer) = take_receiver(&receiver_storage).await;
3694 verify_stream(
3695 &mut reader,
3696 &[
3697 (8u64, 108u64),
3699 (9u64, 109u64),
3700 ],
3701 None,
3702 line!(),
3703 )
3704 .await;
3705 drop(reader);
3707 drop(writer);
3708 };
3709 }
3710 }
3711
3712 #[async_timed_test(timeout_secs = 15)]
3713 async fn test_ack_before_redelivery_in_net_tx() {
3714 let link = MockLink::<u64>::new();
3715 let receiver_storage = link.receiver_storage();
3716 let net_tx = spawn::<u64>(link);
3717
3718 let receipt = net_tx.try_post(100);
3721 let (mut reader, mut writer) = take_receiver(&receiver_storage).await;
3722 verify_stream(&mut reader, &[(0u64, 100u64)], None, line!()).await;
3723 writer = FrameWrite::write_frame(
3725 writer,
3726 serialize_response(NetRxResponse::Ack(0)).unwrap(),
3727 1024,
3728 0,
3729 )
3730 .await
3731 .map_err(|(_, e)| e)
3732 .unwrap();
3733 assert!(receipt.await.is_ok());
3735
3736 let _ = FrameWrite::write_frame(
3741 writer,
3742 serialize_response(NetRxResponse::Ack(1)).unwrap(),
3743 1024,
3744 0,
3745 )
3746 .await
3747 .map_err(|(_, e)| e)
3748 .unwrap();
3749
3750 let receipt = net_tx.try_post(101);
3751 verify_message(&mut reader, (1u64, 101u64), line!()).await;
3753 assert!(receipt.await.is_ok());
3758 }
3759
3760 async fn verify_ack_exceeded_limit(disconnect_before_ack: bool) {
3761 let config = hyperactor_config::global::lock();
3763 let _guard = config.override_key(config::MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(2));
3764
3765 let link: MockLink<u64> = MockLink::<u64>::new();
3766 let disconnect_signal = link.disconnect_signal().clone();
3767 let fail_connect_switch = link.fail_connects_switch();
3768 let receiver_storage = link.receiver_storage();
3769 let tx = spawn::<u64>(link);
3770 let mut tx_status = tx.status().clone();
3771 tx.post(100);
3773 let (mut reader, writer) = take_receiver(&receiver_storage).await;
3774 verify_stream(&mut reader, &[(0u64, 100u64)], None, line!()).await;
3776 let _ = FrameWrite::write_frame(
3778 writer,
3779 serialize_response(NetRxResponse::Ack(0)).unwrap(),
3780 hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH),
3781 0,
3782 )
3783 .await
3784 .map_err(|(_, e)| e)
3785 .unwrap();
3786 tokio::time::sleep(Duration::from_secs(3)).await;
3787 assert!(!tx_status.has_changed().unwrap());
3789 assert_eq!(*tx_status.borrow(), TxStatus::Active);
3790
3791 tx.post(101);
3792 verify_message(&mut reader, (1u64, 101u64), line!()).await;
3794
3795 if disconnect_before_ack {
3796 fail_connect_switch.store(true, Ordering::Release);
3798 disconnect_signal.send(()).unwrap();
3800 }
3801
3802 let expected_log: &str = if disconnect_before_ack {
3804 "failed to receive ack within timeout 2s; link is currently broken"
3805 } else {
3806 "failed to receive ack within timeout 2s; link is currently connected"
3807 };
3808
3809 verify_tx_closed(&mut tx_status, expected_log).await;
3810 }
3811
3812 #[tracing_test::traced_test]
3813 #[async_timed_test(timeout_secs = 30)]
3814 #[cfg_attr(not(fbcode_build), ignore)]
3816 async fn test_ack_exceeded_limit_with_connected_link() {
3817 verify_ack_exceeded_limit(false).await;
3818 }
3819
3820 #[tracing_test::traced_test]
3821 #[async_timed_test(timeout_secs = 30)]
3822 #[cfg_attr(not(fbcode_build), ignore)]
3824 async fn test_ack_exceeded_limit_with_broken_link() {
3825 verify_ack_exceeded_limit(true).await;
3826 }
3827
3828 #[async_timed_test(timeout_secs = 60)]
3831 async fn test_network_flakiness_in_channel() {
3832 hyperactor_telemetry::initialize_logging_for_test();
3833
3834 let sampling_rate = 100;
3835 let mut link = MockLink::<u64>::with_network_flakiness(NetworkFlakiness {
3836 disconnect_params: Some((0.001, 15, Duration::from_millis(400))),
3837 latency_range: Some((Duration::from_millis(100), Duration::from_millis(200))),
3838 });
3839 link.set_sampling_rate(sampling_rate);
3840 link.set_buffer_size(1024000);
3842 let disconnected_count = link.disconnected_count();
3843 let receiver_storage = link.receiver_storage();
3844 let listener = MockLinkListener::new(receiver_storage.clone(), link.dest());
3845 let local_addr = listener.channel_addr.clone();
3846 let (_, mut nx): (ChannelAddr, ChannelRx<u64>) =
3847 super::server::serve_with_listener(listener, local_addr).unwrap();
3848 let tx = spawn::<u64>(link);
3849 let messages: Vec<_> = (0..10001).collect();
3850 let messages_clone = messages.clone();
3851 let send_task_handle = tokio::spawn(async move {
3854 for message in messages_clone {
3855 tokio::time::sleep(Duration::from_micros(rand::random::<u64>() % 100)).await;
3867 tx.post(message);
3868 }
3869 tracing::debug!("ChannelTx sent all messages");
3870 tx
3873 });
3874
3875 for message in &messages {
3876 if message % sampling_rate == 0 {
3877 tracing::debug!("ChannelRx received a message: {message}");
3878 }
3879 assert_eq!(nx.recv().await.unwrap(), *message);
3880 }
3881 tracing::debug!("ChannelRx received all messages");
3882
3883 let send_result = send_task_handle.await;
3884 assert!(send_result.is_ok());
3885
3886 tracing::debug!(
3887 "MockLink disconnected {} times.",
3888 disconnected_count.load(Ordering::SeqCst)
3889 );
3890 }
3893
3894 #[async_timed_test(timeout_secs = 60)]
3895 async fn test_ack_every_n_messages() {
3896 let config = hyperactor_config::global::lock();
3897 let _guard_message_ack = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 600);
3898 let _guard_time_interval =
3899 config.override_key(config::MESSAGE_ACK_TIME_INTERVAL, Duration::from_secs(1000));
3900 sparse_ack().await;
3901 }
3902
3903 #[async_timed_test(timeout_secs = 60)]
3904 async fn test_ack_every_time_interval() {
3905 let config = hyperactor_config::global::lock();
3906 let _guard_message_ack =
3907 config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 100000000);
3908 let _guard_time_interval = config.override_key(
3909 config::MESSAGE_ACK_TIME_INTERVAL,
3910 Duration::from_millis(500),
3911 );
3912 sparse_ack().await;
3913 }
3914
3915 async fn sparse_ack() {
3916 let mut link = MockLink::<u64>::new();
3917 link.set_buffer_size(1024000);
3919 let disconnected_count = link.disconnected_count();
3920 let receiver_storage = link.receiver_storage();
3921 let listener = MockLinkListener::new(receiver_storage.clone(), link.dest());
3922 let local_addr = listener.channel_addr.clone();
3923 let (_, mut nx): (ChannelAddr, ChannelRx<u64>) =
3924 super::server::serve_with_listener(listener, local_addr).unwrap();
3925 let tx = spawn::<u64>(link);
3926 let messages: Vec<_> = (0..20001).collect();
3927 let messages_clone = messages.clone();
3928 let send_task_handle = tokio::spawn(async move {
3931 for message in messages_clone {
3932 tokio::time::sleep(Duration::from_micros(rand::random::<u64>() % 100)).await;
3933 tx.post(message);
3934 }
3935 tokio::time::sleep(Duration::from_secs(5)).await;
3936 tracing::debug!("ChannelTx sent all messages");
3937 tx
3938 });
3939
3940 for message in &messages {
3941 assert_eq!(nx.recv().await.unwrap(), *message);
3942 }
3943 tracing::debug!("ChannelRx received all messages");
3944
3945 let send_result = send_task_handle.await;
3946 assert!(send_result.is_ok());
3947
3948 tracing::debug!(
3949 "MockLink disconnected {} times.",
3950 disconnected_count.load(Ordering::SeqCst)
3951 );
3952 }
3953
3954 #[test]
3955 fn test_metatls_parsing() {
3956 let channel: ChannelAddr = "metatls!localhost:1234".parse().unwrap();
3958 assert_eq!(
3959 channel,
3960 ChannelAddr::MetaTls(TlsAddr::new("localhost", 1234))
3961 );
3962 let channel: ChannelAddr = "metatls!1.2.3.4:1234".parse().unwrap();
3964 assert_eq!(channel, ChannelAddr::MetaTls(TlsAddr::new("1.2.3.4", 1234)));
3965 let channel: ChannelAddr = "metatls!2401:db00:33c:6902:face:0:2a2:0:1234"
3967 .parse()
3968 .unwrap();
3969 assert_eq!(
3970 channel,
3971 ChannelAddr::MetaTls(TlsAddr::new("2401:db00:33c:6902:face:0:2a2:0", 1234))
3972 );
3973
3974 let channel: ChannelAddr = "metatls![::]:1234".parse().unwrap();
3975 assert_eq!(channel, ChannelAddr::MetaTls(TlsAddr::new("::", 1234)));
3976 }
3977
3978 #[async_timed_test(timeout_secs = 300)]
3979 #[cfg_attr(not(fbcode_build), ignore)]
3981 async fn test_tcp_throughput() {
3982 let config = hyperactor_config::global::lock();
3983 let _guard = config.override_key(config::MESSAGE_DELIVERY_TIMEOUT, Duration::from_mins(5));
3984
3985 let socket_addr: SocketAddr = "[::1]:0".parse().unwrap();
3986 let (local_addr, mut rx) =
3987 server::serve::<String>(ChannelAddr::Tcp(socket_addr), None).unwrap();
3988
3989 let total_num_msgs = 500000;
3991
3992 let receive_handle = tokio::spawn(async move {
3993 let mut num = 0;
3994 for _ in 0..10 * total_num_msgs {
3995 rx.recv().await.unwrap();
3996 num += 1;
3997
3998 if num % 100000 == 0 {
3999 tracing::info!("total number of received messages: {}", num);
4000 }
4001 }
4002 });
4003
4004 let mut tx_handles = vec![];
4005 let mut txs = vec![];
4006 for _ in 0..10 {
4007 let server_addr = local_addr.clone();
4008 let tx = Arc::new(channel::dial::<String>(server_addr).unwrap());
4009 let tx2 = Arc::clone(&tx);
4010 txs.push(tx);
4011 tx_handles.push(tokio::spawn(async move {
4012 let random_string = rand::rng()
4013 .sample_iter(&Alphanumeric)
4014 .take(2048)
4015 .map(char::from)
4016 .collect::<String>();
4017 for _ in 0..total_num_msgs {
4018 tx2.post(random_string.clone());
4019 }
4020 }));
4021 }
4022
4023 receive_handle.await.unwrap();
4024 for handle in tx_handles {
4025 handle.await.unwrap();
4026 }
4027 }
4028
4029 #[tracing_test::traced_test]
4030 #[async_timed_test(timeout_secs = 60)]
4031 #[cfg_attr(not(fbcode_build), ignore)]
4033 async fn test_net_tx_closed_on_server_reject() {
4034 let link = MockLink::<u64>::new();
4035 let receiver_storage = link.receiver_storage();
4036 let mut tx = spawn::<u64>(link);
4037 net_tx_send(&tx, &[100]).await;
4038
4039 {
4040 let (_reader, writer) = take_receiver(&receiver_storage).await;
4041 let _ = FrameWrite::write_frame(
4042 writer,
4043 serialize_response(NetRxResponse::Reject("testing".to_string())).unwrap(),
4044 1024,
4045 0,
4046 )
4047 .await
4048 .map_err(|(_, e)| e);
4049
4050 tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
4053 }
4054
4055 verify_tx_closed(&mut tx.status, "server rejected connection").await;
4056 }
4057
4058 #[async_timed_test(timeout_secs = 60)]
4059 async fn test_server_rejects_conn_on_out_of_sequence_message() {
4060 let config = hyperactor_config::global::lock();
4061 let _guard = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1);
4062 let session_id = SessionId(123);
4063 let (_handle, acceptor_tx, mut rx, _cancel_token) = serve_acceptor_test::<u64>(session_id);
4064
4065 let (sender, receiver) = tokio::io::duplex(5000);
4066 acceptor_tx.send(receiver).unwrap();
4067 let (r, writer) = tokio::io::split(sender);
4068 let mut reader = FrameReader::new(
4069 r,
4070 hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH),
4071 );
4072
4073 let _ = write_stream(writer, 123, &[(0, 100u64), (1, 101u64), (3, 103u64)], true).await;
4074 assert_eq!(rx.recv().await, Some(100u64));
4075 assert_eq!(rx.recv().await, Some(101u64));
4076 let (_, bytes) = reader.next().await.unwrap().unwrap();
4077 let acked = deserialize_response(bytes).unwrap().into_ack().unwrap();
4078 assert_eq!(acked, 0);
4079 let (_, bytes) = reader.next().await.unwrap().unwrap();
4080 let acked = deserialize_response(bytes).unwrap().into_ack().unwrap();
4081 assert_eq!(acked, 1);
4082 let (_, bytes) = reader.next().await.unwrap().unwrap();
4083 assert!(deserialize_response(bytes).unwrap().is_reject());
4084 }
4085
4086 #[async_timed_test(timeout_secs = 60)]
4087 #[cfg_attr(not(fbcode_build), ignore)]
4089 async fn test_stop_net_tx_after_stopping_net_rx() {
4090 hyperactor_telemetry::initialize_logging_for_test();
4091
4092 let config = hyperactor_config::global::lock();
4093 let _guard = config.override_key(config::MESSAGE_DELIVERY_TIMEOUT, Duration::from_mins(5));
4094 let (addr, mut rx) =
4095 server::serve::<u64>(ChannelAddr::Tcp("[::1]:0".parse().unwrap()), None).unwrap();
4096 let socket_addr = match addr {
4097 ChannelAddr::Tcp(a) => a,
4098 _ => panic!("unexpected channel type"),
4099 };
4100 let tx: ChannelTx<u64> = spawn(tcp::link(
4101 socket_addr,
4102 SessionId::random(),
4103 0,
4104 ProtocolKind::Simplex,
4105 ));
4106 tx.send(100).await.unwrap();
4111 assert_eq!(rx.recv().await.unwrap(), 100);
4112 rx.server.stop("testing");
4114 assert!(rx.recv().await.is_err());
4115
4116 tx.post(101);
4119 let mut watcher = tx.status().clone();
4120 let _ = watcher.wait_for(|val| val.is_closed()).await;
4122 assert!(watcher.borrow().is_closed());
4126 }
4127
4128 struct QueueListener {
4132 streams: std::collections::VecDeque<DuplexStream>,
4133 addr: ChannelAddr,
4134 }
4135
4136 #[async_trait]
4137 impl super::Listener for QueueListener {
4138 type Stream = DuplexStream;
4139
4140 async fn accept(&mut self) -> Result<(DuplexStream, ChannelAddr), ServerError> {
4141 match self.streams.pop_front() {
4142 Some(s) => Ok((s, self.addr.clone())),
4143 None => std::future::pending().await,
4144 }
4145 }
4146 }
4147
4148 #[derive(Debug)]
4154 pub(super) struct GatedWriteStream {
4155 inner: DuplexStream,
4156 gate: Arc<GateState>,
4157 }
4158
4159 #[derive(Debug)]
4160 pub(super) struct GateState {
4161 open: AtomicBool,
4162 waker: std::sync::Mutex<Option<std::task::Waker>>,
4163 }
4164
4165 impl GateState {
4166 pub(super) fn new() -> Arc<Self> {
4167 Arc::new(Self {
4168 open: AtomicBool::new(false),
4169 waker: std::sync::Mutex::new(None),
4170 })
4171 }
4172
4173 pub(super) fn open(&self) {
4175 self.open.store(true, Ordering::Release);
4176 if let Some(w) = self.waker.lock().unwrap().take() {
4177 w.wake();
4178 }
4179 }
4180 }
4181
4182 impl GatedWriteStream {
4183 pub(super) fn new(inner: DuplexStream, gate: Arc<GateState>) -> Self {
4184 Self { inner, gate }
4185 }
4186 }
4187
4188 impl AsyncRead for GatedWriteStream {
4189 fn poll_read(
4190 mut self: std::pin::Pin<&mut Self>,
4191 cx: &mut std::task::Context<'_>,
4192 buf: &mut tokio::io::ReadBuf<'_>,
4193 ) -> std::task::Poll<std::io::Result<()>> {
4194 std::pin::Pin::new(&mut self.inner).poll_read(cx, buf)
4195 }
4196 }
4197
4198 impl AsyncWrite for GatedWriteStream {
4199 fn poll_write(
4200 mut self: std::pin::Pin<&mut Self>,
4201 cx: &mut std::task::Context<'_>,
4202 buf: &[u8],
4203 ) -> std::task::Poll<std::io::Result<usize>> {
4204 if !self.gate.open.load(Ordering::Acquire) {
4205 *self.gate.waker.lock().unwrap() = Some(cx.waker().clone());
4206 if !self.gate.open.load(Ordering::Acquire) {
4207 return std::task::Poll::Pending;
4208 }
4209 }
4210 std::pin::Pin::new(&mut self.inner).poll_write(cx, buf)
4211 }
4212
4213 fn poll_flush(
4214 mut self: std::pin::Pin<&mut Self>,
4215 cx: &mut std::task::Context<'_>,
4216 ) -> std::task::Poll<std::io::Result<()>> {
4217 std::pin::Pin::new(&mut self.inner).poll_flush(cx)
4218 }
4219
4220 fn poll_shutdown(
4221 mut self: std::pin::Pin<&mut Self>,
4222 cx: &mut std::task::Context<'_>,
4223 ) -> std::task::Poll<std::io::Result<()>> {
4224 std::pin::Pin::new(&mut self.inner).poll_shutdown(cx)
4225 }
4226 }
4227
4228 struct GatedQueueListener {
4232 streams: std::collections::VecDeque<GatedWriteStream>,
4233 addr: ChannelAddr,
4234 }
4235
4236 #[async_trait]
4237 impl super::Listener for GatedQueueListener {
4238 type Stream = GatedWriteStream;
4239
4240 async fn accept(&mut self) -> Result<(GatedWriteStream, ChannelAddr), ServerError> {
4241 match self.streams.pop_front() {
4242 Some(s) => Ok((s, self.addr.clone())),
4243 None => std::future::pending().await,
4244 }
4245 }
4246 }
4247
4248 struct PreparedConnection {
4251 server_side: DuplexStream,
4252 _client_w: tokio::io::WriteHalf<DuplexStream>,
4256 client_r: ReadHalf<DuplexStream>,
4257 }
4258
4259 async fn prepare_connection(
4265 session_id: SessionId,
4266 stream_id: u8,
4267 kind: super::ProtocolKind,
4268 messages: &[(u64, u64)],
4269 ) -> PreparedConnection {
4270 let (client_side, server_side) = tokio::io::duplex(8192);
4271 let (client_r, mut client_w) = tokio::io::split(client_side);
4272
4273 super::write_link_init(&mut client_w, session_id, stream_id, kind)
4274 .await
4275 .unwrap();
4276 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
4277 for (seq, value) in messages {
4278 let payload =
4279 serde_multipart::serialize_bincode(&Frame::<u64>::Message(*seq, *value)).unwrap();
4280 let mut fw = FrameWrite::new(client_w, payload.framed(), max_len, 0)
4281 .map_err(|(_w, e)| e)
4282 .unwrap();
4283 fw.send().await.unwrap();
4284 client_w = fw.complete();
4285 }
4286
4287 PreparedConnection {
4288 server_side,
4289 _client_w: client_w,
4290 client_r,
4291 }
4292 }
4293
4294 struct SeparateSessionPlan {
4297 session_id: SessionId,
4298 stream_id: u8,
4299 messages: Vec<(u64, u64)>,
4300 }
4301
4302 async fn run_separate_sessions_flush_test(
4313 plans: Vec<SeparateSessionPlan>,
4314 stream_id_label: &str,
4315 ) {
4316 let config = hyperactor_config::global::lock();
4317 let _g_msg = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1_000_000);
4318 let _g_time =
4319 config.override_key(config::MESSAGE_ACK_TIME_INTERVAL, Duration::from_secs(3600));
4320
4321 let mut conns: Vec<PreparedConnection> = Vec::with_capacity(plans.len());
4323 let mut expected_messages: std::collections::HashSet<u64> =
4324 std::collections::HashSet::new();
4325 let mut expected_acks: Vec<u64> = Vec::with_capacity(plans.len());
4326 for plan in &plans {
4327 for (_seq, value) in &plan.messages {
4328 expected_messages.insert(*value);
4329 }
4330 expected_acks.push(plan.messages.iter().map(|(s, _)| *s).max().unwrap());
4331 conns.push(
4332 prepare_connection(
4333 plan.session_id,
4334 plan.stream_id,
4335 super::ProtocolKind::Simplex,
4336 &plan.messages,
4337 )
4338 .await,
4339 );
4340 }
4341
4342 let addr = ChannelAddr::Local(u64::MAX);
4343 let listener = QueueListener {
4344 streams: conns
4345 .iter_mut()
4346 .map(|c| {
4347 std::mem::replace(&mut c.server_side, tokio::io::duplex(1).0)
4349 })
4350 .collect(),
4351 addr: addr.clone(),
4352 };
4353 let (_addr, mut rx) = super::server::serve_with_listener::<u64, _>(listener, addr).unwrap();
4354
4355 let mut received: std::collections::HashSet<u64> = std::collections::HashSet::new();
4357 for _ in 0..expected_messages.len() {
4358 received.insert(rx.recv().await.unwrap());
4359 }
4360 assert_eq!(
4361 received, expected_messages,
4362 "{stream_id_label}: every produced message should reach the application"
4363 );
4364
4365 tokio::time::sleep(Duration::from_millis(100)).await;
4372
4373 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
4374 let mut readers: Vec<FrameReader<ReadHalf<DuplexStream>>> = conns
4375 .into_iter()
4376 .map(|c| FrameReader::new(c.client_r, max_len))
4377 .collect();
4378 for (idx, reader) in readers.iter_mut().enumerate() {
4379 match tokio::time::timeout(Duration::from_millis(10), reader.next()).await {
4380 Err(_) => {} Ok(Err(e)) => panic!(
4382 "{stream_id_label}: connection {idx} frame reader error before rx.join: {e}"
4383 ),
4384 Ok(Ok(None)) => {
4385 panic!("{stream_id_label}: connection {idx} closed before rx.join()")
4386 }
4387 Ok(Ok(Some((_, bytes)))) => {
4388 let resp = super::deserialize_response(bytes).unwrap();
4389 panic!(
4390 "{stream_id_label}: connection {idx} unexpectedly received {resp:?} \
4391 before rx.join()"
4392 );
4393 }
4394 }
4395 }
4396
4397 rx.join().await;
4398
4399 for (idx, (reader, expected_ack)) in readers.iter_mut().zip(&expected_acks).enumerate() {
4403 let bytes = tokio::time::timeout(Duration::from_millis(50), reader.next())
4404 .await
4405 .unwrap_or_else(|_| {
4406 panic!(
4407 "{stream_id_label}: connection {idx} produced no Ack frame within 50ms \
4408 after rx.join()"
4409 )
4410 })
4411 .expect("frame reader error")
4412 .expect("frame reader returned None");
4413 let acked = super::deserialize_response(bytes.1)
4414 .unwrap()
4415 .into_ack()
4416 .unwrap_or_else(|other| {
4417 panic!("{stream_id_label}: connection {idx} expected Ack, got {other:?}")
4418 });
4419 assert_eq!(
4420 acked, *expected_ack,
4421 "{stream_id_label}: connection {idx} ack mismatch"
4422 );
4423
4424 let bytes = tokio::time::timeout(Duration::from_millis(50), reader.next())
4425 .await
4426 .unwrap_or_else(|_| {
4427 panic!(
4428 "{stream_id_label}: connection {idx} produced no Closed frame within 50ms"
4429 )
4430 })
4431 .expect("frame reader error")
4432 .expect("frame reader returned None");
4433 assert!(
4434 super::deserialize_response(bytes.1).unwrap().is_closed(),
4435 "{stream_id_label}: connection {idx} expected Closed terminal frame"
4436 );
4437 }
4438 }
4439
4440 #[async_timed_test(timeout_secs = 30)]
4441 async fn rx_join_flushes_pending_ack_single_stream() {
4442 let plans = (1u64..=3)
4446 .map(|sid| SeparateSessionPlan {
4447 session_id: SessionId(sid),
4448 stream_id: 0,
4449 messages: (0u64..3).map(|seq| (seq, sid * 100 + seq)).collect(),
4450 })
4451 .collect();
4452 run_separate_sessions_flush_test(plans, "single-stream").await;
4453 }
4454
4455 #[async_timed_test(timeout_secs = 30)]
4456 async fn rx_join_flushes_pending_ack_multi_stream() {
4457 let plans = (1u64..=3)
4462 .map(|sid| SeparateSessionPlan {
4463 session_id: SessionId(sid),
4464 stream_id: 1,
4465 messages: (0u64..3).map(|seq| (seq, sid * 100 + seq)).collect(),
4466 })
4467 .collect();
4468 run_separate_sessions_flush_test(plans, "multi-stream").await;
4469 }
4470
4471 #[async_timed_test(timeout_secs = 30)]
4479 async fn rx_join_flushes_pending_ack_shared_multi_stream_session() {
4480 let config = hyperactor_config::global::lock();
4481 let _g_msg = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1_000_000);
4482 let _g_time =
4483 config.override_key(config::MESSAGE_ACK_TIME_INTERVAL, Duration::from_secs(3600));
4484
4485 let session_id = SessionId(99);
4486 let num_streams = 3u8;
4487 let msgs_per_stream = 3u64;
4488 let mut conns: Vec<PreparedConnection> = Vec::with_capacity(num_streams as usize);
4489 let mut expected_messages: std::collections::HashSet<u64> =
4490 std::collections::HashSet::new();
4491 for stream_id in 1..=num_streams {
4492 let messages: Vec<(u64, u64)> = (0u64..msgs_per_stream)
4493 .map(|i| {
4494 let seq = (stream_id as u64 - 1) * msgs_per_stream + i;
4495 (seq, 1000 + seq)
4496 })
4497 .collect();
4498 for (_, v) in &messages {
4499 expected_messages.insert(*v);
4500 }
4501 conns.push(
4502 prepare_connection(
4503 session_id,
4504 stream_id,
4505 super::ProtocolKind::Simplex,
4506 &messages,
4507 )
4508 .await,
4509 );
4510 }
4511 let highest_seq = num_streams as u64 * msgs_per_stream - 1;
4512
4513 let addr = ChannelAddr::Local(u64::MAX);
4514 let listener = QueueListener {
4515 streams: conns
4516 .iter_mut()
4517 .map(|c| std::mem::replace(&mut c.server_side, tokio::io::duplex(1).0))
4518 .collect(),
4519 addr: addr.clone(),
4520 };
4521 let (_addr, mut rx) = super::server::serve_with_listener::<u64, _>(listener, addr).unwrap();
4522
4523 let mut received: std::collections::HashSet<u64> = std::collections::HashSet::new();
4524 for _ in 0..expected_messages.len() {
4525 received.insert(rx.recv().await.unwrap());
4526 }
4527 assert_eq!(
4528 received, expected_messages,
4529 "shared-session multi-stream: every message reaches the application"
4530 );
4531
4532 tokio::time::sleep(Duration::from_millis(100)).await;
4533
4534 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
4535 let mut readers: Vec<FrameReader<ReadHalf<DuplexStream>>> = conns
4536 .into_iter()
4537 .map(|c| FrameReader::new(c.client_r, max_len))
4538 .collect();
4539 for (idx, reader) in readers.iter_mut().enumerate() {
4540 match tokio::time::timeout(Duration::from_millis(10), reader.next()).await {
4541 Err(_) | Ok(Ok(None)) => {} Ok(Err(e)) => {
4543 panic!("shared-session multi-stream: stream {idx} frame reader error: {e}")
4544 }
4545 Ok(Ok(Some((_, bytes)))) => {
4546 let resp = super::deserialize_response(bytes).unwrap();
4547 panic!(
4548 "shared-session multi-stream: stream {idx} unexpectedly received \
4549 {resp:?} before rx.join()"
4550 );
4551 }
4552 }
4553 }
4554
4555 rx.join().await;
4556
4557 let mut ack_count = 0;
4562 let mut closed_count = 0;
4563 for (idx, reader) in readers.iter_mut().enumerate() {
4564 loop {
4565 match tokio::time::timeout(Duration::from_millis(50), reader.next()).await {
4566 Err(_) => panic!(
4567 "shared-session multi-stream: stream {idx} did not yield expected \
4568 frames within 50ms after rx.join()"
4569 ),
4570 Ok(Err(e)) => panic!("frame reader error: {e}"),
4571 Ok(Ok(None)) => break,
4572 Ok(Ok(Some((_, bytes)))) => {
4573 let resp = super::deserialize_response(bytes).unwrap();
4574 match resp {
4575 NetRxResponse::Ack(seq) => {
4576 assert_eq!(
4577 seq, highest_seq,
4578 "shared-session multi-stream: ack should cover the full \
4579 contiguous range 0..={highest_seq}"
4580 );
4581 ack_count += 1;
4582 }
4583 NetRxResponse::Closed => {
4584 closed_count += 1;
4585 break;
4586 }
4587 other => panic!(
4588 "shared-session multi-stream: stream {idx} unexpected {other:?}"
4589 ),
4590 }
4591 }
4592 }
4593 }
4594 }
4595 assert!(
4596 ack_count >= 1,
4597 "shared-session multi-stream: expected at least one Ack({highest_seq}); \
4598 got {ack_count}"
4599 );
4600 assert!(
4601 ack_count <= num_streams as usize,
4602 "shared-session multi-stream: expected at most {num_streams} Ack({highest_seq}) \
4603 frames; got {ack_count}"
4604 );
4605 assert_eq!(
4606 closed_count, num_streams as usize,
4607 "shared-session multi-stream: every stream should emit its own Closed frame"
4608 );
4609 }
4610
4611 #[async_timed_test(timeout_secs = 30)]
4618 async fn duplex_test_listener_rejects_simplex_link_init() {
4619 let conn =
4620 prepare_connection(SessionId(1), 0, super::ProtocolKind::Simplex, &[(0, 123)]).await;
4621 let addr = ChannelAddr::Local(u64::MAX);
4622 let listener = QueueListener {
4623 streams: std::collections::VecDeque::from([conn.server_side]),
4624 addr: addr.clone(),
4625 };
4626
4627 let mut server = super::duplex::serve_with_listener::<u64, u64, _>(listener, addr).unwrap();
4628
4629 assert!(
4630 tokio::time::timeout(Duration::from_millis(500), server.accept())
4631 .await
4632 .is_err(),
4633 "duplex test server accepted a simplex LinkInit",
4634 );
4635 }
4636
4637 #[async_timed_test(timeout_secs = 30)]
4644 async fn server_join_flushes_pending_ack_duplex_session() {
4645 let config = hyperactor_config::global::lock();
4646 let _g_msg = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1_000_000);
4647 let _g_time =
4648 config.override_key(config::MESSAGE_ACK_TIME_INTERVAL, Duration::from_secs(3600));
4649
4650 let session_count = 3u64;
4651 let msgs_per_session = 3u64;
4652 let mut conns: Vec<PreparedConnection> = Vec::with_capacity(session_count as usize);
4653 let mut expected_messages: std::collections::HashSet<u64> =
4654 std::collections::HashSet::new();
4655 let mut expected_acks: Vec<u64> = Vec::with_capacity(session_count as usize);
4656 for sid in 1..=session_count {
4657 let messages: Vec<(u64, u64)> = (0u64..msgs_per_session)
4658 .map(|seq| (seq, sid * 100 + seq))
4659 .collect();
4660 for (_, v) in &messages {
4661 expected_messages.insert(*v);
4662 }
4663 expected_acks.push(messages.iter().map(|(s, _)| *s).max().unwrap());
4664 conns.push(
4665 prepare_connection(SessionId(sid), 0, super::ProtocolKind::Duplex, &messages).await,
4666 );
4667 }
4668
4669 let addr = ChannelAddr::Local(u64::MAX);
4670 let listener = QueueListener {
4671 streams: conns
4672 .iter_mut()
4673 .map(|c| std::mem::replace(&mut c.server_side, tokio::io::duplex(1).0))
4674 .collect(),
4675 addr: addr.clone(),
4676 };
4677 let mut server = super::duplex::serve_with_listener::<u64, u64, _>(listener, addr).unwrap();
4678
4679 let mut all_rx: Vec<super::duplex::DuplexRx<u64>> =
4685 Vec::with_capacity(session_count as usize);
4686 let mut all_tx: Vec<super::duplex::DuplexTx<u64>> =
4687 Vec::with_capacity(session_count as usize);
4688 for _ in 0..session_count {
4689 let (rx, tx) = server.accept().await.unwrap();
4690 all_rx.push(rx);
4691 all_tx.push(tx);
4692 }
4693
4694 let mut received: std::collections::HashSet<u64> = std::collections::HashSet::new();
4699 for rx in all_rx.iter_mut() {
4700 for _ in 0..msgs_per_session {
4701 received.insert(rx.recv().await.unwrap());
4702 }
4703 }
4704 assert_eq!(
4705 received, expected_messages,
4706 "duplex: every produced message should reach the application"
4707 );
4708
4709 tokio::time::sleep(Duration::from_millis(100)).await;
4713
4714 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
4715 let mut readers: Vec<FrameReader<ReadHalf<DuplexStream>>> = conns
4716 .into_iter()
4717 .map(|c| FrameReader::new(c.client_r, max_len))
4718 .collect();
4719 for (idx, reader) in readers.iter_mut().enumerate() {
4720 match tokio::time::timeout(Duration::from_millis(10), reader.next()).await {
4721 Err(_) => {} Ok(Err(e)) => {
4723 panic!("duplex: connection {idx} frame reader error before join: {e}")
4724 }
4725 Ok(Ok(None)) => {
4726 panic!("duplex: connection {idx} closed before server.join()")
4727 }
4728 Ok(Ok(Some((_, bytes)))) => {
4729 let resp = super::deserialize_response(bytes).unwrap();
4730 panic!(
4731 "duplex: connection {idx} unexpectedly received {resp:?} \
4732 before server.join()"
4733 );
4734 }
4735 }
4736 }
4737
4738 server.join().await;
4744
4745 for (idx, (reader, expected_ack)) in readers.iter_mut().zip(&expected_acks).enumerate() {
4750 let bytes = tokio::time::timeout(Duration::from_millis(50), reader.next())
4751 .await
4752 .unwrap_or_else(|_| {
4753 panic!(
4754 "duplex: connection {idx} produced no Ack frame within 50ms after \
4755 server.join()"
4756 )
4757 })
4758 .expect("frame reader error")
4759 .expect("frame reader returned None");
4760 let acked = super::deserialize_response(bytes.1)
4761 .unwrap()
4762 .into_ack()
4763 .unwrap_or_else(|other| {
4764 panic!("duplex: connection {idx} expected Ack, got {other:?}")
4765 });
4766 assert_eq!(
4767 acked, *expected_ack,
4768 "duplex: connection {idx} ack mismatch"
4769 );
4770
4771 let bytes = tokio::time::timeout(Duration::from_millis(50), reader.next())
4772 .await
4773 .unwrap_or_else(|_| {
4774 panic!("duplex: connection {idx} produced no Closed frame within 50ms")
4775 })
4776 .expect("frame reader error")
4777 .expect("frame reader returned None");
4778 assert!(
4779 super::deserialize_response(bytes.1).unwrap().is_closed(),
4780 "duplex: connection {idx} expected Closed terminal frame"
4781 );
4782 }
4783 }
4784
4785 struct DuplexDialMockLink {
4790 session_id: SessionId,
4791 streams: std::collections::VecDeque<DuplexStream>,
4792 }
4793
4794 impl fmt::Debug for DuplexDialMockLink {
4795 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4796 f.debug_struct("DuplexDialMockLink")
4797 .field("session_id", &self.session_id)
4798 .field("remaining_streams", &self.streams.len())
4799 .finish()
4800 }
4801 }
4802
4803 #[async_trait]
4804 impl super::Link for DuplexDialMockLink {
4805 type Stream = DuplexStream;
4806
4807 fn dest(&self) -> ChannelAddr {
4808 ChannelAddr::Local(u64::MAX)
4809 }
4810
4811 fn link_id(&self) -> SessionId {
4812 self.session_id
4813 }
4814
4815 async fn next(&mut self) -> Result<DuplexStream, ClientError> {
4816 match self.streams.pop_front() {
4817 Some(mut stream) => {
4818 super::write_link_init(
4819 &mut stream,
4820 self.session_id,
4821 0,
4822 super::ProtocolKind::Duplex,
4823 )
4824 .await
4825 .map_err(|err| ClientError::Io(self.dest(), err))?;
4826 Ok(stream)
4827 }
4828 None => Err(ClientError::Connect(
4829 self.dest(),
4830 std::io::Error::other("mock link exhausted"),
4831 "no more streams".into(),
4832 )),
4833 }
4834 }
4835 }
4836
4837 #[async_timed_test(timeout_secs = 30)]
4846 async fn duplex_serve_flushes_pending_ack_on_app_closed() {
4847 let config = hyperactor_config::global::lock();
4848 let _g_msg = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1_000_000);
4849 let _g_time =
4850 config.override_key(config::MESSAGE_ACK_TIME_INTERVAL, Duration::from_secs(3600));
4851
4852 let session_id = SessionId(1);
4853 let messages: Vec<(u64, u64)> = vec![(0, 100), (1, 200), (2, 300)];
4854 let expected_ack: u64 = 2;
4855 let conn = prepare_connection(session_id, 0, super::ProtocolKind::Duplex, &messages).await;
4856
4857 let addr = ChannelAddr::Local(u64::MAX);
4858 let listener = QueueListener {
4859 streams: std::collections::VecDeque::from([conn.server_side]),
4860 addr: addr.clone(),
4861 };
4862
4863 let mut server = super::duplex::serve_with_listener::<u64, u64, _>(listener, addr).unwrap();
4864
4865 let (mut server_rx, server_tx) = server.accept().await.unwrap();
4866
4867 let mut received: Vec<u64> = Vec::with_capacity(messages.len());
4869 for _ in &messages {
4870 received.push(server_rx.recv().await.unwrap());
4871 }
4872 let expected_values: Vec<u64> = messages.iter().map(|(_, v)| *v).collect();
4873 assert_eq!(
4874 received, expected_values,
4875 "duplex serve: every message should reach the application"
4876 );
4877
4878 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
4881 tokio::time::sleep(Duration::from_millis(100)).await;
4882 let mut reader = FrameReader::new(conn.client_r, max_len);
4883 match tokio::time::timeout(Duration::from_millis(10), reader.next()).await {
4884 Err(_) => {} Ok(Err(e)) => panic!("duplex serve: frame reader error before app close: {e}"),
4886 Ok(Ok(None)) => panic!("duplex serve: wire closed before app close"),
4887 Ok(Ok(Some((_, bytes)))) => {
4888 let resp = super::deserialize_response(bytes).unwrap();
4889 panic!("duplex serve: unexpectedly received {resp:?} before app close");
4890 }
4891 }
4892
4893 drop(server_tx);
4899
4900 let bytes = tokio::time::timeout(Duration::from_millis(100), reader.next())
4901 .await
4902 .unwrap_or_else(|_| panic!("duplex serve: produced no Ack frame within 100ms"))
4903 .expect("frame reader error")
4904 .expect("frame reader returned None");
4905 let acked = super::deserialize_response(bytes.1)
4906 .unwrap()
4907 .into_ack()
4908 .unwrap_or_else(|other| panic!("duplex serve: expected Ack, got {other:?}"));
4909 assert_eq!(
4910 acked, expected_ack,
4911 "duplex serve: ack should cover the highest seq received"
4912 );
4913
4914 let bytes = tokio::time::timeout(Duration::from_millis(100), reader.next())
4915 .await
4916 .unwrap_or_else(|_| panic!("duplex serve: produced no Closed frame within 100ms"))
4917 .expect("frame reader error")
4918 .expect("frame reader returned None");
4919 assert!(
4920 super::deserialize_response(bytes.1).unwrap().is_closed(),
4921 "duplex serve: expected Closed terminal frame after Ack"
4922 );
4923
4924 drop(server_rx);
4925 server.join().await;
4930 }
4931
4932 #[async_timed_test(timeout_secs = 30)]
4942 async fn mux_duplex_join_flushes_pending_ack_for_session() {
4943 let config = hyperactor_config::global::lock();
4944 let _g_msg = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1_000_000);
4945 let _g_time =
4946 config.override_key(config::MESSAGE_ACK_TIME_INTERVAL, Duration::from_secs(3600));
4947
4948 let session_id = SessionId(1);
4949 let messages: Vec<(u64, u64)> = vec![(0, 100), (1, 200), (2, 300)];
4950 let expected_ack: u64 = 2;
4951 let conn = prepare_connection(session_id, 0, super::ProtocolKind::Duplex, &messages).await;
4952
4953 let addr = ChannelAddr::Local(u64::MAX);
4954 let listener = QueueListener {
4955 streams: std::collections::VecDeque::from([conn.server_side]),
4956 addr: addr.clone(),
4957 };
4958
4959 let mut parts =
4960 super::mux::serve_with_listener::<u64, u64, u64, _>(listener, addr).unwrap();
4961
4962 let (mut server_rx, _server_tx) = parts.duplex.accept().await.unwrap();
4967 for _ in &messages {
4968 server_rx.recv().await.unwrap();
4969 }
4970
4971 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
4973 let mut reader = FrameReader::new(conn.client_r, max_len);
4974 tokio::time::sleep(Duration::from_millis(50)).await;
4975 match tokio::time::timeout(Duration::from_millis(10), reader.next()).await {
4976 Err(_) => {} Ok(Err(e)) => panic!("mux: frame reader error before shutdown: {e}"),
4978 Ok(Ok(None)) => panic!("mux: wire closed before shutdown"),
4979 Ok(Ok(Some((_, bytes)))) => {
4980 let resp = super::deserialize_response(bytes).unwrap();
4981 panic!("mux: unexpectedly received {resp:?} before shutdown");
4982 }
4983 }
4984
4985 parts.duplex.stop("test mux duplex join flush");
4991 parts.duplex.join().await;
4992
4993 let bytes = tokio::time::timeout(Duration::from_millis(100), reader.next())
4994 .await
4995 .unwrap_or_else(|_| panic!("mux: produced no Ack frame within 100ms after join"))
4996 .expect("frame reader error")
4997 .expect("frame reader returned None");
4998 let acked = super::deserialize_response(bytes.1)
4999 .unwrap()
5000 .into_ack()
5001 .unwrap_or_else(|other| panic!("mux: expected Ack, got {other:?}"));
5002 assert_eq!(
5003 acked, expected_ack,
5004 "mux: ack should cover the highest seq received"
5005 );
5006
5007 let bytes = tokio::time::timeout(Duration::from_millis(100), reader.next())
5008 .await
5009 .unwrap_or_else(|_| panic!("mux: produced no Closed frame within 100ms after join"))
5010 .expect("frame reader error")
5011 .expect("frame reader returned None");
5012 assert!(
5013 super::deserialize_response(bytes.1).unwrap().is_closed(),
5014 "mux: expected Closed terminal frame after Ack"
5015 );
5016
5017 let _ = parts.simplex;
5019 let _ = parts.join_handle.await;
5020 }
5021
5022 #[async_timed_test(timeout_secs = 30)]
5032 async fn mux_duplex_join_blocks_until_terminal_write_completes() {
5033 let config = hyperactor_config::global::lock();
5034 let _g_msg = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1_000_000);
5035 let _g_time =
5036 config.override_key(config::MESSAGE_ACK_TIME_INTERVAL, Duration::from_secs(3600));
5037
5038 let session_id = SessionId(1);
5039 let messages: Vec<(u64, u64)> = vec![(0, 100), (1, 200), (2, 300)];
5040 let expected_ack: u64 = 2;
5041 let conn = prepare_connection(session_id, 0, super::ProtocolKind::Duplex, &messages).await;
5042
5043 let gate = GateState::new();
5046 let server_side_gated = GatedWriteStream::new(conn.server_side, gate.clone());
5047
5048 let addr = ChannelAddr::Local(u64::MAX);
5049 let listener = GatedQueueListener {
5050 streams: std::collections::VecDeque::from([server_side_gated]),
5051 addr: addr.clone(),
5052 };
5053
5054 let mut parts =
5055 super::mux::serve_with_listener::<u64, u64, u64, _>(listener, addr).unwrap();
5056
5057 let (mut server_rx, _server_tx) = parts.duplex.accept().await.unwrap();
5058 for _ in &messages {
5059 server_rx.recv().await.unwrap();
5060 }
5061
5062 parts
5068 .duplex
5069 .stop("test mux duplex join blocks until terminal write");
5070 let mut join_task = tokio::spawn(async move { parts.duplex.join().await });
5071
5072 let blocked = tokio::time::timeout(Duration::from_millis(200), &mut join_task).await;
5073 assert!(
5074 blocked.is_err(),
5075 "duplex.join() returned before the gated terminal write could complete"
5076 );
5077
5078 gate.open();
5081
5082 tokio::time::timeout(Duration::from_secs(5), join_task)
5083 .await
5084 .expect("duplex.join() did not resolve after gate opened")
5085 .expect("join task panicked");
5086
5087 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
5089 let mut reader = FrameReader::new(conn.client_r, max_len);
5090
5091 let bytes = tokio::time::timeout(Duration::from_millis(200), reader.next())
5092 .await
5093 .expect("no Ack frame")
5094 .expect("frame reader error")
5095 .expect("frame reader returned None");
5096 let acked = super::deserialize_response(bytes.1)
5097 .unwrap()
5098 .into_ack()
5099 .unwrap_or_else(|other| panic!("expected Ack, got {other:?}"));
5100 assert_eq!(acked, expected_ack);
5101
5102 let bytes = tokio::time::timeout(Duration::from_millis(200), reader.next())
5103 .await
5104 .expect("no Closed frame")
5105 .expect("frame reader error")
5106 .expect("frame reader returned None");
5107 assert!(super::deserialize_response(bytes.1).unwrap().is_closed());
5108 }
5109
5110 #[async_timed_test(timeout_secs = 30)]
5117 async fn mux_split_simplex_join_flushes_final_ack() {
5118 let config = hyperactor_config::global::lock();
5119 let _g_msg = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1_000_000);
5120 let _g_time =
5121 config.override_key(config::MESSAGE_ACK_TIME_INTERVAL, Duration::from_secs(3600));
5122
5123 let session_id = SessionId(1);
5124 let messages: Vec<(u64, u64)> = vec![(0, 100), (1, 200), (2, 300)];
5125 let expected_ack: u64 = 2;
5126 let conn = prepare_connection(session_id, 0, super::ProtocolKind::Simplex, &messages).await;
5127
5128 let addr = ChannelAddr::Local(u64::MAX);
5129 let listener = QueueListener {
5130 streams: std::collections::VecDeque::from([conn.server_side]),
5131 addr: addr.clone(),
5132 };
5133
5134 let mut parts =
5135 super::mux::serve_with_listener::<u64, u64, u64, _>(listener, addr).unwrap();
5136
5137 for _ in &messages {
5139 parts.simplex.recv().await.unwrap();
5140 }
5141
5142 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
5144 let mut reader = FrameReader::new(conn.client_r, max_len);
5145 tokio::time::sleep(Duration::from_millis(50)).await;
5146 match tokio::time::timeout(Duration::from_millis(10), reader.next()).await {
5147 Err(_) => {}
5148 Ok(Err(e)) => panic!("mux split: frame reader error before join: {e}"),
5149 Ok(Ok(None)) => panic!("mux split: wire closed before join"),
5150 Ok(Ok(Some((_, bytes)))) => {
5151 let resp = super::deserialize_response(bytes).unwrap();
5152 panic!("mux split: unexpectedly received {resp:?} before join");
5153 }
5154 }
5155
5156 parts.simplex.join().await;
5161
5162 let bytes = tokio::time::timeout(Duration::from_millis(100), reader.next())
5163 .await
5164 .unwrap_or_else(|_| panic!("mux split: produced no Ack frame within 100ms after join"))
5165 .expect("frame reader error")
5166 .expect("frame reader returned None");
5167 let acked = super::deserialize_response(bytes.1)
5168 .unwrap()
5169 .into_ack()
5170 .unwrap_or_else(|other| panic!("mux split: expected Ack, got {other:?}"));
5171 assert_eq!(
5172 acked, expected_ack,
5173 "mux split: simplex.join must flush the final ack before returning"
5174 );
5175
5176 let bytes = tokio::time::timeout(Duration::from_millis(100), reader.next())
5177 .await
5178 .unwrap_or_else(|_| {
5179 panic!("mux split: produced no Closed frame within 100ms after join")
5180 })
5181 .expect("frame reader error")
5182 .expect("frame reader returned None");
5183 assert!(
5184 super::deserialize_response(bytes.1).unwrap().is_closed(),
5185 "mux split: expected Closed terminal frame after Ack"
5186 );
5187
5188 let _ = parts.duplex;
5190 parts.cancel.cancel();
5191 let _ = parts.join_handle.await;
5192 }
5193
5194 #[async_timed_test(timeout_secs = 30)]
5201 async fn duplex_client_stop_then_join_flushes_pending_ack() {
5202 let config = hyperactor_config::global::lock();
5203 let _g_msg = config.override_key(config::MESSAGE_ACK_EVERY_N_MESSAGES, 1_000_000);
5204 let _g_time =
5205 config.override_key(config::MESSAGE_ACK_TIME_INTERVAL, Duration::from_secs(3600));
5206
5207 let session_id = SessionId(123);
5208 let (client_side, server_side) = tokio::io::duplex(8192);
5209 let (mut test_r, mut test_w) = tokio::io::split(server_side);
5210
5211 let link = DuplexDialMockLink {
5212 session_id,
5213 streams: std::collections::VecDeque::from([client_side]),
5214 };
5215
5216 let mut dial_client = super::duplex::spawn::<u64, u64>(link);
5217 let _dial_tx = dial_client.tx();
5218 let mut dial_rx = dial_client.take_rx().unwrap();
5219
5220 super::read_link_init(&mut test_r).await.unwrap();
5222
5223 let max_len = hyperactor_config::global::get(config::CODEC_MAX_FRAME_LENGTH);
5228 let messages: Vec<(u64, u64)> = vec![(0, 100), (1, 200), (2, 300)];
5229 let expected_ack: u64 = 2;
5230 for (seq, value) in &messages {
5231 let payload =
5232 serde_multipart::serialize_bincode(&Frame::<u64>::Message(*seq, *value)).unwrap();
5233 let mut fw = FrameWrite::new(
5234 test_w,
5235 payload.framed(),
5236 max_len,
5237 super::ACCEPTOR_TO_INITIATOR,
5238 )
5239 .map_err(|(_w, e)| e)
5240 .unwrap();
5241 fw.send().await.unwrap();
5242 test_w = fw.complete();
5243 }
5244
5245 let mut received: Vec<u64> = Vec::with_capacity(messages.len());
5248 for _ in &messages {
5249 received.push(dial_rx.recv().await.unwrap());
5250 }
5251 let expected_values: Vec<u64> = messages.iter().map(|(_, v)| *v).collect();
5252 assert_eq!(
5253 received, expected_values,
5254 "dial: every message should reach the application"
5255 );
5256
5257 tokio::time::sleep(Duration::from_millis(100)).await;
5260 let mut reader = FrameReader::new(test_r, max_len);
5261 match tokio::time::timeout(Duration::from_millis(10), reader.next()).await {
5262 Err(_) => {} Ok(Err(e)) => panic!("dial: frame reader error before join: {e}"),
5264 Ok(Ok(None)) => panic!("dial: wire closed before join"),
5265 Ok(Ok(Some((_, bytes)))) => {
5266 let resp = super::deserialize_response(bytes).unwrap();
5267 panic!("dial: unexpectedly received {resp:?} before join");
5268 }
5269 }
5270
5271 dial_client.join().await;
5276
5277 let bytes = tokio::time::timeout(Duration::from_millis(100), reader.next())
5278 .await
5279 .unwrap_or_else(|_| panic!("dial: produced no Ack frame within 100ms after join"))
5280 .expect("frame reader error")
5281 .expect("frame reader returned None");
5282 let acked = super::deserialize_response(bytes.1)
5283 .unwrap()
5284 .into_ack()
5285 .unwrap_or_else(|other| panic!("dial: expected Ack, got {other:?}"));
5286 assert_eq!(
5287 acked, expected_ack,
5288 "dial: ack should cover the highest seq received"
5289 );
5290
5291 let bytes = tokio::time::timeout(Duration::from_millis(100), reader.next())
5295 .await
5296 .unwrap_or_else(|_| panic!("dial: produced no Closed frame within 100ms after join"))
5297 .expect("frame reader error")
5298 .expect("frame reader returned None");
5299 assert!(
5300 super::deserialize_response(bytes.1).unwrap().is_closed(),
5301 "dial: expected Closed terminal frame after Ack"
5302 );
5303
5304 drop(dial_rx);
5305 drop(test_w);
5306 }
5307
5308 #[async_timed_test(timeout_secs = 30)]
5316 async fn duplex_client_stop_then_join_terminates_in_progress_recv() {
5317 let session_id = SessionId(123);
5318 let (client_side, server_side) = tokio::io::duplex(8192);
5319 let (mut test_r, _test_w) = tokio::io::split(server_side);
5320
5321 let link = DuplexDialMockLink {
5322 session_id,
5323 streams: std::collections::VecDeque::from([client_side]),
5324 };
5325
5326 let mut dial_client = super::duplex::spawn::<u64, u64>(link);
5327 let mut dial_rx = dial_client.take_rx().unwrap();
5328
5329 super::read_link_init(&mut test_r).await.unwrap();
5332
5333 let recv_handle: tokio::task::JoinHandle<Result<u64, ChannelError>> =
5338 tokio::spawn(async move { dial_rx.recv().await });
5339
5340 dial_client.join().await;
5344
5345 let result = tokio::time::timeout(Duration::from_millis(100), recv_handle)
5346 .await
5347 .expect("parked recv should resolve within 100ms after join")
5348 .expect("recv task should not panic");
5349 assert!(
5350 matches!(result, Err(ChannelError::Closed)),
5351 "in-progress recv should resolve with ChannelError::Closed after join, got {result:?}"
5352 );
5353 }
5354}