mod common;
use std::time::Duration;
use common::Server;
use weida::{Identity, Limits, Runtime, RuntimeConfig, TransferMeta, Trust};
const DEADLINE: Duration = Duration::from_secs(15);
const PAYLOAD: usize = 512 * 1024;
async fn within<F: Future>(f: F) -> F::Output {
tokio::time::timeout(DEADLINE, f)
.await
.expect("operation timed out")
}
fn narrow() -> RuntimeConfig {
RuntimeConfig {
limits: Limits {
stream_receive_window: 64 * 1024,
connection_receive_window: 128 * 1024,
..Limits::default()
},
..RuntimeConfig::default()
}
}
#[tokio::test]
async fn a_finished_transfer_that_shutdown_cuts_short_arrives_under_drain() {
let server = Server::start_with_config(narrow()).await;
let puller = server.listener.puller("/slow").expect("puller");
let reader = tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(200)).await;
let transfer = puller.recv().await.expect("recv");
transfer.collect(PAYLOAD).await
});
let client = server.client_runtime_with_config(narrow());
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/slow")))
.await
.expect("connect");
let mut transfer = within(pusher.open(TransferMeta::default()))
.await
.expect("open");
within(transfer.write_all(&vec![7u8; PAYLOAD]))
.await
.expect("write");
drop(transfer.finish().expect("finish"));
drop(pusher);
let drained = within(client.drain(Duration::from_secs(5))).await;
assert_eq!(
drained.outstanding, 0,
"the drain must have waited for the transfer: {drained:?}"
);
assert_eq!(drained.delivered, 1);
let body = within(reader).await.expect("reader task").expect("collect");
assert_eq!(body.len(), PAYLOAD, "the whole payload must have arrived");
assert!(body.iter().all(|byte| *byte == 7));
}
#[tokio::test]
async fn the_same_transfer_is_cut_short_by_shutdown() {
let server = Server::start_with_config(narrow()).await;
let puller = server.listener.puller("/slow").expect("puller");
let reader = tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(200)).await;
let transfer = puller.recv().await.expect("recv");
transfer.collect(PAYLOAD).await
});
let client = server.client_runtime_with_config(narrow());
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/slow")))
.await
.expect("connect");
let mut transfer = within(pusher.open(TransferMeta::default()))
.await
.expect("open");
within(transfer.write_all(&vec![7u8; PAYLOAD]))
.await
.expect("write");
drop(transfer.finish().expect("finish"));
drop(pusher);
within(client.shutdown()).await;
let body = within(reader).await.expect("reader task");
if let Ok(bytes) = body {
assert!(
bytes.len() < PAYLOAD,
"shutdown must not deliver the whole payload, got {} bytes",
bytes.len()
);
}
}
#[tokio::test]
async fn a_drain_against_a_peer_that_reads_nothing_expires_with_a_count() {
const FREEZE: Duration = Duration::from_secs(2);
let identity = Identity::generate().expect("identity");
let fingerprint = identity.fingerprint().expect("fingerprint");
let (addr_tx, addr_rx) = std::sync::mpsc::channel();
let (freeze_tx, freeze_rx) = std::sync::mpsc::channel::<()>();
let peer = std::thread::spawn(move || {
let reactor = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("peer reactor");
reactor.block_on(async move {
let runtime = Runtime::new(narrow()).expect("peer runtime");
let listener = runtime.listener();
let binding = listener
.bind_quic("127.0.0.1:0".parse().expect("loopback"), identity)
.await
.expect("bind");
let _puller = listener.puller("/frozen").expect("puller");
addr_tx
.send(binding.local_addr())
.expect("hand over the port");
let _ = tokio::task::spawn_blocking(move || freeze_rx.recv()).await;
std::thread::sleep(FREEZE);
});
});
let addr = addr_rx.recv().expect("peer port");
let url = format!("weida://127.0.0.1:{}/frozen", addr.port());
let client = Runtime::new(narrow()).expect("client runtime");
let pusher = client.pusher(Trust::pin(fingerprint));
within(pusher.connect(&url)).await.expect("connect");
let mut transfer = within(pusher.open(TransferMeta::default()))
.await
.expect("open");
within(transfer.write_all(b"a payload nobody will confirm"))
.await
.expect("write");
freeze_tx.send(()).expect("freeze the peer");
tokio::time::sleep(Duration::from_millis(100)).await;
drop(transfer.finish().expect("finish"));
drop(pusher);
let started = std::time::Instant::now();
let drained = within(client.drain(Duration::from_millis(200))).await;
assert_eq!(
drained.outstanding, 1,
"the unacknowledged transfer must be counted: {drained:?}"
);
assert_eq!(drained.delivered, 0);
assert!(
started.elapsed() >= Duration::from_millis(200),
"the drain must have waited for its deadline"
);
peer.join().expect("peer thread");
}
struct Draining {
runtime: Runtime,
url: String,
peer: std::thread::JoinHandle<()>,
_pullers: Vec<weida::Puller>,
_binding: weida::Binding,
}
impl Draining {
async fn start() -> Draining {
let frozen_identity = Identity::generate().expect("frozen identity");
let frozen_fp = frozen_identity.fingerprint().expect("fingerprint");
let (addr_tx, addr_rx) = std::sync::mpsc::channel();
let (freeze_tx, freeze_rx) = std::sync::mpsc::channel::<()>();
let peer = std::thread::spawn(move || {
let reactor = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("peer reactor");
reactor.block_on(async move {
let runtime = Runtime::new(RuntimeConfig::default()).expect("peer runtime");
let listener = runtime.listener();
let binding = listener
.bind_quic("127.0.0.1:0".parse().expect("loopback"), frozen_identity)
.await
.expect("bind");
let _puller = listener.puller("/frozen").expect("puller");
addr_tx
.send(binding.local_addr())
.expect("hand over the port");
let _ = tokio::task::spawn_blocking(move || freeze_rx.recv()).await;
std::thread::sleep(Duration::from_secs(5));
});
});
let identity = Identity::generate().expect("identity");
let fingerprint = identity.fingerprint().expect("fingerprint");
let runtime = Runtime::new(RuntimeConfig::default()).expect("runtime");
let listener = runtime.listener();
let binding = listener
.bind_quic("127.0.0.1:0".parse().expect("loopback"), identity)
.await
.expect("bind");
let pullers = vec![
listener.puller("/early").expect("puller"),
listener.puller("/late").expect("puller"),
];
let url = format!(
"weida://{}@127.0.0.1:{}",
fingerprint,
binding.local_addr().port()
);
let frozen_addr = addr_rx.recv().expect("peer port");
let pusher = runtime.pusher(Trust::pin(frozen_fp));
let frozen_url = format!("weida://127.0.0.1:{}/frozen", frozen_addr.port());
within(pusher.connect(&frozen_url)).await.expect("connect");
let mut transfer = within(pusher.open(TransferMeta::default()))
.await
.expect("open");
within(transfer.write_all(b"nobody will confirm this"))
.await
.expect("write");
freeze_tx.send(()).expect("freeze the peer");
tokio::time::sleep(Duration::from_millis(100)).await;
drop(transfer.finish().expect("finish"));
drop(pusher);
Draining {
runtime,
url,
peer,
_pullers: pullers,
_binding: binding,
}
}
async fn begin(&self, deadline: Duration) -> tokio::task::JoinHandle<weida::Drained> {
let runtime = self.runtime.clone();
let mut drain = Box::pin(runtime.drain(deadline));
let first =
std::future::poll_fn(|cx| std::task::Poll::Ready(drain.as_mut().poll(cx))).await;
assert!(
first.is_pending(),
"a drain with a stalled receipt outstanding cannot finish on its first poll"
);
tokio::spawn(drain)
}
}
#[tokio::test]
async fn a_draining_binding_refuses_a_new_connection() {
let server = Draining::start().await;
let client = Runtime::new(RuntimeConfig::default()).expect("client");
let pusher = client.pusher(Trust::by_address());
within(pusher.connect(&format!("{}/early", server.url)))
.await
.expect("a dial before the drain must be accepted");
let drain = server.begin(Duration::from_secs(2)).await;
let err = within(pusher.connect(&format!("{}/late", server.url)))
.await
.expect_err("a dial during the drain must be refused");
assert!(
!drain.is_finished(),
"the refusal must have been observed while the drain was still running"
);
assert_eq!(
pusher.peer_count(),
1,
"only the early path connected: {err:?}"
);
let drained = within(drain).await.expect("drain task");
assert_eq!(drained.outstanding, 1, "{drained:?}");
client.shutdown().await;
server.peer.join().expect("peer thread");
}
#[tokio::test]
async fn a_draining_runtime_refuses_a_late_stream_on_an_open_connection() {
let server = Draining::start().await;
let client = Runtime::new(RuntimeConfig::default()).expect("client");
let pusher = client.pusher(Trust::by_address());
within(pusher.connect(&format!("{}/early", server.url)))
.await
.expect("connect");
let mut accepted = within(pusher.open(TransferMeta::default()))
.await
.expect("open");
within(accepted.write_all(b"in time")).await.expect("write");
within(accepted.finish().expect("finish").delivered())
.await
.expect("a transfer before the drain is acknowledged");
let drain = server.begin(Duration::from_secs(2)).await;
let mut late = within(pusher.open(TransferMeta::default()))
.await
.expect("the stream budget is local, so opening still succeeds");
let past_the_window = vec![0x7au8; 2 * 1024 * 1024];
let err = match within(late.write_all(&past_the_window)).await {
Err(e) => e,
Ok(()) => match late.finish() {
Ok(receipt) => within(receipt.delivered())
.await
.expect_err("a late transfer must be refused"),
Err(e) => e,
},
};
assert!(
matches!(err, weida::Error::Rejected),
"STOP_SENDING(SHUTDOWN) must reach the sender as a refusal, got {err:?}"
);
assert!(!drain.is_finished(), "the drain was still running");
let drained = within(drain).await.expect("drain task");
assert_eq!(drained.outstanding, 1, "{drained:?}");
client.shutdown().await;
server.peer.join().expect("peer thread");
}
#[tokio::test]
async fn a_drain_closes_a_local_connection_the_way_shutdown_does() {
let harness = common::Harness::start(common::Transport::Inproc).await;
let puller = harness.listener.puller("/jobs").expect("puller");
let client = harness.client();
let pusher = client.pusher(harness.trust());
within(pusher.connect(&harness.url("/jobs")))
.await
.expect("connect");
within(pusher.send(b"before the drain"))
.await
.expect("send");
assert_eq!(
within(within(puller.recv()).await.expect("recv").collect(64))
.await
.expect("collect"),
b"before the drain"
);
let drained = within(client.clone().drain(Duration::from_secs(2))).await;
assert_eq!(
drained.outstanding, 0,
"nothing was left in flight: {drained:?}"
);
let after = within(pusher.send(b"after the drain"))
.await
.expect_err("a drained runtime has no live connections left");
assert!(
after.is_definite_failure(),
"a closed connection is a definite failure, got {after:?}"
);
harness.shutdown().await;
}
#[tokio::test]
async fn a_fire_and_forget_producer_never_fills_the_parked_receipt_set() {
const MESSAGES: usize = 20_000;
let server = Server::start().await;
let puller = server.listener.puller("/jobs").expect("puller");
let draining = tokio::spawn(async move {
let mut seen = 0usize;
while let Ok(transfer) = puller.recv().await {
if transfer.collect(64).await.is_err() {
break;
}
seen += 1;
}
seen
});
let client = server.client_runtime();
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/jobs")))
.await
.expect("connect");
let started = std::time::Instant::now();
for _ in 0..MESSAGES {
within(pusher.send(b"job")).await.expect("send");
}
let elapsed = started.elapsed();
let drained = within(client.clone().drain(Duration::from_secs(5))).await;
println!(
"B-249: {MESSAGES} fire-and-forget sends in {elapsed:?} ({:.1} Kmsg/s), \
{} delivered, {} outstanding",
MESSAGES as f64 / elapsed.as_secs_f64() / 1000.0,
drained.delivered,
drained.outstanding
);
assert_eq!(
drained.outstanding, 0,
"a receipt evicted at the cap counts as outstanding, and none was: {drained:?}"
);
assert!(
drained.delivered > 0,
"the drain waited on parked receipts and they settled, so the peer really did \
acknowledge: {drained:?}"
);
draining.abort();
server.runtime.shutdown().await;
}