use std::io::{Read, Write};
use std::net::{SocketAddr, TcpStream};
use std::sync::mpsc::{RecvTimeoutError, channel};
use std::time::Duration;
use liminal::protocol::{Frame, ProtocolError, decode, encode, encoded_len};
use crate::ServerError;
use crate::config::types::{LimitsConfig, ServerConfig, ServicesConfig, WebSocketConfig};
use crate::server::connection::WebSocketListener;
use crate::server::connection::participant_only_services::{HeldStart, held_start_supervisor};
use crate::server::listener::ServerListener;
type TestResult = Result<(), Box<dyn std::error::Error>>;
const WITHIN: Duration = Duration::from_secs(5);
fn config() -> Result<ServerConfig, Box<dyn std::error::Error>> {
Ok(ServerConfig {
listen_address: "127.0.0.1:0".parse()?,
extra_listen_addresses: Vec::new(),
health_listen_address: "127.0.0.1:0".parse()?,
drain_timeout_ms: 30_000,
channels: Vec::new(),
routing_rules: Vec::new(),
persistence_path: None,
cluster: None,
auth: None,
services: ServicesConfig::default(),
limits: LimitsConfig::default(),
participant: None,
websocket: None,
})
}
fn held_listener() -> Result<(HeldStart, ServerListener), Box<dyn std::error::Error>> {
let held = held_start_supervisor()?;
let listener = ServerListener::bind(&config()?, held.supervisor.clone())?;
held.entered.recv_timeout(WITHIN)?;
Ok((held, listener))
}
fn stops_within<T: Send + 'static>(
stop: impl FnOnce() -> Result<T, ServerError> + Send + 'static,
) -> (bool, std::thread::JoinHandle<Result<T, ServerError>>) {
let (done, stopped) = channel();
let worker = std::thread::spawn(move || {
let result = stop();
let _sent = done.send(());
result
});
let returned = !matches!(stopped.recv_timeout(WITHIN), Err(RecvTimeoutError::Timeout));
(returned, worker)
}
fn write_ping(stream: &mut TcpStream) -> TestResult {
let frame = Frame::Ping { flags: 0 };
let mut bytes = vec![0_u8; encoded_len(&frame)?];
let written = encode(&frame, &mut bytes)?;
stream.write_all(
bytes
.get(..written)
.ok_or("encoded frame length was invalid")?,
)?;
Ok(())
}
fn read_frame(stream: &mut TcpStream) -> Result<Option<Frame>, Box<dyn std::error::Error>> {
let mut buffer = Vec::new();
loop {
match decode(&buffer) {
Ok((frame, _)) => return Ok(Some(frame)),
Err(
ProtocolError::IncompleteHeader { .. } | ProtocolError::TruncatedPayload { .. },
) => {
let mut chunk = [0_u8; 1024];
let read = stream.read(&mut chunk)?;
if read == 0 {
return if buffer.is_empty() {
Ok(None)
} else {
Err("the server closed inside a frame".into())
};
}
buffer.extend_from_slice(chunk.get(..read).ok_or("invalid read length")?);
}
Err(error) => return Err(error.into()),
}
}
}
fn client(address: SocketAddr) -> Result<TcpStream, Box<dyn std::error::Error>> {
let stream = TcpStream::connect(address)?;
stream.set_read_timeout(Some(WITHIN))?;
Ok(stream)
}
fn holds(held: &HeldStart, count: usize) -> TestResult {
if held.supervisor.inner.held.wait_held(count, WITHIN) {
Ok(())
} else {
Err(format!("the supervisor did not hold {count} client(s)").into())
}
}
fn served(stream: &mut TcpStream) -> Result<bool, Box<dyn std::error::Error>> {
write_ping(stream)?;
Ok(matches!(read_frame(stream)?, Some(Frame::Pong { .. })))
}
#[test]
fn stopping_the_listener_does_not_wait_on_the_start_and_the_held_client_is_served() -> TestResult {
let (held, mut listener) = held_listener()?;
let mut first = client(listener.local_addr())?;
holds(&held, 1)?;
let (returned, stopping) = stops_within(move || {
listener.stop_accepting()?;
Ok(listener)
});
held.release.send(true)?;
let _listener = stopping.join().map_err(|_| "the stop panicked")??;
assert!(
returned,
"stop_accepting waited on the incarnation start behind a client it accepted"
);
assert!(
served(&mut first)?,
"the held client is served once the start finishes"
);
held.supervisor.shutdown();
Ok(())
}
#[test]
fn held_clients_are_spawned_in_the_order_they_arrived() -> TestResult {
let (held, mut listener) = held_listener()?;
let mut first = client(listener.local_addr())?;
let mut second = client(listener.local_addr())?;
holds(&held, 2)?;
let (returned, stopping) = stops_within(move || {
listener.stop_accepting()?;
Ok(listener)
});
held.release.send(true)?;
let _listener = stopping.join().map_err(|_| "the stop panicked")??;
assert!(returned, "stop_accepting waited on the incarnation start");
assert!(served(&mut first)? && served(&mut second)?);
let mut spawned = held.supervisor.inner.runtime.active_connections();
spawned.sort_by_key(|connection| connection.pid);
let peers: Vec<_> = spawned
.iter()
.map(|connection| connection.peer_addr)
.collect();
assert_eq!(
peers,
vec![Some(first.local_addr()?), Some(second.local_addr()?)],
"held connections are spawned in arrival order"
);
held.supervisor.shutdown();
Ok(())
}
#[test]
fn a_failed_start_refuses_the_held_client_with_its_error() -> TestResult {
let (held, mut listener) = held_listener()?;
let mut first = client(listener.local_addr())?;
holds(&held, 1)?;
let (returned, stopping) = stops_within(move || {
listener.stop_accepting()?;
Ok(listener)
});
held.release.send(false)?;
let _listener = stopping.join().map_err(|_| "the stop panicked")??;
assert!(returned, "stop_accepting waited on the incarnation start");
let refusal = read_frame(&mut first)?;
assert!(
matches!(refusal, Some(Frame::ConnectError { .. })),
"the held client reads the start's refusal: {refusal:?}"
);
assert!(read_frame(&mut first)?.is_none(), "and the socket closes");
held.supervisor.shutdown();
Ok(())
}
#[test]
fn the_supervisor_shutdown_closes_a_held_client_without_spawning_it() -> TestResult {
let (held, mut listener) = held_listener()?;
let mut first = client(listener.local_addr())?;
holds(&held, 1)?;
let supervisor = held.supervisor.clone();
let (returned, stopping) = stops_within(move || {
listener.stop_accepting()?;
supervisor.shutdown();
Ok(listener)
});
let closed = if returned {
read_frame(&mut first).map(|frame| frame.is_none())
} else {
Ok(false)
};
held.release.send(true)?;
let _listener = stopping.join().map_err(|_| "the stop panicked")??;
assert!(returned, "the stop waited on the incarnation start");
assert!(
closed?,
"the held client is closed by the shutdown, before the start finishes"
);
assert_eq!(held.supervisor.active_connection_count(), 0);
Ok(())
}
#[test]
fn stopping_the_websocket_listener_does_not_wait_on_the_start() -> TestResult {
let held = held_start_supervisor()?;
let mut listener = WebSocketListener::bind(
&WebSocketConfig {
listen_address: "127.0.0.1:0".parse()?,
path: "/liminal".to_owned(),
allowed_origins: Vec::new(),
allowed_origin_ports: Vec::new(),
ping_interval_ms: None,
},
held.supervisor.clone(),
)?;
held.supervisor.listening()?;
held.entered.recv_timeout(WITHIN)?;
let address = listener.local_addr();
let stream = TcpStream::connect(address)?;
stream.set_read_timeout(Some(WITHIN))?;
let (mut socket, _) = tungstenite::client::client(format!("ws://{address}/liminal"), stream)?;
let (returned, stopping) = stops_within(move || {
listener.stop_accepting()?;
Ok(listener)
});
held.release.send(true)?;
let _listener = stopping.join().map_err(|_| "the stop panicked")??;
assert!(
returned,
"the websocket stop waited on the incarnation start behind an upgraded client"
);
let mut bytes = vec![0_u8; encoded_len(&Frame::Ping { flags: 0 })?];
let written = encode(&Frame::Ping { flags: 0 }, &mut bytes)?;
bytes.truncate(written);
socket.send(tungstenite::Message::Binary(bytes.into()))?;
let tungstenite::Message::Binary(reply) = socket.read()? else {
return Err("the held websocket client got no binary reply".into());
};
assert!(matches!(decode(&reply)?.0, Frame::Pong { .. }));
held.supervisor.shutdown();
Ok(())
}