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,