In quic loss detection tuner, do not give feedback if a connection 1) has no packets lost, 2) would still have no packets lost if the reordering_shift increased by one. protected by existing flag --gfe2_reloadable_flag_quic_enable_loss_detection_experiment_at_gfe.

PiperOrigin-RevId: 312830024
Change-Id: I76bc158d505792c31bf99fe040c7958418517173
diff --git a/quic/core/congestion_control/general_loss_algorithm.cc b/quic/core/congestion_control/general_loss_algorithm.cc
index f00045e..eb0ed77 100644
--- a/quic/core/congestion_control/general_loss_algorithm.cc
+++ b/quic/core/congestion_control/general_loss_algorithm.cc
@@ -56,7 +56,7 @@
   QuicTime::Delta max_rtt =
       std::max(rtt_stats.previous_srtt(), rtt_stats.latest_rtt());
   max_rtt = std::max(kAlarmGranularity, max_rtt);
-  QuicTime::Delta loss_delay = max_rtt + (max_rtt >> reordering_shift_);
+  const QuicTime::Delta loss_delay = max_rtt + (max_rtt >> reordering_shift_);
   QuicPacketNumber packet_number = unacked_packets.GetLeastUnacked();
   auto it = unacked_packets.begin();
   if (least_in_flight_.IsInitialized() && least_in_flight_ >= packet_number) {
@@ -106,6 +106,10 @@
     // Time threshold loss detection.
     QuicTime when_lost = it->sent_time + loss_delay;
     if (time < when_lost) {
+      if (time >=
+          it->sent_time + max_rtt + (max_rtt >> (reordering_shift_ + 1))) {
+        ++detection_stats.sent_packets_num_borderline_time_reorderings;
+      }
       loss_detection_timeout_ = when_lost;
       if (!least_in_flight_.IsInitialized()) {
         // At this point, packet_number is in flight and not detected as lost.
diff --git a/quic/core/congestion_control/general_loss_algorithm_test.cc b/quic/core/congestion_control/general_loss_algorithm_test.cc
index 1a58ade..be33c25 100644
--- a/quic/core/congestion_control/general_loss_algorithm_test.cc
+++ b/quic/core/congestion_control/general_loss_algorithm_test.cc
@@ -62,14 +62,17 @@
                     const AckedPacketVector& packets_acked,
                     const std::vector<uint64_t>& losses_expected) {
     return VerifyLosses(largest_newly_acked, packets_acked, losses_expected,
+                        quiche::QuicheOptional<QuicPacketCount>(),
                         quiche::QuicheOptional<QuicPacketCount>());
   }
 
-  void VerifyLosses(uint64_t largest_newly_acked,
-                    const AckedPacketVector& packets_acked,
-                    const std::vector<uint64_t>& losses_expected,
-                    quiche::QuicheOptional<QuicPacketCount>
-                        max_sequence_reordering_expected) {
+  void VerifyLosses(
+      uint64_t largest_newly_acked,
+      const AckedPacketVector& packets_acked,
+      const std::vector<uint64_t>& losses_expected,
+      quiche::QuicheOptional<QuicPacketCount> max_sequence_reordering_expected,
+      quiche::QuicheOptional<QuicPacketCount>
+          num_borderline_time_reorderings_expected) {
     unacked_packets_.MaybeUpdateLargestAckedOfPacketNumberSpace(
         APPLICATION_DATA, QuicPacketNumber(largest_newly_acked));
     LostPacketVector lost_packets;
@@ -80,6 +83,10 @@
       EXPECT_EQ(stats.sent_packets_max_sequence_reordering,
                 max_sequence_reordering_expected.value());
     }
+    if (num_borderline_time_reorderings_expected.has_value()) {
+      EXPECT_EQ(stats.sent_packets_num_borderline_time_reorderings,
+                num_borderline_time_reorderings_expected.value());
+    }
     ASSERT_EQ(losses_expected.size(), lost_packets.size());
     for (size_t i = 0; i < losses_expected.size(); ++i) {
       EXPECT_EQ(lost_packets[i].packet_number,
@@ -104,19 +111,19 @@
   unacked_packets_.RemoveFromInFlight(QuicPacketNumber(2));
   packets_acked.push_back(AckedPacket(
       QuicPacketNumber(2), kMaxOutgoingPacketSize, QuicTime::Zero()));
-  VerifyLosses(2, packets_acked, std::vector<uint64_t>{}, 1);
+  VerifyLosses(2, packets_acked, std::vector<uint64_t>{}, 1, 0);
   packets_acked.clear();
   // No loss on two acks.
   unacked_packets_.RemoveFromInFlight(QuicPacketNumber(3));
   packets_acked.push_back(AckedPacket(
       QuicPacketNumber(3), kMaxOutgoingPacketSize, QuicTime::Zero()));
-  VerifyLosses(3, packets_acked, std::vector<uint64_t>{}, 2);
+  VerifyLosses(3, packets_acked, std::vector<uint64_t>{}, 2, 0);
   packets_acked.clear();
   // Loss on three acks.
   unacked_packets_.RemoveFromInFlight(QuicPacketNumber(4));
   packets_acked.push_back(AckedPacket(
       QuicPacketNumber(4), kMaxOutgoingPacketSize, QuicTime::Zero()));
-  VerifyLosses(4, packets_acked, {1}, 3);
+  VerifyLosses(4, packets_acked, {1}, 3, 0);
   EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
 }
 
@@ -176,7 +183,12 @@
   EXPECT_EQ(clock_.Now() + 1.25 * rtt_stats_.smoothed_rtt(),
             loss_algorithm_.GetLossTimeout());
 
-  clock_.AdvanceTime(1.25 * rtt_stats_.latest_rtt());
+  clock_.AdvanceTime(1.13 * rtt_stats_.latest_rtt());
+  // If reordering_shift increases by one we should have detected a loss.
+  VerifyLosses(2, packets_acked, {}, /*max_sequence_reordering_expected=*/1,
+               /*num_borderline_time_reorderings_expected=*/1);
+
+  clock_.AdvanceTime(0.13 * rtt_stats_.latest_rtt());
   VerifyLosses(2, packets_acked, {1});
   EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
 }
diff --git a/quic/core/congestion_control/loss_detection_interface.h b/quic/core/congestion_control/loss_detection_interface.h
index 8d91976..cd2bd72 100644
--- a/quic/core/congestion_control/loss_detection_interface.h
+++ b/quic/core/congestion_control/loss_detection_interface.h
@@ -29,6 +29,7 @@
   struct QUIC_NO_EXPORT DetectionStats {
     // Maximum sequence reordering observed in newly acked packets.
     QuicPacketCount sent_packets_max_sequence_reordering = 0;
+    QuicPacketCount sent_packets_num_borderline_time_reorderings = 0;
   };
 
   // Called when a new ack arrives or the loss alarm fires.
diff --git a/quic/core/congestion_control/uber_loss_algorithm.cc b/quic/core/congestion_control/uber_loss_algorithm.cc
index 0f294d6..6fd23d6 100644
--- a/quic/core/congestion_control/uber_loss_algorithm.cc
+++ b/quic/core/congestion_control/uber_loss_algorithm.cc
@@ -54,6 +54,8 @@
     overall_stats.sent_packets_max_sequence_reordering =
         std::max(overall_stats.sent_packets_max_sequence_reordering,
                  stats.sent_packets_max_sequence_reordering);
+    overall_stats.sent_packets_num_borderline_time_reorderings +=
+        stats.sent_packets_num_borderline_time_reorderings;
   }
 
   return overall_stats;
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h
index 0aee970..397936b 100644
--- a/quic/core/quic_connection_stats.h
+++ b/quic/core/quic_connection_stats.h
@@ -104,6 +104,9 @@
 
   // Maximum sequence reordering observed from acked packets.
   QuicPacketCount sent_packets_max_sequence_reordering = 0;
+  // Number of times that a packet is not detected as lost per reordering_shift,
+  // but would have been if the reordering_shift increases by one.
+  QuicPacketCount sent_packets_num_borderline_time_reorderings = 0;
 
   // The following stats are used only in TcpCubicSender.
   // The number of loss events from TCP's perspective.  Each loss event includes
diff --git a/quic/core/quic_sent_packet_manager.cc b/quic/core/quic_sent_packet_manager.cc
index 7d32176..3941a3e 100644
--- a/quic/core/quic_sent_packet_manager.cc
+++ b/quic/core/quic_sent_packet_manager.cc
@@ -972,6 +972,9 @@
         detection_stats.sent_packets_max_sequence_reordering;
   }
 
+  stats_->sent_packets_num_borderline_time_reorderings +=
+      detection_stats.sent_packets_num_borderline_time_reorderings;
+
   for (const LostPacket& packet : packets_lost_) {
     QuicTransmissionInfo* info =
         unacked_packets_.GetMutableTransmissionInfo(packet.packet_number);