Repository: Tencent/tquic (language: rust)

Issue:
Set path init rtt when receive PATH_RESPONSE frame, get path rtt earlier.



Discussion / hints:

Fix patch:
diff --git a/src/connection/path.rs b/src/connection/path.rs
index c5238f63..b36f013a 100644
--- a/src/connection/path.rs
+++ b/src/connection/path.rs
@@ -19,6 +19,7 @@ use std::net::IpAddr;
 use std::net::SocketAddr;
 use std::time;
 use std::time::Duration;
+use std::time::Instant;
 
 use slab::Slab;
 
@@ -70,7 +71,7 @@ pub struct Path {
     recv_chals: VecDeque<[u8; 8]>,
 
     /// Pending challenge data with the size of the packet containing them.
-    sent_chals: VecDeque<([u8; 8], usize, time::Instant)>,
+    sent_chals: VecDeque<([u8; 8], usize, time::Instant, time::Instant)>,
 
     /// Whether it requires sending PATH_CHALLENGE?
     need_send_challenge: bool,
@@ -190,9 +191,14 @@ impl Path {
 
         // The 4-tuple is reachable, but we didn't check Path MTU yet.
         let mut challenge_size = 0;
-        self.sent_chals.retain(|(d, s, _)| {
+        self.sent_chals.retain(|(d, s, sent_time, _)| {
             if *d == data {
                 challenge_size = *s;
+                // Use the delay between sending a PATH_CHALLENGE and receiving a PATH_RESPONSE
+                // to set the initial RTT for the new path. This delay should not be considered
+                // an RTT sample.
+                let initial_rtt = Instant::now().duration_since(*sent_time);
+                self.recovery.rtt.try_set_init_rtt(initial_rtt);
                 false
             } else {
                 true
@@ -243,7 +249,8 @@ impl Path {
         let loss_time =
             sent_time + time::Duration::from_millis(INITIAL_CHAL_TIMEOUT << self.lost_chal);
 
-        self.sent_chals.push_back((data, pkt_size, loss_time));
+        self.sent_chals
+            .push_back((data, pkt_size, sent_time, loss_time));
     }
 
     /// Handle timeout of PATH_CHALLENGE
@@ -254,7 +261,7 @@ impl Path {
 
         // Remove the lost challenges.
         while let Some(first_chal) = self.sent_chals.front() {
-            if first_chal.2 > now {
+            if first_chal.3 > now {
                 return;
             }
 
@@ -563,8 +570,8 @@ impl PathMap {
         self.paths
             .iter()
             .filter_map(|(_, p)| p.sent_chals.front())
-            .min_by_key(|&(_, _, loss_time)| loss_time)
-            .map(|&(_, _, loss_time)| loss_time)
+            .min_by_key(|&(_, _, _, loss_time)| loss_time)
+            .map(|&(_, _, _, loss_time)| loss_time)
     }
 
     /// Return the maximum PTO among all paths.
diff --git a/src/connection/rtt.rs b/src/connection/rtt.rs
index 5716d3d9..bd2e1112 100644
--- a/src/connection/rtt.rs
+++ b/src/connection/rtt.rs
@@ -81,6 +81,27 @@ impl RttEstimator {
         self.smoothed_rtt() + cmp::max(4 * self.rttvar, TIMER_GRANULARITY)
     }
 
+    /// Try to set the initial RTT if no RTT samples have been updated.
+    ///
+    /// The API is generally used in the following scenarios:
+    /// - A connection MAY use the delay between sending a PATH_CHALLENGE and
+    ///   receiving a PATH_RESPONSE to set the initial RTT for a new path, but the
+    ///   delay SHOULD NOT be considered an RTT sample.
+    /// - Resumed connections over the same network MAY use the previous connection's
+    ///   final smoothed RTT value as the resumed connection's initial RTT.
+    pub fn try_set_init_rtt(&mut self, init_rtt: Duration) {
+        // Only set init_rtt when no rtt sample.
+        if self.smoothed_rtt.is_some() {
+            return;
+        }
+
+        self.latest_rtt = init_rtt;
+        self.smoothed_rtt = None;
+        self.rttvar = init_rtt / 2;
+        self.min_rtt = init_rtt;
+        self.max_rtt = init_rtt;
+    }
+
     /// Update estimator with the given RTT sample
     pub fn update(&mut self, ack_delay: Duration, rtt: Duration) {
         self.latest_rtt = rtt;
@@ -135,6 +156,15 @@ mod tests {
         let initial_rtt = time::Duration::from_millis(200);
         let mut r = RttEstimator::new(initial_rtt);
 
+        // Try set init_rtt before get RTT sample
+        let initial_rtt_before_sample = time::Duration::from_millis(100);
+        r.try_set_init_rtt(initial_rtt_before_sample);
+        assert_eq!(r.latest_rtt(), initial_rtt_before_sample);
+        assert_eq!(r.min_rtt(), initial_rtt_before_sample);
+        assert_eq!(r.max_rtt(), initial_rtt_before_sample);
+        assert_eq!(r.rttvar(), initial_rtt_before_sample / 2);
+        assert_eq!(r.smoothed_rtt(), initial_rtt_before_sample);
+
         // First rtt sample
         let rtt_sample = time::Duration::from_millis(400);
         let ack_delay = time::Duration::from_millis(100);
@@ -146,6 +176,15 @@ mod tests {
         assert_eq!(r.smoothed_rtt(), rtt_sample);
         assert_eq!(r.pto_base(), rtt_sample * 3);
 
+        // Try set init_rtt after get RTT sample
+        let initial_rtt_after_sample = time::Duration::from_millis(300);
+        r.try_set_init_rtt(initial_rtt_after_sample);
+        assert_eq!(r.latest_rtt(), rtt_sample);
+        assert_eq!(r.min_rtt(), rtt_sample);
+        assert_eq!(r.max_rtt(), rtt_sample);
+        assert_eq!(r.rttvar(), rtt_sample / 2);
+        assert_eq!(r.smoothed_rtt(), rtt_sample);
+
         // Second rtt sample
         let rtt_sample = time::Duration::from_millis(700);
         let ack_delay = time::Duration::from_millis(100);


Test patch: