Two waits3e1748aacleft, and the door it named. - The relay: session::request gave a request stream ten seconds (the SETUP timeout) for its first message and dropped it. QUIC opens every stream below the highest a packet has named (RFC 9000 §3.2), so a request whose first packet was lost stood open and empty at the relay from when a later request arrived, and was answered by nothing once QUIC sent it again. It is now held until it comes or the session ends, as a data stream's header is since3e1748aac: no bytes, one of max_requests. - The client: should a request's wait stretch with the path, as the object wait now does? Yes, by the client's statement of its path, not by a guess of the relay's. A request and its response are each recovered at QUIC's probe timeout when dropped, and a session's first requests (a participant's PUBLISHes) go out before any round trip is measured, when that timeout starts near a second and doubles with each probe dropped: ten seconds covers three in a row, and one run on a very lossy path ended "PUBLISH receipts: no response in time". ClientOptions::request_wait (default STEP, ten seconds) is the wait of a request, a FETCH's stream and reads and a subgroup's write; a session over a path that drops packets sets it. joule-moq-room's participants set Impairment::request_wait: ten seconds and two round trips of the path, and LOSSY_WAIT_MS (a minute) where the path drops datagrams, so a lossy session's request waits as long as its objects. A clean path keeps ten seconds, which says soon that a relay does not answer. SETUP keeps STEP: the relay gives a session that long. - wai dev --impair wrote hold_ms = 2000 for each door, which3e1748aacshowed refuses objects whose receipts QUIC is still recovering. It now writes LOSSY_DOOR_HOLD_MS = 60000 when its relay has the participants' QUIC binding (two seconds otherwise), held by a test in joule-moq-room to LOSSY_WAIT_MS, which wai-rs does not build on. tests/request_wait.rs (a 300 ms wait gives up on an answer the relay sends at 1.5 s, a 20 s wait has it; a request stream left empty twelve seconds behind an answered one is answered); unit tests of the participants' wait and of wai dev's relay configuration. Co-Authored-By: Claude Fable 5.2 <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
80 lines
4.7 KiB
Rust
80 lines
4.7 KiB
Rust
//! How long a client's request waits is the client's to state (`ClientOptions::request_wait`).
|
|
//! A relay holds a SUBSCRIBE of a track nobody publishes for the RENDEZVOUS_TIMEOUT it carries
|
|
//! before it answers; a session that waits less gives up with the answer still to come, and one
|
|
//! that waits longer has the relay's answer. The default is the client's ten seconds.
|
|
//!
|
|
//! This is the wait a session over a path that drops packets stretches
|
|
//! (`joule-moq-room`'s synthetic participants): there the answer is late because the request
|
|
//! or its response was dropped and QUIC is recovering it.
|
|
//!
|
|
//! And the relay's half: a request stream whose first message comes late is still answered.
|
|
//! QUIC opens every stream below the highest one a packet has named (RFC 9000 §3.2), so a
|
|
//! request whose first packet was lost is open at the relay, empty, from the moment a later
|
|
//! request arrives. The relay gave such a stream ten seconds and dropped it. Here the delay
|
|
//! is the client's own: it opens two request streams, writes the second, and writes the
|
|
//! first twelve seconds later.
|
|
|
|
mod common;
|
|
|
|
use common::*;
|
|
use joule_moq::draft22::{ControlMessage, NamespaceRequest, Parameter, Parameters};
|
|
use joule_moq_relay::wire::MessageReader;
|
|
use joule_moq_relay::client::{self, ClientOptions};
|
|
use std::time::{Duration, Instant};
|
|
use tokio::time::timeout;
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
async fn a_request_waits_as_long_as_its_session_states() {
|
|
timeout(Duration::from_secs(60), async {
|
|
assert_eq!(ClientOptions::default().request_wait, client::STEP);
|
|
let server = relay(|_| {}).await;
|
|
// The relay answers after 1.5 s, that nobody publishes the track.
|
|
let late = || Parameters::new().with(Parameter::RendezvousTimeout(1500));
|
|
let (brief, _e1) = connect(&server, Via::Quic, ClientOptions { request_wait: Duration::from_millis(300), ..ClientOptions::default() }).await;
|
|
let began = Instant::now();
|
|
let Err(gave_up) = brief.subscribe(ns(&["nobody"]), name("here"), late()).await else { panic!("no answer within the session's wait") };
|
|
assert_eq!(gave_up, "no response in time");
|
|
assert!(began.elapsed() >= Duration::from_millis(300) && began.elapsed() < Duration::from_millis(1400), "it waited what it stated: {:?}", began.elapsed());
|
|
|
|
let (patient, _e2) = connect(&server, Via::Quic, ClientOptions { request_wait: Duration::from_secs(20), ..ClientOptions::default() }).await;
|
|
let began = Instant::now();
|
|
let Err(answered) = patient.subscribe(ns(&["nobody"]), name("here"), late()).await else { panic!("the relay refuses a track nobody publishes") };
|
|
assert!(answered.starts_with("REQUEST_ERROR"), "the relay's own answer: {answered}");
|
|
assert!(began.elapsed() >= Duration::from_millis(1400), "which came after the rendezvous wait: {:?}", began.elapsed());
|
|
|
|
for c in [&brief, &patient] {
|
|
c.close(0, "done");
|
|
}
|
|
server.shutdown().await;
|
|
})
|
|
.await
|
|
.expect("the test finishes in time");
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
async fn a_request_whose_first_message_comes_late_is_answered() {
|
|
timeout(Duration::from_secs(120), async {
|
|
let server = relay(|_| {}).await;
|
|
let (c, _e) = connect(&server, Via::Quic, ClientOptions::default()).await;
|
|
let request = |prefix: &str| ControlMessage::PublishNamespace(NamespaceRequest { request_id: c.next_request_id(), prefix: ns(&[prefix]), params: Parameters::new() }).encode().expect("a request");
|
|
// Two request streams, in the order of their IDs. Nothing of the first is sent yet;
|
|
// the second's bytes open both at the relay.
|
|
let (mut first_tx, first_rx) = c.conn.open_bi().await.expect("a stream");
|
|
let (mut second_tx, second_rx) = c.conn.open_bi().await.expect("a stream");
|
|
second_tx.write_all(&request("early")).await.expect("written");
|
|
let mut second = MessageReader::new(second_rx);
|
|
assert!(matches!(timeout(STEP, second.next()).await, Ok(Ok(Some(ControlMessage::RequestOk(_))))), "the second request is answered");
|
|
// Longer than the ten seconds the relay waited.
|
|
tokio::time::sleep(Duration::from_secs(12)).await;
|
|
first_tx.write_all(&request("late")).await.expect("the relay has not dropped the stream");
|
|
let mut first = MessageReader::new(first_rx);
|
|
match timeout(STEP, first.next()).await {
|
|
Ok(Ok(Some(ControlMessage::RequestOk(_)))) => {}
|
|
other => panic!("the late request is answered as any: {other:?}"),
|
|
}
|
|
c.close(0, "done");
|
|
server.shutdown().await;
|
|
})
|
|
.await
|
|
.expect("the test finishes in time");
|
|
}
|