Merge branch 'fix/managed-finite-lifetime-wake'
Rust CI / cargo fmt (push) Successful in 19s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 35s
Rust CI / cargo clippy (push) Successful in 37s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo doc (push) Successful in 23s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m16s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 27s
Supply chain / cargo-deny advisories (push) Successful in 9s
Supply chain / actionlint (push) Successful in 3s
Supply chain / cargo-deny bans licenses sources (push) Successful in 8s
Netem Interop / netem interop (push) Successful in 1m3s

This commit is contained in:
Nils Andreas Svee committed 2026-10-04 15:47:51 +07:00
commit 9c703ecff2
2 files changed
+115

No files matched your search

@@ -48,6 +48,11 @@ impl ProbeSchedule {
self.next_send_at self.next_send_at
} }
/// Wake for finite expiry independently of send readiness until finished.
pub(crate) fn end_deadline(&self) -> Option<Instant> {
self.end_at.filter(|_| !self.is_finished())
}
pub(crate) fn interval(&self) -> Duration { pub(crate) fn interval(&self) -> Duration {
self.interval self.interval
} }
@@ -152,3 +157,42 @@ pub(crate) fn instant_abs_diff(left: Instant, right: Instant) -> Duration {
.or_else(|| right.checked_duration_since(left)) .or_else(|| right.checked_duration_since(left))
.unwrap_or(Duration::ZERO) .unwrap_or(Duration::ZERO)
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lifetime_deadline_exists_only_for_an_unfinished_finite_schedule() {
let start = Instant::now();
let end = start + Duration::from_millis(500);
let mut negotiated = NegotiatedParams {
params: irtt_proto::Params {
duration_ns: 500_000_000,
interval_ns: 1_000_000,
..irtt_proto::Params::default()
},
restrictions: Vec::new(),
};
let mut finite = ProbeSchedule::new(start, &negotiated).unwrap();
assert_eq!(finite.end_deadline(), Some(end));
assert!(finite.permit_probe_at(end - Duration::from_nanos(1)));
assert_eq!(finite.end_deadline(), Some(end));
assert!(!finite.permit_probe_at(end));
assert!(finite.is_finished());
assert_eq!(finite.end_deadline(), None);
let mut finite = ProbeSchedule::new(start, &negotiated).unwrap();
let commit = finite
.preflight_managed_commit(start, end - Duration::from_nanos(1))
.unwrap();
finite.commit(commit);
assert!(finite.is_finished());
assert_eq!(finite.end_deadline(), None);
negotiated.params.duration_ns = 0;
let mut continuous = ProbeSchedule::new(start, &negotiated).unwrap();
assert!(continuous.permit_probe_at(end));
assert_eq!(continuous.end_deadline(), None);
}
}
+71
View File
@@ -1936,6 +1936,12 @@ impl ManagedClientTask {
non_send_deadline = non_send_deadline non_send_deadline = non_send_deadline
.into_iter() .into_iter()
.chain(client.next_probe_timeout_deadline()) .chain(client.next_probe_timeout_deadline())
.chain(
target
.schedule
.as_ref()
.and_then(ProbeSchedule::end_deadline),
)
.min(); .min();
if target.desired && target.retirement.is_none() && !target.send_waiting { if target.desired && target.retirement.is_none() && !target.send_waiting {
send_deadline = send_deadline send_deadline = send_deadline
@@ -2399,6 +2405,71 @@ fn close_timeout_failure() -> ManagedTargetFailure {
mod tests { mod tests {
use super::*; use super::*;
// Real EAGAIN needs privileged queue shaping. Inspect only the owning
// deadline selection here, using a production socket without sending.
#[test]
fn finite_lifetime_deadline_survives_send_waiting_and_stagger_gating() {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async {
let start = Instant::now();
let end = start + Duration::from_millis(500);
let negotiated = crate::NegotiatedParams {
params: irtt_proto::Params {
duration_ns: 500_000_000,
interval_ns: 1_000_000,
..irtt_proto::Params::default()
},
restrictions: Vec::new(),
};
for pacing in [ManagedPacing::Burst, ManagedPacing::Staggered] {
let (mut task, _handle) = ManagedClient::task(
ManagedClientConfig {
pacing,
..ManagedClientConfig::default()
},
vec![ManagedTargetConfig::new("target", "127.0.0.1:9")],
)
.unwrap();
let client = AsyncClient::connect(ClientConfig {
server_addr: "127.0.0.1:9".into(),
..ClientConfig::default()
})
.await
.unwrap();
task.state = DriverState::Running;
task.install_target_state(0, TargetState::Active { client });
task.send_gate = Some(end + Duration::from_secs(1));
for send_waiting in [false, true] {
task.targets[0].send_waiting = send_waiting;
task.targets[0].schedule =
Some(ProbeSchedule::new(start, &negotiated).unwrap());
let expected = if pacing == ManagedPacing::Burst && !send_waiting {
start
} else {
end
};
assert_eq!(task.next_deadline(), Some(expected));
task.targets[0]
.schedule
.as_mut()
.unwrap()
.permit_probe_at(end);
assert_eq!(task.next_deadline(), None);
}
let mut continuous = negotiated.clone();
continuous.params.duration_ns = 0;
task.targets[0].schedule = Some(ProbeSchedule::new(start, &continuous).unwrap());
assert_eq!(task.next_deadline(), None);
}
});
}
#[test] #[test]
fn release_gate_only_delays_an_existing_send_deadline() { fn release_gate_only_delays_an_existing_send_deadline() {
let earlier = Instant::now(); let earlier = Instant::now();