fix(proto): Report time threshold losses correctly in qlog (#827)

Fixes #825. Same fix as quinn-rs/quinn#2896, merged.

Made with [Cursor](https://cursor.com)
This commit is contained in:
t0ms
2026-10-01 17:21:33 +00:00
committed by GitHub
parent 89ed00c312
commit 475b142259
2 changed files with 103 additions and 1 deletions
+1 -1
View File
@@ -212,7 +212,7 @@ impl QlogSink {
}),
frames: None,
trigger: Some(
match info.time_sent.saturating_duration_since(now) >= loss_delay {
match now.saturating_duration_since(info.time_sent) >= loss_delay {
true => PacketLostTrigger::TimeThreshold,
false => PacketLostTrigger::ReorderingThreshold,
},
+102
View File
@@ -5135,3 +5135,105 @@ fn sent_packets_stats_snapshot_test() {
assert_eq!(client_stats2.sent_packets, 14);
assert_eq!(server_stats2.sent_packets, 11);
}
#[cfg(feature = "qlog")]
#[test]
fn qlog_packet_lost_trigger() {
use std::{
io::{self, Write},
sync::Mutex,
};
use qlog::{
events::{EventData, quic::PacketLostTrigger},
reader::{Event as QlogEvent, QlogSeqReader},
};
use crate::{ConnectionId, QlogConfig, QlogFactory, Side};
/// Captures the qlog trace of every connection into one buffer
#[derive(Clone, Default)]
struct SharedBuffer(Arc<Mutex<Vec<u8>>>);
impl QlogFactory for SharedBuffer {
fn for_connection(
&self,
_side: Side,
_remote: SocketAddr,
_initial_dst_cid: ConnectionId,
_now: Instant,
) -> Option<QlogConfig> {
Some(QlogConfig::new(Box::new(self.clone())))
}
}
impl Write for SharedBuffer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
let _guard = subscribe();
let qlog = SharedBuffer::default();
let mut transport = TransportConfig::default();
transport
.deterministic_packet_numbers(true)
.qlog_factory(Arc::new(qlog.clone()));
let mut config = client_config();
config.transport_config(Arc::new(transport));
let mut pair = Pair::default();
let (client_ch, _) = pair.connect_with(config);
pair.drive();
// Drop a packet, then deliver fewer later packets than the packet threshold, so that only the
// time threshold can declare it lost
pair.client_conn_mut(client_ch).ping();
pair.client.drive(pair.time);
assert_eq!(pair.client.outbound.len(), 1);
pair.client.outbound.clear();
pair.client_conn_mut(client_ch).ping();
pair.drive();
// Drop a packet, then deliver as many later packets as the packet threshold without advancing
// time, so that only the packet threshold can declare it lost
pair.client_conn_mut(client_ch).ping();
pair.client.drive(pair.time);
assert_eq!(pair.client.outbound.len(), 1);
pair.client.outbound.clear();
for _ in 0..3 {
pair.client_conn_mut(client_ch).ping();
pair.client.drive(pair.time);
}
assert_eq!(pair.client.outbound.len(), 3);
pair.drive();
assert_eq!(
pair.client_conn_mut(client_ch)
.path_stats(PathId::ZERO)
.unwrap()
.lost_packets,
2
);
let triggers = QlogSeqReader::new(Box::new(&qlog.0.lock().unwrap()[..]))
.unwrap()
.filter_map(|event| match event {
QlogEvent::Qlog(event) => match event.data {
EventData::QuicPacketLost(lost) => lost.trigger,
_ => None,
},
QlogEvent::Json(_) => None,
})
.collect::<Vec<_>>();
assert_eq!(
triggers,
[
PacketLostTrigger::TimeThreshold,
PacketLostTrigger::ReorderingThreshold
]
);
}