Log QUIC_BUG if connection sends multiple connection closes.

PiperOrigin-RevId: 437010467
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 6234489..17f4b7d 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -4537,6 +4537,16 @@
     auto* frame = new QuicConnectionCloseFrame(
         transport_version(), error, ietf_error, details,
         framer_.current_received_frame_type());
+    if (level == ENCRYPTION_FORWARD_SECURE) {
+      if (connection_close_frame_sent_.has_value()) {
+        QUIC_BUG(quic_send_multiple_connection_closes)
+            << ENDPOINT << "Already sent connection close: "
+            << connection_close_frame_sent_.value()
+            << ", going to send connection close: " << *frame;
+      } else {
+        connection_close_frame_sent_ = *frame;
+      }
+    }
     packet_creator_.ConsumeRetransmittableControlFrame(QuicFrame(frame));
     packet_creator_.FlushCurrentPacket();
   }
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index ec39ff1..a0738e5 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -2255,6 +2255,10 @@
   const bool flush_after_coalesce_higher_space_packets_ =
       GetQuicReloadableFlag(quic_flush_after_coalesce_higher_space_packets);
 
+  // Records the 1-RTT connection close frame sent.
+  // TODO(b/180103273): remove this after the bug gets fixed.
+  absl::optional<QuicConnectionCloseFrame> connection_close_frame_sent_;
+
   // TODO(b/205023946) Debug-only fields, to be deprecated after the bug is
   // fixed.
   absl::optional<QuicWallTime> quic_bug_10511_43_timestamp_;
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 9fe34d3..6a943c5 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -15699,6 +15699,38 @@
   EXPECT_EQ(1u, writer_->stream_frames().size());
 }
 
+// Regression test for b/180103273
+TEST_P(QuicConnectionTest, SendMultipleConnectionCloses) {
+  if (!version().HasIetfQuicFrames() ||
+      !GetQuicReloadableFlag(quic_default_enable_5rto_blackhole_detection2)) {
+    return;
+  }
+  set_perspective(Perspective::IS_SERVER);
+  // Finish handshake.
+  QuicConnectionPeer::SetAddressValidated(&connection_);
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  notifier_.NeuterUnencryptedData();
+  connection_.NeuterUnencryptedPackets();
+  connection_.OnHandshakeComplete();
+  connection_.RemoveEncrypter(ENCRYPTION_INITIAL);
+  connection_.RemoveEncrypter(ENCRYPTION_HANDSHAKE);
+  EXPECT_CALL(visitor_, GetHandshakeState())
+      .WillRepeatedly(Return(HANDSHAKE_COMPLETE));
+
+  SendStreamDataToPeer(1, "foo", 0, NO_FIN, nullptr);
+  ASSERT_TRUE(connection_.BlackholeDetectionInProgress());
+  // Verify BeforeConnectionCloseSent gets called twice while OnConnectionClosed
+  // is called once.
+  EXPECT_CALL(visitor_, BeforeConnectionCloseSent()).Times(2);
+  EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
+  // Send connection close w/o closing connection.
+  QuicConnectionPeer::SendConnectionClosePacket(
+      &connection_, INTERNAL_ERROR, QUIC_INTERNAL_ERROR, "internal error");
+  // Fire blackhole detection alarm.
+  EXPECT_QUIC_BUG(connection_.GetBlackholeDetectorAlarm()->Fire(),
+                  "Already sent connection close");
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic