gfe-relnote: In a QUIC connection, if a write error happens after a successful MTU probe, ignore this error and rollback to the previous MTU before the probe. Protected by --gfe2_reloadable_flag_quic_ignore_one_write_error_after_mtu_probe.

PiperOrigin-RevId: 301825026
Change-Id: Ic1ffd0a0f42ddd5f0b3e314ac8e70a90b9c48609
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 31a5eb2..fd5e539 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -309,6 +309,7 @@
       connected_(true),
       can_truncate_connection_ids_(perspective == Perspective::IS_SERVER),
       mtu_probe_count_(0),
+      previous_validated_mtu_(0),
       peer_max_packet_size_(kDefaultMaxPacketSizeTransportParam),
       largest_received_packet_size_(0),
       write_error_occurred_(false),
@@ -2313,13 +2314,25 @@
   }
 
   if (IsWriteError(result.status)) {
-    OnWriteError(result.error_code);
     QUIC_LOG_FIRST_N(ERROR, 10)
         << ENDPOINT << "Failed writing packet " << packet_number << " of "
         << encrypted_length << " bytes from " << self_address().host() << " to "
         << peer_address() << ", with error code " << result.error_code
         << ". long_term_mtu_:" << long_term_mtu_
+        << ", previous_validated_mtu_:" << previous_validated_mtu_
+        << ", max_packet_length():" << max_packet_length()
         << ", is_mtu_discovery:" << is_mtu_discovery;
+    if (previous_validated_mtu_ != 0 &&
+        GetQuicReloadableFlag(quic_ignore_one_write_error_after_mtu_probe)) {
+      QUIC_CODE_COUNT(quic_ignore_one_write_error_after_mtu_probe);
+      SetMaxPacketLength(previous_validated_mtu_);
+      mtu_discoverer_.Disable();
+      mtu_discovery_alarm_->Cancel();
+      previous_validated_mtu_ = 0;
+      return true;
+    }
+
+    OnWriteError(result.error_code);
     return false;
   }
 
@@ -2535,9 +2548,9 @@
 
 void QuicConnection::OnPathMtuIncreased(QuicPacketLength packet_size) {
   if (packet_size > max_packet_length()) {
-    const QuicByteCount old_max_packet_length = max_packet_length();
+    previous_validated_mtu_ = max_packet_length();
     SetMaxPacketLength(packet_size);
-    mtu_discoverer_.OnMaxPacketLengthUpdated(old_max_packet_length,
+    mtu_discoverer_.OnMaxPacketLengthUpdated(previous_validated_mtu_,
                                              max_packet_length());
   }
 }
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index e92c876..f86c52c 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -1434,6 +1434,13 @@
   // The number of MTU probes already sent.
   size_t mtu_probe_count_;
 
+  // The value of |long_term_mtu_| prior to the last successful MTU increase.
+  // 0 means either
+  // - MTU discovery has never been enabled, or
+  // - MTU discovery has been enabled, but the connection got a packet write
+  //   error with a new (successfully probed) MTU, so it reverted
+  //   |long_term_mtu_| to the value before the increase.
+  QuicPacketLength previous_validated_mtu_;
   // The value of the MTU regularly used by the connection. This is different
   // from the value returned by max_packet_size(), as max_packet_size() returns
   // the value of the MTU as currently used by the serializer, so if
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 98c2d57..f73b736 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -1677,6 +1677,9 @@
     // same overhead, either 12 bytes for ones using Google QUIC crypto, or 16
     // bytes for ones using TLS.
     connection_.SetEncrypter(ENCRYPTION_INITIAL, nullptr);
+    // Prevent packets from being coalesced.
+    EXPECT_CALL(visitor_, GetHandshakeState())
+        .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
     EXPECT_TRUE(connection_.connected());
   }
 
@@ -5086,6 +5089,7 @@
   EXPECT_EQ(1u, connection_.mtu_probe_count());
 
   QuicStreamOffset stream_offset = packets_between_probes_base;
+  QuicByteCount last_probe_size = 0;
   for (size_t num_probes = 1; num_probes < kMtuDiscoveryAttempts;
        ++num_probes) {
     // Send just enough packets without triggering the next probe.
@@ -5112,11 +5116,30 @@
     EXPECT_EQ(new_probe_size, connection_.max_packet_length());
     EXPECT_EQ(0u, connection_.GetBytesInFlight());
 
+    last_probe_size = probe_size;
     probe_size = new_probe_size;
   }
 
   // The last probe size should be equal to the target.
   EXPECT_EQ(probe_size, kMtuDiscoveryTargetPacketSizeHigh);
+
+  if (GetQuicReloadableFlag(quic_ignore_one_write_error_after_mtu_probe)) {
+    writer_->SetShouldWriteFail();
+
+    // Ignore PACKET_WRITE_ERROR once.
+    SendStreamDataToPeer(3, "(", stream_offset++, NO_FIN, nullptr);
+    EXPECT_EQ(last_probe_size, connection_.max_packet_length());
+    EXPECT_TRUE(connection_.connected());
+
+    // Close connection on another PACKET_WRITE_ERROR.
+    EXPECT_CALL(visitor_, OnConnectionClosed(_, _))
+        .WillOnce(Invoke(this, &QuicConnectionTest::SaveConnectionCloseFrame));
+    SendStreamDataToPeer(3, ")", stream_offset++, NO_FIN, nullptr);
+    EXPECT_EQ(last_probe_size, connection_.max_packet_length());
+    EXPECT_FALSE(connection_.connected());
+    EXPECT_THAT(saved_connection_close_frame_.quic_error_code,
+                IsError(QUIC_PACKET_WRITE_ERROR));
+  }
 }
 
 // Simulate the case where the first attempt to send a probe is write blocked,