//! A shutdown returns with every port the relay listened on closed, so a relay can start again //! at once on the same ports, in the same process: its UDP endpoint (every socket of its group, //! where it has one), its WebSocket listener and its control listener. By then every session //! has ended and signed its summary. The test runs in a task on a runtime of one worker, as a //! restart from a spawned task does: there a task that a shutdown only told to go //! (`JoinHandle::abort`), or a QUIC endpoint's driver that has yet to see its last handle go, //! cannot have run to its end, socket and all, until the shutdown's caller yields. The relay is //! shut down and started again on its ports many times, with sessions open over QUIC, //! WebTransport and WebSocket. mod common; use common::*; use joule_moq_relay::client::ClientOptions; use joule_moq_relay::{RelayConfig, RelayServer}; use std::net::SocketAddr; use std::time::Duration; use tokio::time::timeout; const ROUNDS: usize = 20; /// Bind each address at once, as a relay starting again on it does first. fn free(tcp: &[SocketAddr], udp: &[SocketAddr], round: usize) { for &a in tcp { if let Err(e) = std::net::TcpListener::bind(a) { panic!("round {round}: TCP {a} is still bound when the shutdown returns: {e}"); } } for &a in udp { if let Err(e) = std::net::UdpSocket::bind(a) { panic!("round {round}: UDP {a} is still bound when the shutdown returns: {e}"); } } } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn a_shutdown_from_a_task_frees_every_port_for_a_start_at_once() { let t = tokio::spawn(async { let mut cfg = RelayConfig::loopback(); cfg.control.listen = Some(loopback()); cfg.limits.goaway_timeout_ms = 200; for round in 0..ROUNDS { let server = RelayServer::start(cfg.clone()).await.unwrap_or_else(|e| panic!("round {round}: the relay starts again on the same ports: {e}")); let (quic, ws, control) = (server.quic_addr.unwrap(), server.websocket_addr.unwrap(), server.control_addr.unwrap()); (cfg.quic_listen, cfg.websocket_listen, cfg.control.listen) = (Some(quic), Some(ws), Some(control)); // A session over each transport, open as the shutdown begins. let mut clients = Vec::new(); for via in [Via::Quic, Via::WebTransport, Via::WebSocket] { clients.push(connect(&server, via, ClientOptions::default()).await); } let relay = server.relay().clone(); assert!(until(STEP, || relay.stats().sessions_open == 3).await, "round {round}: three sessions open"); server.shutdown().await; free(&[ws, control], &[quic], round); assert_eq!(relay.stats().sessions_open, 0, "round {round}: every session has ended"); assert_eq!(relay.summaries().len(), 3, "round {round}: every session's summary is in"); drop(clients); } }); timeout(Duration::from_secs(300), t).await.expect("in time").unwrap(); }