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,