use std::sync::Arc;
use std::time::Duration;
use atomic_websocket::client_sender::ClientSenders;
use atomic_websocket::external::tokio_tungstenite::tungstenite::{Bytes, Message};
use tokio::sync::mpsc;
const CHANNEL_CAPACITY: usize = 8;
async fn wedged_peer(senders: &ClientSenders, peer: &str) -> mpsc::Receiver<Message> {
let (tx, rx) = mpsc::channel(CHANNEL_CAPACITY);
senders.add(peer, tx.clone()).await;
for _ in 0..CHANNEL_CAPACITY {
tx.try_send(Message::Binary(Bytes::from_static(b"backlog")))
.expect("channel should accept up to its capacity");
}
assert_eq!(tx.capacity(), 0, "the channel must be full for this test");
rx
}
#[tokio::test]
async fn replacing_a_wedged_peer_does_not_block() {
let senders = ClientSenders::new();
let _old_rx = wedged_peer(&senders, "tablet-7").await;
let (new_tx, mut new_rx) = mpsc::channel(CHANNEL_CAPACITY);
tokio::time::timeout(Duration::from_secs(1), senders.add("tablet-7", new_tx))
.await
.expect("add() must not wait on a connection that cannot accept messages");
assert!(
senders
.send("tablet-7", Message::Binary(Bytes::from_static(b"hello")))
.await
);
assert!(
new_rx.try_recv().is_ok(),
"the new connection should have received the message"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_peer_being_replaced_does_not_freeze_the_client_table() {
let senders = Arc::new(ClientSenders::new());
let _old_rx = wedged_peer(&senders, "tablet-7").await;
let mut others = Vec::new();
for n in 0..47 {
let (tx, rx) = mpsc::channel(CHANNEL_CAPACITY);
senders.add(&format!("tablet-other-{n}"), tx).await;
others.push(rx);
}
let replacing = {
let senders = Arc::clone(&senders);
tokio::spawn(async move {
let (tx, _rx) = mpsc::channel(CHANNEL_CAPACITY);
senders.add("tablet-7", tx).await;
})
};
tokio::time::sleep(Duration::from_millis(50)).await;
let contended = {
let senders = Arc::clone(&senders);
tokio::task::spawn_blocking(move || senders.remove("tablet-7"))
};
tokio::time::timeout(Duration::from_secs(1), contended)
.await
.expect("the client table must not be locked out by a peer being replaced")
.expect("remove task should not panic");
tokio::time::timeout(Duration::from_secs(1), async { senders.peers() })
.await
.expect("peers() must not queue behind a wedged replacement");
tokio::time::timeout(Duration::from_secs(2), replacing)
.await
.expect("the replacement itself must finish")
.expect("replacement task should not panic");
}
#[tokio::test]
async fn reconnecting_does_not_inflate_the_active_connection_count() {
let senders = ClientSenders::new();
let mut keep_alive = Vec::new();
for _ in 0..5 {
let (tx, rx) = mpsc::channel(CHANNEL_CAPACITY);
senders.add("tablet-7", tx).await;
keep_alive.push(rx);
}
let snapshot = senders.metrics.snapshot();
assert_eq!(
senders.len(),
1,
"five reconnects of one tablet are still one entry"
);
assert_eq!(
snapshot.connections_active, 1,
"…and still one active connection"
);
assert_eq!(
snapshot.connections_total, 5,
"but all five reconnects are counted in the total"
);
}