mod common;
use std::time::Duration;
use common::{Server, raw};
use tokio::sync::mpsc;
use weida::{GuaranteeSet, Limits, OrderingMode, RuntimeConfig, TransferMeta};
use weida_protocol::{DataHeader, FrameKind, Hello, encode_frame};
const DEADLINE: Duration = Duration::from_secs(15);
async fn within<F: Future>(f: F) -> F::Output {
tokio::time::timeout(DEADLINE, f)
.await
.expect("operation timed out")
}
fn reassemble() -> GuaranteeSet {
GuaranteeSet {
ordering: OrderingMode::PerProducerReassemble,
..GuaranteeSet::CORE
}
}
fn config(max_reorder_hold: usize) -> RuntimeConfig {
RuntimeConfig {
guarantees: reassemble(),
limits: Limits {
max_reorder_hold,
..Limits::default()
},
..RuntimeConfig::default()
}
}
async fn send_numbered(conn: &quinn::Connection, path: &str, sequence: u64) {
let mut header = DataHeader::addressed(path);
header.sequence = Some(sequence);
let mut stream = conn.open_uni().await.expect("open uni");
stream
.write_all(&encode_frame(FrameKind::Data, &header.encode()))
.await
.expect("write header");
stream
.write_all(&sequence.to_le_bytes())
.await
.expect("write body");
stream.finish().expect("finish");
}
async fn declaring_peer(server: &Server) -> quinn::Connection {
let endpoint = raw::client_endpoint(&server.certs);
let conn = within(endpoint.connect(server.addr, "127.0.0.1").expect("connect"))
.await
.expect("handshake");
let hello = Hello {
guarantees_offered: Some(reassemble()),
guarantees_required: Some(reassemble()),
..Hello::v0(16 * 1024, 1024)
};
raw::send_frame(&conn, FrameKind::Hello, &hello.encode()).await;
std::mem::forget(endpoint);
conn
}
#[tokio::test]
async fn scrambled_arrivals_are_delivered_in_sequence_order() {
let server = Server::start_with_config(config(256)).await;
let puller = server.listener.puller("/jobs").expect("puller");
let conn = declaring_peer(&server).await;
for sequence in [0u64, 3, 2, 1, 4] {
send_numbered(&conn, "/jobs", sequence).await;
}
let mut arrivals = Vec::new();
for _ in 0..5 {
let transfer = within(puller.recv()).await.expect("recv");
let sequence = transfer.meta().sequence.expect("numbered");
assert_eq!(
transfer.meta().gap,
None,
"every hole was filled before release"
);
let body = within(transfer.collect(64)).await.expect("collect");
assert_eq!(
u64::from_le_bytes(body[..].try_into().expect("8 bytes")),
sequence,
"the held stream and its header must stay together"
);
arrivals.push(sequence);
}
assert_eq!(arrivals, vec![0, 1, 2, 3, 4]);
}
#[tokio::test]
async fn a_full_hold_releases_out_of_order_and_reports_the_gap() {
let server = Server::start_with_config(config(2)).await;
let puller = server.listener.puller("/jobs").expect("puller");
let conn = declaring_peer(&server).await;
for sequence in [0u64, 2, 3, 4] {
send_numbered(&conn, "/jobs", sequence).await;
}
let first = within(puller.recv()).await.expect("recv 0");
assert_eq!(first.meta().sequence, Some(0));
assert_eq!(first.meta().gap, None);
let forced = within(puller.recv()).await.expect("recv 2");
assert_eq!(forced.meta().sequence, Some(2));
assert_eq!(forced.meta().gap.expect("a gap").missed(), 1);
for expected in [3u64, 4] {
let transfer = within(puller.recv()).await.expect("recv");
assert_eq!(transfer.meta().sequence, Some(expected));
assert_eq!(
transfer.meta().gap,
None,
"only the forced release skipped anything"
);
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Finish {
Batched,
Sequenced,
}
async fn reverse_finish_case(n: usize, finish: Finish) {
let server = Server::start_with_config(RuntimeConfig {
endpoint_queue: n + 1,
..config(256)
})
.await;
let puller = server.listener.puller("/reorder").expect("puller");
let client = server.client_runtime_with_config(config(256));
let pusher = client.pusher(server.trust());
within(pusher.connect(&server.url("/reorder")))
.await
.expect("connect");
let (tx, mut rx) = mpsc::unbounded_channel();
let reader = tokio::spawn(async move {
for position in 0..n {
let transfer = puller.recv().await.expect("recv");
let sequence = transfer.meta().sequence.expect("numbered");
let tx = tx.clone();
tokio::spawn(async move {
let body = transfer.collect(64).await.expect("collect");
let payload = u64::from_le_bytes(body[..].try_into().expect("8 bytes"));
let _ = tx.send((position, sequence, payload));
});
}
puller
});
let mut transfers = Vec::with_capacity(n);
for seq in 0..n as u64 {
let mut transfer = within(pusher.open(TransferMeta::default()))
.await
.expect("open");
within(transfer.write_all(&seq.to_le_bytes()))
.await
.expect("write");
transfers.push(transfer);
}
for transfer in transfers.into_iter().rev() {
let delivery = transfer.finish().expect("finish");
if finish == Finish::Sequenced {
within(delivery.delivered()).await.expect("delivered");
}
}
let mut seen = vec![false; n];
for _ in 0..n {
let (position, sequence, payload) = within(rx.recv()).await.expect("arrival");
assert_eq!(
sequence, position as u64,
"reassemble mode must release in sequence order"
);
assert_eq!(payload, sequence, "each stream kept its own header");
assert!(
!std::mem::replace(&mut seen[sequence as usize], true),
"each transfer arrives exactly once"
);
}
let _puller = within(reader).await.expect("reader task");
client.shutdown().await;
}
#[tokio::test]
async fn reverse_order_completion_is_released_in_sequence_order() {
reverse_finish_case(16, Finish::Batched).await;
reverse_finish_case(256, Finish::Batched).await;
reverse_finish_case(16, Finish::Sequenced).await;
}