Factor the QuicDispatcher's blocked writer list into a stand-alone
QuicBlockedWriterList.
PiperOrigin-RevId: 627519607
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 83c2a51..a28d283 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -275,6 +275,7 @@
"quic/core/quic_arena_scoped_ptr.h",
"quic/core/quic_bandwidth.h",
"quic/core/quic_blocked_writer_interface.h",
+ "quic/core/quic_blocked_writer_list.h",
"quic/core/quic_buffered_packet_store.h",
"quic/core/quic_chaos_protector.h",
"quic/core/quic_clock.h",
@@ -606,6 +607,7 @@
"quic/core/quic_ack_listener_interface.cc",
"quic/core/quic_alarm.cc",
"quic/core/quic_bandwidth.cc",
+ "quic/core/quic_blocked_writer_list.cc",
"quic/core/quic_buffered_packet_store.cc",
"quic/core/quic_chaos_protector.cc",
"quic/core/quic_coalesced_packet.cc",
@@ -1239,6 +1241,7 @@
"quic/core/quic_alarm_test.cc",
"quic/core/quic_arena_scoped_ptr_test.cc",
"quic/core/quic_bandwidth_test.cc",
+ "quic/core/quic_blocked_writer_list_test.cc",
"quic/core/quic_buffered_packet_store_test.cc",
"quic/core/quic_chaos_protector_test.cc",
"quic/core/quic_coalesced_packet_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 70587e1..5abdeab 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -275,6 +275,7 @@
"src/quiche/quic/core/quic_arena_scoped_ptr.h",
"src/quiche/quic/core/quic_bandwidth.h",
"src/quiche/quic/core/quic_blocked_writer_interface.h",
+ "src/quiche/quic/core/quic_blocked_writer_list.h",
"src/quiche/quic/core/quic_buffered_packet_store.h",
"src/quiche/quic/core/quic_chaos_protector.h",
"src/quiche/quic/core/quic_clock.h",
@@ -606,6 +607,7 @@
"src/quiche/quic/core/quic_ack_listener_interface.cc",
"src/quiche/quic/core/quic_alarm.cc",
"src/quiche/quic/core/quic_bandwidth.cc",
+ "src/quiche/quic/core/quic_blocked_writer_list.cc",
"src/quiche/quic/core/quic_buffered_packet_store.cc",
"src/quiche/quic/core/quic_chaos_protector.cc",
"src/quiche/quic/core/quic_coalesced_packet.cc",
@@ -1240,6 +1242,7 @@
"src/quiche/quic/core/quic_alarm_test.cc",
"src/quiche/quic/core/quic_arena_scoped_ptr_test.cc",
"src/quiche/quic/core/quic_bandwidth_test.cc",
+ "src/quiche/quic/core/quic_blocked_writer_list_test.cc",
"src/quiche/quic/core/quic_buffered_packet_store_test.cc",
"src/quiche/quic/core/quic_chaos_protector_test.cc",
"src/quiche/quic/core/quic_coalesced_packet_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 191c426..38d2b75 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -274,6 +274,7 @@
"quiche/quic/core/quic_arena_scoped_ptr.h",
"quiche/quic/core/quic_bandwidth.h",
"quiche/quic/core/quic_blocked_writer_interface.h",
+ "quiche/quic/core/quic_blocked_writer_list.h",
"quiche/quic/core/quic_buffered_packet_store.h",
"quiche/quic/core/quic_chaos_protector.h",
"quiche/quic/core/quic_clock.h",
@@ -605,6 +606,7 @@
"quiche/quic/core/quic_ack_listener_interface.cc",
"quiche/quic/core/quic_alarm.cc",
"quiche/quic/core/quic_bandwidth.cc",
+ "quiche/quic/core/quic_blocked_writer_list.cc",
"quiche/quic/core/quic_buffered_packet_store.cc",
"quiche/quic/core/quic_chaos_protector.cc",
"quiche/quic/core/quic_coalesced_packet.cc",
@@ -1239,6 +1241,7 @@
"quiche/quic/core/quic_alarm_test.cc",
"quiche/quic/core/quic_arena_scoped_ptr_test.cc",
"quiche/quic/core/quic_bandwidth_test.cc",
+ "quiche/quic/core/quic_blocked_writer_list_test.cc",
"quiche/quic/core/quic_buffered_packet_store_test.cc",
"quiche/quic/core/quic_chaos_protector_test.cc",
"quiche/quic/core/quic_coalesced_packet_test.cc",
diff --git a/quiche/quic/core/quic_blocked_writer_list.cc b/quiche/quic/core/quic_blocked_writer_list.cc
new file mode 100644
index 0000000..105d8b9
--- /dev/null
+++ b/quiche/quic/core/quic_blocked_writer_list.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/quic_blocked_writer_list.h"
+
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_flag_utils.h"
+
+namespace quic {
+
+void QuicBlockedWriterList::Add(QuicBlockedWriterInterface& blocked_writer) {
+ if (!blocked_writer.IsWriterBlocked()) {
+ // It is a programming error if this ever happens. When we are sure it is
+ // not happening, replace it with a QUICHE_DCHECK.
+ QUIC_BUG(quic_bug_12724_4)
+ << "Tried to add writer into blocked list when it shouldn't be added";
+ // Return without adding the connection to the blocked list, to avoid
+ // infinite loops in OnCanWrite.
+ return;
+ }
+
+ write_blocked_list_.insert(std::make_pair(&blocked_writer, true));
+}
+
+bool QuicBlockedWriterList::Empty() const {
+ return write_blocked_list_.empty();
+}
+
+bool QuicBlockedWriterList::Remove(QuicBlockedWriterInterface& blocked_writer) {
+ return write_blocked_list_.erase(&blocked_writer) != 0;
+}
+
+void QuicBlockedWriterList::OnWriterUnblocked() {
+ // Move every blocked writer in |write_blocked_list_| to a temporary list.
+ const size_t num_blocked_writers_before = write_blocked_list_.size();
+ WriteBlockedList temp_list;
+ temp_list.swap(write_blocked_list_);
+ QUICHE_DCHECK(write_blocked_list_.empty());
+
+ // Give each blocked writer a chance to write what they intended to write.
+ // If they are blocked again, they will call |OnWriteBlocked| to add
+ // themselves back into |write_blocked_list_|.
+ while (!temp_list.empty()) {
+ QuicBlockedWriterInterface* blocked_writer = temp_list.begin()->first;
+ temp_list.erase(temp_list.begin());
+ blocked_writer->OnBlockedWriterCanWrite();
+ }
+ const size_t num_blocked_writers_after = write_blocked_list_.size();
+ if (num_blocked_writers_after != 0) {
+ if (num_blocked_writers_before == num_blocked_writers_after) {
+ QUIC_CODE_COUNT(quic_zero_progress_on_can_write);
+ } else {
+ QUIC_CODE_COUNT(quic_blocked_again_on_can_write);
+ }
+ }
+}
+
+} // namespace quic
diff --git a/quiche/quic/core/quic_blocked_writer_list.h b/quiche/quic/core/quic_blocked_writer_list.h
new file mode 100644
index 0000000..5cbcb67
--- /dev/null
+++ b/quiche/quic/core/quic_blocked_writer_list.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QUIC_BLOCKED_WRITER_LIST_H_
+#define QUICHE_QUIC_CORE_QUIC_BLOCKED_WRITER_LIST_H_
+
+#include "quiche/quic/core/quic_blocked_writer_interface.h"
+#include "quiche/common/quiche_linked_hash_map.h"
+
+namespace quic {
+
+// Maintains a list of blocked writers which can be resumed when unblocked.
+class QUICHE_EXPORT QuicBlockedWriterList {
+ public:
+ // Adds `blocked_writer` (which must be write blocked) to the list. If
+ // `blocked_writer` is already in the list, this method has no effect.
+ void Add(QuicBlockedWriterInterface& blocked_writer);
+
+ // Returns false if there are any blocked writers.
+ bool Empty() const;
+
+ // Removes `blocked_writer` to the list. Returns true if `blocked_writer`
+ // was in the list and false otherwise.
+ bool Remove(QuicBlockedWriterInterface& blocked_writer);
+
+ // Calls `OnCanWrite()` on all the writers in the list.
+ void OnWriterUnblocked();
+
+ private:
+ // Ideally we'd have a linked_hash_set: the boolean is unused.
+ using WriteBlockedList =
+ quiche::QuicheLinkedHashMap<QuicBlockedWriterInterface*, bool>;
+
+ // The list of writers waiting to write.
+ WriteBlockedList write_blocked_list_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_QUIC_BLOCKED_WRITER_LIST_H_
diff --git a/quiche/quic/core/quic_blocked_writer_list_test.cc b/quiche/quic/core/quic_blocked_writer_list_test.cc
new file mode 100644
index 0000000..5e5531e
--- /dev/null
+++ b/quiche/quic/core/quic_blocked_writer_list_test.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/quic_blocked_writer_list.h"
+
+#include "quiche/quic/core/quic_blocked_writer_interface.h"
+#include "quiche/quic/platform/api/quic_test.h"
+
+namespace quic {
+
+using testing::Invoke;
+using testing::Return;
+
+namespace {
+class TestWriter : public QuicBlockedWriterInterface {
+ public:
+ ~TestWriter() override = default;
+
+ MOCK_METHOD(void, OnBlockedWriterCanWrite, ());
+ MOCK_METHOD(bool, IsWriterBlocked, (), (const));
+};
+} // namespace
+
+TEST(QuicBlockedWriterList, Empty) {
+ QuicBlockedWriterList list;
+ EXPECT_TRUE(list.Empty());
+}
+
+TEST(QuicBlockedWriterList, NotEmpty) {
+ QuicBlockedWriterList list;
+ testing::StrictMock<TestWriter> writer1;
+ EXPECT_CALL(writer1, IsWriterBlocked()).WillOnce(Return(true));
+ list.Add(writer1);
+ EXPECT_FALSE(list.Empty());
+ list.Remove(writer1);
+ EXPECT_TRUE(list.Empty());
+}
+
+TEST(QuicBlockedWriterList, OnWriterUnblocked) {
+ QuicBlockedWriterList list;
+ testing::StrictMock<TestWriter> writer1;
+
+ EXPECT_CALL(writer1, IsWriterBlocked()).WillOnce(Return(true));
+ list.Add(writer1);
+ EXPECT_CALL(writer1, OnBlockedWriterCanWrite());
+ list.OnWriterUnblocked();
+ EXPECT_TRUE(list.Empty());
+}
+
+TEST(QuicBlockedWriterList, OnWriterUnblockedInOrder) {
+ QuicBlockedWriterList list;
+ testing::StrictMock<TestWriter> writer1;
+ testing::StrictMock<TestWriter> writer2;
+ testing::StrictMock<TestWriter> writer3;
+
+ EXPECT_CALL(writer1, IsWriterBlocked()).WillOnce(Return(true));
+ EXPECT_CALL(writer2, IsWriterBlocked()).WillOnce(Return(true));
+ EXPECT_CALL(writer3, IsWriterBlocked()).WillOnce(Return(true));
+
+ list.Add(writer1);
+ list.Add(writer2);
+ list.Add(writer3);
+
+ testing::InSequence s;
+ EXPECT_CALL(writer1, OnBlockedWriterCanWrite());
+ EXPECT_CALL(writer2, OnBlockedWriterCanWrite());
+ EXPECT_CALL(writer3, OnBlockedWriterCanWrite());
+ list.OnWriterUnblocked();
+ EXPECT_TRUE(list.Empty());
+}
+
+TEST(QuicBlockedWriterList, OnWriterUnblockedInOrderAfterReinsertion) {
+ QuicBlockedWriterList list;
+ testing::StrictMock<TestWriter> writer1;
+ testing::StrictMock<TestWriter> writer2;
+ testing::StrictMock<TestWriter> writer3;
+
+ EXPECT_CALL(writer1, IsWriterBlocked()).WillOnce(Return(true));
+ EXPECT_CALL(writer2, IsWriterBlocked()).WillOnce(Return(true));
+ EXPECT_CALL(writer3, IsWriterBlocked()).WillOnce(Return(true));
+
+ list.Add(writer1);
+ list.Add(writer2);
+ list.Add(writer3);
+
+ EXPECT_CALL(writer1, IsWriterBlocked()).WillOnce(Return(true));
+ list.Add(writer1);
+
+ testing::InSequence s;
+ EXPECT_CALL(writer1, OnBlockedWriterCanWrite());
+ EXPECT_CALL(writer2, OnBlockedWriterCanWrite());
+ EXPECT_CALL(writer3, OnBlockedWriterCanWrite());
+ list.OnWriterUnblocked();
+ EXPECT_TRUE(list.Empty());
+}
+
+TEST(QuicBlockedWriterList, OnWriterUnblockedThenBlocked) {
+ QuicBlockedWriterList list;
+ testing::StrictMock<TestWriter> writer1;
+ testing::StrictMock<TestWriter> writer2;
+ testing::StrictMock<TestWriter> writer3;
+
+ EXPECT_CALL(writer1, IsWriterBlocked()).WillOnce(Return(true));
+ EXPECT_CALL(writer2, IsWriterBlocked()).WillOnce(Return(true));
+ EXPECT_CALL(writer3, IsWriterBlocked()).WillOnce(Return(true));
+
+ list.Add(writer1);
+ list.Add(writer2);
+ list.Add(writer3);
+
+ EXPECT_CALL(writer1, OnBlockedWriterCanWrite());
+ EXPECT_CALL(writer2, IsWriterBlocked()).WillOnce(Return(true));
+ EXPECT_CALL(writer2, OnBlockedWriterCanWrite()).WillOnce(Invoke([&]() {
+ list.Add(writer2);
+ }));
+
+ EXPECT_CALL(writer3, OnBlockedWriterCanWrite());
+ list.OnWriterUnblocked();
+ EXPECT_FALSE(list.Empty());
+
+ EXPECT_CALL(writer2, OnBlockedWriterCanWrite());
+ list.OnWriterUnblocked();
+ EXPECT_TRUE(list.Empty());
+}
+
+} // namespace quic
diff --git a/quiche/quic/core/quic_dispatcher.cc b/quiche/quic/core/quic_dispatcher.cc
index 1d371e4..02c69ce 100644
--- a/quiche/quic/core/quic_dispatcher.cc
+++ b/quiche/quic/core/quic_dispatcher.cc
@@ -794,7 +794,7 @@
QuicErrorCode /*error*/,
const std::string& /*error_details*/,
ConnectionCloseSource /*source*/) {
- write_blocked_list_.erase(connection);
+ write_blocked_list_.Remove(*connection);
QuicTimeWaitListManager::TimeWaitAction action =
QuicTimeWaitListManager::SEND_STATELESS_RESET;
if (connection->termination_packets() != nullptr &&
@@ -872,9 +872,9 @@
}
void QuicDispatcher::DeleteSessions() {
- if (!write_blocked_list_.empty()) {
+ if (!write_blocked_list_.Empty()) {
for (const auto& session : closed_session_list_) {
- if (write_blocked_list_.erase(session->connection()) != 0) {
+ if (write_blocked_list_.Remove(*session->connection())) {
QUIC_BUG(quic_bug_12724_2)
<< "QuicConnection was in WriteBlockedList before destruction "
<< session->connection()->connection_id();
@@ -892,32 +892,11 @@
// The socket is now writable.
writer_->SetWritable();
- // Move every blocked writer in |write_blocked_list_| to a temporary list.
- const size_t num_blocked_writers_before = write_blocked_list_.size();
- WriteBlockedList temp_list;
- temp_list.swap(write_blocked_list_);
- QUICHE_DCHECK(write_blocked_list_.empty());
-
- // Give each blocked writer a chance to write what they intended to write.
- // If they are blocked again, they will call |OnWriteBlocked| to add
- // themselves back into |write_blocked_list_|.
- while (!temp_list.empty()) {
- QuicBlockedWriterInterface* blocked_writer = temp_list.begin()->first;
- temp_list.erase(temp_list.begin());
- blocked_writer->OnBlockedWriterCanWrite();
- }
- const size_t num_blocked_writers_after = write_blocked_list_.size();
- if (num_blocked_writers_after != 0) {
- if (num_blocked_writers_before == num_blocked_writers_after) {
- QUIC_CODE_COUNT(quic_zero_progress_on_can_write);
- } else {
- QUIC_CODE_COUNT(quic_blocked_again_on_can_write);
- }
- }
+ write_blocked_list_.OnWriterUnblocked();
}
bool QuicDispatcher::HasPendingWrites() const {
- return !write_blocked_list_.empty();
+ return !write_blocked_list_.Empty();
}
void QuicDispatcher::Shutdown() {
@@ -999,17 +978,7 @@
void QuicDispatcher::OnWriteBlocked(
QuicBlockedWriterInterface* blocked_writer) {
- if (!blocked_writer->IsWriterBlocked()) {
- // It is a programming error if this ever happens. When we are sure it is
- // not happening, replace it with a QUICHE_DCHECK.
- QUIC_BUG(quic_bug_12724_4)
- << "Tried to add writer into blocked list when it shouldn't be added";
- // Return without adding the connection to the blocked list, to avoid
- // infinite loops in OnCanWrite.
- return;
- }
-
- write_blocked_list_.insert(std::make_pair(blocked_writer, true));
+ write_blocked_list_.Add(*blocked_writer);
}
void QuicDispatcher::OnRstStreamReceived(const QuicRstStreamFrame& /*frame*/) {}
diff --git a/quiche/quic/core/quic_dispatcher.h b/quiche/quic/core/quic_dispatcher.h
index d1d0d6f..efe5a2c 100644
--- a/quiche/quic/core/quic_dispatcher.h
+++ b/quiche/quic/core/quic_dispatcher.h
@@ -26,6 +26,7 @@
#include "quiche/quic/core/quic_alarm.h"
#include "quiche/quic/core/quic_alarm_factory.h"
#include "quiche/quic/core/quic_blocked_writer_interface.h"
+#include "quiche/quic/core/quic_blocked_writer_list.h"
#include "quiche/quic/core/quic_buffered_packet_store.h"
#include "quiche/quic/core/quic_connection.h"
#include "quiche/quic/core/quic_connection_id.h"
@@ -58,10 +59,6 @@
public ProcessPacketInterface,
public QuicBufferedPacketStore::VisitorInterface {
public:
- // Ideally we'd have a linked_hash_set: the boolean is unused.
- using WriteBlockedList =
- quiche::QuicheLinkedHashMap<QuicBlockedWriterInterface*, bool>;
-
QuicDispatcher(
const QuicConfig* config, const QuicCryptoServerConfig* crypto_config,
QuicVersionManager* version_manager,
@@ -419,7 +416,7 @@
QuicCompressedCertsCache compressed_certs_cache_;
// The list of connections waiting to write.
- WriteBlockedList write_blocked_list_;
+ QuicBlockedWriterList write_blocked_list_;
ReferenceCountedSessionMap reference_counted_session_map_;
diff --git a/quiche/quic/core/quic_dispatcher_test.cc b/quiche/quic/core/quic_dispatcher_test.cc
index 406bbc8..05e6e69 100644
--- a/quiche/quic/core/quic_dispatcher_test.cc
+++ b/quiche/quic/core/quic_dispatcher_test.cc
@@ -2061,7 +2061,7 @@
MockQuicConnectionHelper helper_;
MockAlarmFactory alarm_factory_;
BlockingWriter* writer_;
- QuicDispatcher::WriteBlockedList* blocked_list_;
+ QuicBlockedWriterList* blocked_list_;
};
INSTANTIATE_TEST_SUITE_P(QuicDispatcherWriteBlockedListTests,
@@ -2108,7 +2108,7 @@
// Add and remove one connction.
SetBlocked();
dispatcher_->OnWriteBlocked(connection1());
- blocked_list_->erase(connection1());
+ blocked_list_->Remove(*connection1());
EXPECT_CALL(*connection1(), OnCanWrite()).Times(0);
dispatcher_->OnCanWrite();
@@ -2116,14 +2116,14 @@
SetBlocked();
dispatcher_->OnWriteBlocked(connection1());
dispatcher_->OnWriteBlocked(connection2());
- blocked_list_->erase(connection1());
+ blocked_list_->Remove(*connection1());
EXPECT_CALL(*connection2(), OnCanWrite());
dispatcher_->OnCanWrite();
// Add it, remove it, and add it back and make sure things are OK.
SetBlocked();
dispatcher_->OnWriteBlocked(connection1());
- blocked_list_->erase(connection1());
+ blocked_list_->Remove(*connection1());
dispatcher_->OnWriteBlocked(connection1());
EXPECT_CALL(*connection1(), OnCanWrite()).Times(1);
dispatcher_->OnCanWrite();
@@ -2134,7 +2134,7 @@
SetBlocked();
dispatcher_->OnWriteBlocked(connection1());
dispatcher_->OnWriteBlocked(connection1());
- blocked_list_->erase(connection1());
+ blocked_list_->Remove(*connection1());
EXPECT_CALL(*connection1(), OnCanWrite()).Times(0);
dispatcher_->OnCanWrite();
diff --git a/quiche/quic/test_tools/quic_dispatcher_peer.cc b/quiche/quic/test_tools/quic_dispatcher_peer.cc
index 8a803c5..f08c2d7 100644
--- a/quiche/quic/test_tools/quic_dispatcher_peer.cc
+++ b/quiche/quic/test_tools/quic_dispatcher_peer.cc
@@ -54,7 +54,7 @@
}
// static
-QuicDispatcher::WriteBlockedList* QuicDispatcherPeer::GetWriteBlockedList(
+QuicBlockedWriterList* QuicDispatcherPeer::GetWriteBlockedList(
QuicDispatcher* dispatcher) {
return &dispatcher->write_blocked_list_;
}
diff --git a/quiche/quic/test_tools/quic_dispatcher_peer.h b/quiche/quic/test_tools/quic_dispatcher_peer.h
index 1238a89..985dc67 100644
--- a/quiche/quic/test_tools/quic_dispatcher_peer.h
+++ b/quiche/quic/test_tools/quic_dispatcher_peer.h
@@ -37,8 +37,7 @@
static QuicAlarmFactory* GetAlarmFactory(QuicDispatcher* dispatcher);
- static QuicDispatcher::WriteBlockedList* GetWriteBlockedList(
- QuicDispatcher* dispatcher);
+ static QuicBlockedWriterList* GetWriteBlockedList(QuicDispatcher* dispatcher);
// Get the dispatcher's record of the last error reported to its framer
// visitor's OnError() method. Then set that record to QUIC_NO_ERROR.