-
-
Notifications
You must be signed in to change notification settings - Fork 392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: ack receiver timestamps #1992
base: main
Are you sure you want to change the base?
Changes from 17 commits
bdd1115
cb6d5c4
8dc4579
d83b684
3366153
94cb098
76ddf51
a5dae4a
ead292b
c98b93f
3fd8f2e
3af9df4
6c56bc1
9d9ee23
e47ade6
6ac89db
96153e6
ea3bb6a
caa86bd
1e3c936
1cc4043
67af041
55d207d
e85e8f1
64acada
fa004c5
53bcc35
7bee845
8bc98b1
bb8df6b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -34,6 +34,20 @@ pub trait Controller: Send + Sync { | |||||
) { | ||||||
} | ||||||
|
||||||
#[allow(unused_variables)] | ||||||
/// Packet deliveries were confirmed with timestamps information. | ||||||
fn on_ack_packet( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these actually changes you'd look to make to the If so, should this default implementation call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, I did it this way to prevent any breaking changes if there were users of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
&mut self, | ||||||
pn: u64, | ||||||
now: Instant, | ||||||
sent: Instant, | ||||||
received: Option<Instant>, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
bytes: u64, | ||||||
app_limited: bool, | ||||||
rtt: &RttEstimator, | ||||||
) { | ||||||
} | ||||||
|
||||||
/// Packets are acked in batches, all with the same `now` argument. This indicates one of those batches has completed. | ||||||
#[allow(unused_variables)] | ||||||
fn on_end_acks( | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,8 +20,7 @@ use crate::{ | |
coding::BufMutExt, | ||
config::{ServerConfig, TransportConfig}, | ||
crypto::{self, KeyPair, Keys, PacketKey}, | ||
frame, | ||
frame::{Close, Datagram, FrameStruct}, | ||
frame::{self, Close, Datagram, FrameStruct}, | ||
packet::{ | ||
FixedLengthConnectionIdParser, Header, InitialHeader, InitialPacket, LongType, Packet, | ||
PacketNumber, PartialDecode, SpaceId, | ||
|
@@ -37,6 +36,8 @@ use crate::{ | |
VarInt, MAX_STREAM_COUNT, MIN_INITIAL_SIZE, TIMER_GRANULARITY, | ||
}; | ||
|
||
use crate::config::AckTimestampsConfig; | ||
sterlingdeng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
mod ack_frequency; | ||
use ack_frequency::AckFrequencyState; | ||
|
||
|
@@ -65,7 +66,10 @@ use paths::{PathData, PathResponses}; | |
|
||
mod send_buffer; | ||
|
||
mod spaces; | ||
mod receiver_timestamps; | ||
pub(crate) use receiver_timestamps::{PacketTimestamp, ReceiverTimestamps}; | ||
|
||
pub(crate) mod spaces; | ||
#[cfg(fuzzing)] | ||
pub use spaces::Retransmits; | ||
#[cfg(not(fuzzing))] | ||
|
@@ -227,6 +231,11 @@ pub struct Connection { | |
/// no outgoing application data. | ||
app_limited: bool, | ||
|
||
// | ||
// Ack Receive Timestamps | ||
// | ||
peer_ack_timestamp_cfg: Option<AckTimestampsConfig>, | ||
sterlingdeng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
streams: StreamsState, | ||
/// Surplus remote CIDs for future use on new paths | ||
rem_cids: CidQueue, | ||
|
@@ -238,6 +247,8 @@ pub struct Connection { | |
stats: ConnectionStats, | ||
/// QUIC version used for the connection. | ||
version: u32, | ||
/// Created at time instant. | ||
epoch: Instant, | ||
} | ||
|
||
impl Connection { | ||
|
@@ -337,6 +348,8 @@ impl Connection { | |
&TransportParameters::default(), | ||
)), | ||
|
||
peer_ack_timestamp_cfg: None, | ||
|
||
pto_count: 0, | ||
|
||
app_limited: false, | ||
|
@@ -357,6 +370,7 @@ impl Connection { | |
rng, | ||
stats: ConnectionStats::default(), | ||
version, | ||
epoch: now, | ||
}; | ||
if side.is_client() { | ||
// Kick off the connection | ||
|
@@ -817,6 +831,7 @@ impl Connection { | |
&mut self.spaces[space_id], | ||
buf, | ||
&mut self.stats, | ||
None, | ||
); | ||
} | ||
|
||
|
@@ -1343,6 +1358,13 @@ impl Connection { | |
if ack.largest >= self.spaces[space].next_packet_number { | ||
return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); | ||
} | ||
|
||
if ack.timestamps.is_some() != self.config.ack_timestamp_config.is_some() { | ||
return Err(TransportError::PROTOCOL_VIOLATION( | ||
"ack with timestamps expectation mismatched", | ||
)); | ||
} | ||
sterlingdeng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let new_largest = { | ||
let space = &mut self.spaces[space]; | ||
if space | ||
|
@@ -1371,13 +1393,21 @@ impl Connection { | |
} | ||
} | ||
|
||
let mut timestamp_iter = self.config.ack_timestamp_config.as_ref().map(|cfg| { | ||
let decoder = ack.timestamp_iter(cfg.basis, cfg.exponent.0).unwrap(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should at least have a comment about why this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I fixed it so it does a |
||
let mut v: tinyvec::TinyVec<[PacketTimestamp; 10]> = tinyvec::TinyVec::new(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: import There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't seem possible to What I'm essentially trying to do is match a packet number and it's timestamp (if available) together by "zipping" both together. When reading the values from the frame (for both packet number and timestamp), the values are produced from high packet number to low, but when we use them later on in the code, they're used from low to high, hence why I do a reverse. One option to reduce an allocation and avoid the need of However, I don't think it makes sense to modify the existing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay nvm, I think I found a better solution - going to test it and report back.. |
||
decoder.for_each(|elt| v.push(elt)); | ||
v.reverse(); | ||
v.into_iter().peekable() | ||
}); | ||
|
||
if newly_acked.is_empty() { | ||
return Ok(()); | ||
} | ||
|
||
let mut ack_eliciting_acked = false; | ||
for packet in newly_acked.elts() { | ||
if let Some(info) = self.spaces[space].take(packet) { | ||
if let Some(mut info) = self.spaces[space].take(packet) { | ||
if let Some(acked) = info.largest_acked { | ||
// Assume ACKs for all packets below the largest acknowledged in `packet` have | ||
// been received. This can cause the peer to spuriously retransmit if some of | ||
|
@@ -1399,6 +1429,25 @@ impl Connection { | |
// Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame | ||
self.ack_frequency.on_acked(packet); | ||
|
||
if let Some(timestamp_iter) = timestamp_iter.as_mut() { | ||
while let Some(v) = timestamp_iter.peek() { | ||
sterlingdeng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
match v.packet_number.cmp(&packet) { | ||
cmp::Ordering::Less => { | ||
let _ = timestamp_iter.next(); | ||
} | ||
cmp::Ordering::Equal => { | ||
// Unwrap safety is guaranteed because a value was validated | ||
// to exist using peek | ||
let ts = timestamp_iter.next().unwrap(); | ||
info.time_received = Some(ts.timestamp); | ||
} | ||
cmp::Ordering::Greater => { | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
self.on_packet_acked(now, packet, info); | ||
} | ||
} | ||
|
@@ -1497,6 +1546,16 @@ impl Connection { | |
self.app_limited, | ||
&self.path.rtt, | ||
); | ||
|
||
self.path.congestion.on_ack_packet( | ||
pn, | ||
now, | ||
info.time_sent, | ||
info.time_received, | ||
info.size.into(), | ||
self.app_limited, | ||
&self.path.rtt, | ||
); | ||
} | ||
|
||
// Update state for confirmed delivery of frames | ||
|
@@ -2184,8 +2243,10 @@ impl Connection { | |
} | ||
Ok((packet, number)) => { | ||
let span = match number { | ||
Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), | ||
None => trace_span!("recv", space = ?packet.header.space()), | ||
Some(pn) => { | ||
trace_span!("recv", space = ?packet.header.space(), pn, side=?self.side) | ||
} | ||
None => trace_span!("recv", space = ?packet.header.space(), side=?self.side), | ||
}; | ||
let _guard = span.enter(); | ||
|
||
|
@@ -3047,6 +3108,7 @@ impl Connection { | |
space, | ||
buf, | ||
&mut self.stats, | ||
self.peer_ack_timestamp_cfg.clone(), | ||
); | ||
} | ||
|
||
|
@@ -3231,6 +3293,7 @@ impl Connection { | |
space: &mut PacketSpace, | ||
buf: &mut Vec<u8>, | ||
stats: &mut ConnectionStats, | ||
timestamp_config: Option<AckTimestampsConfig>, | ||
) { | ||
debug_assert!(!space.pending_acks.ranges().is_empty()); | ||
|
||
|
@@ -3255,7 +3318,21 @@ impl Connection { | |
delay_micros | ||
); | ||
|
||
frame::Ack::encode(delay as _, space.pending_acks.ranges(), ecn, buf); | ||
frame::Ack::encode( | ||
delay as _, | ||
space.pending_acks.ranges(), | ||
ecn, | ||
timestamp_config.map(|cfg| { | ||
( | ||
space.pending_acks.receiver_timestamps_as_ref().unwrap(), | ||
cfg.basis, | ||
cfg.exponent.0, | ||
cfg.max_timestamps_per_ack.0, | ||
) | ||
}), | ||
buf, | ||
); | ||
|
||
stats.frame_tx.acks += 1; | ||
} | ||
|
||
|
@@ -3309,6 +3386,26 @@ impl Connection { | |
self.path.mtud.on_peer_max_udp_payload_size_received( | ||
u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX), | ||
); | ||
|
||
{ | ||
self.peer_ack_timestamp_cfg = if let (Some(max_timestamps_per_ack), Some(exponent)) = ( | ||
params.max_recv_timestamps_per_ack, | ||
params.receive_timestamps_exponent, | ||
) { | ||
for space in self.spaces.iter_mut() { | ||
space | ||
.pending_acks | ||
.set_receiver_timestamp(max_timestamps_per_ack.0 as usize); | ||
} | ||
Some(AckTimestampsConfig { | ||
exponent, | ||
max_timestamps_per_ack, | ||
basis: self.epoch, | ||
}) | ||
} else { | ||
None | ||
}; | ||
} | ||
sterlingdeng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
fn decrypt_packet( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is public, then the main config struct should have a single config setter that accepts an
Option<AckTimestampsConfig>
. If you want setters for individual fields directly on the main struct, then it's confusing for this to be public.