Provide an EpollServer-based QuicEventLoop implementation and an API to integrate it into QUICHE.
PiperOrigin-RevId: 457981780
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 4f58c6a..e4570e6 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -924,6 +924,7 @@
]
epoll_tool_support_hdrs = [
"common/platform/api/quiche_epoll.h",
+ "common/platform/api/quiche_event_loop.h",
"common/platform/api/quiche_stream_buffer_allocator.h",
"common/platform/api/quiche_udp_socket_platform_api.h",
"epoll_server/platform/api/epoll_bug.h",
@@ -935,6 +936,7 @@
"quic/core/batch_writer/quic_batch_writer_test.h",
"quic/core/batch_writer/quic_gso_batch_writer.h",
"quic/core/batch_writer/quic_sendmmsg_batch_writer.h",
+ "quic/core/io/quic_default_event_loop.h",
"quic/core/io/quic_event_loop.h",
"quic/core/io/quic_poll_event_loop.h",
"quic/core/quic_default_packet_writer.h",
@@ -967,6 +969,7 @@
"quic/core/batch_writer/quic_batch_writer_buffer.cc",
"quic/core/batch_writer/quic_gso_batch_writer.cc",
"quic/core/batch_writer/quic_sendmmsg_batch_writer.cc",
+ "quic/core/io/quic_default_event_loop.cc",
"quic/core/io/quic_poll_event_loop.cc",
"quic/core/quic_default_packet_writer.cc",
"quic/core/quic_epoll_alarm_factory.cc",
@@ -1281,6 +1284,7 @@
"quic/core/http/quic_spdy_client_session_test.cc",
"quic/core/http/quic_spdy_client_stream_test.cc",
"quic/core/http/quic_spdy_server_stream_base_test.cc",
+ "quic/core/io/quic_all_event_loops_test.cc",
"quic/core/io/quic_poll_event_loop_test.cc",
"quic/core/quic_epoll_alarm_factory_test.cc",
"quic/core/quic_epoll_clock_test.cc",
@@ -1399,6 +1403,7 @@
]
default_platform_impl_tool_support_hdrs = [
"common/platform/default/quiche_platform_impl/quiche_command_line_flags_impl.h",
+ "common/platform/default/quiche_platform_impl/quiche_event_loop_impl.h",
"common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h",
"common/platform/default/quiche_platform_impl/quiche_stream_buffer_allocator_impl.h",
"common/platform/default/quiche_platform_impl/quiche_system_event_loop_impl.h",
diff --git a/build/source_list.gni b/build/source_list.gni
index 5cf7491..738fe2c 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -924,6 +924,7 @@
]
epoll_tool_support_hdrs = [
"src/quiche/common/platform/api/quiche_epoll.h",
+ "src/quiche/common/platform/api/quiche_event_loop.h",
"src/quiche/common/platform/api/quiche_stream_buffer_allocator.h",
"src/quiche/common/platform/api/quiche_udp_socket_platform_api.h",
"src/quiche/epoll_server/platform/api/epoll_bug.h",
@@ -935,6 +936,7 @@
"src/quiche/quic/core/batch_writer/quic_batch_writer_test.h",
"src/quiche/quic/core/batch_writer/quic_gso_batch_writer.h",
"src/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.h",
+ "src/quiche/quic/core/io/quic_default_event_loop.h",
"src/quiche/quic/core/io/quic_event_loop.h",
"src/quiche/quic/core/io/quic_poll_event_loop.h",
"src/quiche/quic/core/quic_default_packet_writer.h",
@@ -967,6 +969,7 @@
"src/quiche/quic/core/batch_writer/quic_batch_writer_buffer.cc",
"src/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc",
"src/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.cc",
+ "src/quiche/quic/core/io/quic_default_event_loop.cc",
"src/quiche/quic/core/io/quic_poll_event_loop.cc",
"src/quiche/quic/core/quic_default_packet_writer.cc",
"src/quiche/quic/core/quic_epoll_alarm_factory.cc",
@@ -1281,6 +1284,7 @@
"src/quiche/quic/core/http/quic_spdy_client_session_test.cc",
"src/quiche/quic/core/http/quic_spdy_client_stream_test.cc",
"src/quiche/quic/core/http/quic_spdy_server_stream_base_test.cc",
+ "src/quiche/quic/core/io/quic_all_event_loops_test.cc",
"src/quiche/quic/core/io/quic_poll_event_loop_test.cc",
"src/quiche/quic/core/quic_epoll_alarm_factory_test.cc",
"src/quiche/quic/core/quic_epoll_clock_test.cc",
@@ -1399,6 +1403,7 @@
]
default_platform_impl_tool_support_hdrs = [
"src/quiche/common/platform/default/quiche_platform_impl/quiche_command_line_flags_impl.h",
+ "src/quiche/common/platform/default/quiche_platform_impl/quiche_event_loop_impl.h",
"src/quiche/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h",
"src/quiche/common/platform/default/quiche_platform_impl/quiche_stream_buffer_allocator_impl.h",
"src/quiche/common/platform/default/quiche_platform_impl/quiche_system_event_loop_impl.h",
diff --git a/build/source_list.json b/build/source_list.json
index 0334858..c8c1513 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -923,6 +923,7 @@
],
"epoll_tool_support_hdrs": [
"quiche/common/platform/api/quiche_epoll.h",
+ "quiche/common/platform/api/quiche_event_loop.h",
"quiche/common/platform/api/quiche_stream_buffer_allocator.h",
"quiche/common/platform/api/quiche_udp_socket_platform_api.h",
"quiche/epoll_server/platform/api/epoll_bug.h",
@@ -934,6 +935,7 @@
"quiche/quic/core/batch_writer/quic_batch_writer_test.h",
"quiche/quic/core/batch_writer/quic_gso_batch_writer.h",
"quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.h",
+ "quiche/quic/core/io/quic_default_event_loop.h",
"quiche/quic/core/io/quic_event_loop.h",
"quiche/quic/core/io/quic_poll_event_loop.h",
"quiche/quic/core/quic_default_packet_writer.h",
@@ -966,6 +968,7 @@
"quiche/quic/core/batch_writer/quic_batch_writer_buffer.cc",
"quiche/quic/core/batch_writer/quic_gso_batch_writer.cc",
"quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.cc",
+ "quiche/quic/core/io/quic_default_event_loop.cc",
"quiche/quic/core/io/quic_poll_event_loop.cc",
"quiche/quic/core/quic_default_packet_writer.cc",
"quiche/quic/core/quic_epoll_alarm_factory.cc",
@@ -1280,6 +1283,7 @@
"quiche/quic/core/http/quic_spdy_client_session_test.cc",
"quiche/quic/core/http/quic_spdy_client_stream_test.cc",
"quiche/quic/core/http/quic_spdy_server_stream_base_test.cc",
+ "quiche/quic/core/io/quic_all_event_loops_test.cc",
"quiche/quic/core/io/quic_poll_event_loop_test.cc",
"quiche/quic/core/quic_epoll_alarm_factory_test.cc",
"quiche/quic/core/quic_epoll_clock_test.cc",
@@ -1398,6 +1402,7 @@
],
"default_platform_impl_tool_support_hdrs": [
"quiche/common/platform/default/quiche_platform_impl/quiche_command_line_flags_impl.h",
+ "quiche/common/platform/default/quiche_platform_impl/quiche_event_loop_impl.h",
"quiche/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h",
"quiche/common/platform/default/quiche_platform_impl/quiche_stream_buffer_allocator_impl.h",
"quiche/common/platform/default/quiche_platform_impl/quiche_system_event_loop_impl.h"
diff --git a/quiche/common/platform/api/quiche_event_loop.h b/quiche/common/platform/api/quiche_event_loop.h
new file mode 100644
index 0000000..fd5bde0
--- /dev/null
+++ b/quiche/common/platform/api/quiche_event_loop.h
@@ -0,0 +1,27 @@
+// Copyright 2022 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_COMMON_PLATFORM_API_QUIC_EVENT_LOOP_H_
+#define QUICHE_COMMON_PLATFORM_API_QUIC_EVENT_LOOP_H_
+
+#include "quiche_platform_impl/quiche_event_loop_impl.h"
+
+namespace quic {
+class QuicEventLoopFactory;
+}
+
+namespace quiche {
+
+inline quic::QuicEventLoopFactory* GetOverrideForDefaultEventLoop() {
+ return GetOverrideForDefaultEventLoopImpl();
+}
+
+inline std::vector<quic::QuicEventLoopFactory*>
+GetExtraEventLoopImplementations() {
+ return GetExtraEventLoopImplementationsImpl();
+}
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_PLATFORM_API_QUIC_EVENT_LOOP_H_
diff --git a/quiche/common/platform/default/quiche_platform_impl/quiche_event_loop_impl.h b/quiche/common/platform/default/quiche_platform_impl/quiche_event_loop_impl.h
new file mode 100644
index 0000000..44613ce
--- /dev/null
+++ b/quiche/common/platform/default/quiche_platform_impl/quiche_event_loop_impl.h
@@ -0,0 +1,27 @@
+// Copyright 2022 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_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_EVENT_LOOP_IMPL_H_
+#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_EVENT_LOOP_IMPL_H_
+
+#include <vector>
+
+namespace quic {
+class QuicEventLoopFactory;
+}
+
+namespace quiche {
+
+inline quic::QuicEventLoopFactory* GetOverrideForDefaultEventLoopImpl() {
+ return nullptr;
+}
+
+inline std::vector<quic::QuicEventLoopFactory*>
+GetExtraEventLoopImplementationsImpl() {
+ return {};
+}
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_EVENT_LOOP_IMPL_H_
diff --git a/quiche/quic/core/io/quic_all_event_loops_test.cc b/quiche/quic/core/io/quic_all_event_loops_test.cc
new file mode 100644
index 0000000..19dafee
--- /dev/null
+++ b/quiche/quic/core/io/quic_all_event_loops_test.cc
@@ -0,0 +1,351 @@
+// Copyright 2022 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.
+
+// A universal test for all event loops supported by the build of QUICHE in
+// question.
+//
+// This test is very similar to QuicPollEventLoopTest, however, there are some
+// notable differences:
+// (1) This test uses the real clock, since the event loop implementation may
+// not support accepting a mock clock.
+// (2) This test covers both level-triggered and edge-triggered event loops.
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "absl/memory/memory.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/io/quic_default_event_loop.h"
+#include "quiche/quic/core/io/quic_event_loop.h"
+#include "quiche/quic/core/quic_alarm.h"
+#include "quiche/quic/core/quic_alarm_factory.h"
+#include "quiche/quic/core/quic_default_clock.h"
+#include "quiche/quic/core/quic_time.h"
+#include "quiche/quic/platform/api/quic_test.h"
+
+namespace quic::test {
+namespace {
+
+using testing::_;
+using testing::AtMost;
+
+constexpr QuicSocketEventMask kAllEvents =
+ kSocketEventReadable | kSocketEventWritable | kSocketEventError;
+
+class MockQuicSocketEventListener : public QuicSocketEventListener {
+ public:
+ MOCK_METHOD(void, OnSocketEvent,
+ (QuicEventLoop* /*event_loop*/, QuicUdpSocketFd /*fd*/,
+ QuicSocketEventMask /*events*/),
+ (override));
+};
+
+class MockDelegate : public QuicAlarm::Delegate {
+ public:
+ QuicConnectionContext* GetConnectionContext() override { return nullptr; }
+ MOCK_METHOD(void, OnAlarm, (), (override));
+};
+
+class QuicEventLoopFactoryTest
+ : public QuicTestWithParam<QuicEventLoopFactory*> {
+ public:
+ QuicEventLoopFactoryTest()
+ : loop_(GetParam()->Create(&clock_)), factory_(loop_->GetAlarmFactory()) {
+ int fds[2];
+ int result = ::pipe(fds);
+ QUICHE_CHECK(result >= 0) << "Failed to create a pipe, errno: " << errno;
+ read_fd_ = fds[0];
+ write_fd_ = fds[1];
+
+ QUICHE_CHECK(::fcntl(read_fd_, F_SETFL,
+ ::fcntl(read_fd_, F_GETFL) | O_NONBLOCK) == 0)
+ << "Failed to mark pipe FD non-blocking, errno: " << errno;
+ QUICHE_CHECK(::fcntl(write_fd_, F_SETFL,
+ ::fcntl(write_fd_, F_GETFL) | O_NONBLOCK) == 0)
+ << "Failed to mark pipe FD non-blocking, errno: " << errno;
+ }
+
+ ~QuicEventLoopFactoryTest() {
+ close(read_fd_);
+ close(write_fd_);
+ }
+
+ std::pair<std::unique_ptr<QuicAlarm>, MockDelegate*> CreateAlarm() {
+ auto delegate = std::make_unique<testing::StrictMock<MockDelegate>>();
+ MockDelegate* delegate_unowned = delegate.get();
+ auto alarm = absl::WrapUnique(factory_->CreateAlarm(delegate.release()));
+ return std::make_pair(std::move(alarm), delegate_unowned);
+ }
+
+ template <typename Condition>
+ void RunEventLoopUntil(Condition condition, QuicTime::Delta timeout) {
+ const QuicTime end = clock_.Now() + timeout;
+ while (!condition() && clock_.Now() < end) {
+ loop_->RunEventLoopOnce(end - clock_.Now());
+ }
+ }
+
+ protected:
+ QuicDefaultClock clock_;
+ std::unique_ptr<QuicEventLoop> loop_;
+ QuicAlarmFactory* factory_;
+ int read_fd_;
+ int write_fd_;
+};
+
+std::string GetTestParamName(
+ ::testing::TestParamInfo<QuicEventLoopFactory*> info) {
+ std::string name = info.param->GetName();
+ // Escape all characters that are not allowed by gtest ([a-zA-Z0-9_]).
+ for (char& c : name) {
+ bool valid = absl::ascii_isalnum(c) || c == '_';
+ if (!valid) {
+ c = '_';
+ }
+ }
+ return name;
+}
+
+INSTANTIATE_TEST_SUITE_P(QuicEventLoopFactoryTests, QuicEventLoopFactoryTest,
+ ::testing::ValuesIn(GetAllSupportedEventLoops()),
+ GetTestParamName);
+
+TEST_P(QuicEventLoopFactoryTest, NothingHappens) {
+ testing::StrictMock<MockQuicSocketEventListener> listener;
+ ASSERT_TRUE(loop_->RegisterSocket(read_fd_, kAllEvents, &listener));
+ ASSERT_TRUE(loop_->RegisterSocket(write_fd_, kAllEvents, &listener));
+
+ // Attempt double-registration.
+ EXPECT_FALSE(loop_->RegisterSocket(write_fd_, kAllEvents, &listener));
+
+ EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable));
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(4));
+ // Expect no further calls.
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(5));
+}
+
+TEST_P(QuicEventLoopFactoryTest, RearmWriter) {
+ testing::StrictMock<MockQuicSocketEventListener> listener;
+ ASSERT_TRUE(loop_->RegisterSocket(write_fd_, kAllEvents, &listener));
+
+ if (loop_->SupportsEdgeTriggered()) {
+ EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable))
+ .Times(1);
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+ } else {
+ EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable))
+ .Times(2);
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+ ASSERT_TRUE(loop_->RearmSocket(write_fd_, kSocketEventWritable));
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+ }
+}
+
+TEST_P(QuicEventLoopFactoryTest, Readable) {
+ testing::StrictMock<MockQuicSocketEventListener> listener;
+ ASSERT_TRUE(loop_->RegisterSocket(read_fd_, kAllEvents, &listener));
+
+ ASSERT_EQ(4, write(write_fd_, "test", 4));
+ EXPECT_CALL(listener, OnSocketEvent(_, read_fd_, kSocketEventReadable));
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+ // Expect no further calls.
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+}
+
+// A common pattern: read a limited amount of data from an FD, and expect to
+// read the remainder on the next operation.
+TEST_P(QuicEventLoopFactoryTest, ArtificialNotifyFromCallback) {
+ testing::StrictMock<MockQuicSocketEventListener> listener;
+ ASSERT_TRUE(loop_->RegisterSocket(read_fd_, kSocketEventReadable, &listener));
+
+ constexpr absl::string_view kData = "test test test test test test test ";
+ constexpr size_t kTimes = kData.size() / 5;
+ ASSERT_EQ(kData.size(), write(write_fd_, kData.data(), kData.size()));
+ EXPECT_CALL(listener, OnSocketEvent(_, read_fd_, kSocketEventReadable))
+ .Times(loop_->SupportsEdgeTriggered() ? (kTimes + 1) : kTimes)
+ .WillRepeatedly([&]() {
+ char buf[5];
+ int read_result = read(read_fd_, buf, sizeof(buf));
+ if (read_result > 0) {
+ ASSERT_EQ(read_result, 5);
+ if (loop_->SupportsEdgeTriggered()) {
+ EXPECT_TRUE(
+ loop_->ArtificiallyNotifyEvent(read_fd_, kSocketEventReadable));
+ } else {
+ EXPECT_TRUE(loop_->RearmSocket(read_fd_, kSocketEventReadable));
+ }
+ } else {
+ EXPECT_EQ(errno, EAGAIN);
+ }
+ });
+ for (size_t i = 0; i < kTimes + 2; i++) {
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+ }
+}
+
+TEST_P(QuicEventLoopFactoryTest, WriterUnblocked) {
+ testing::StrictMock<MockQuicSocketEventListener> listener;
+ ASSERT_TRUE(loop_->RegisterSocket(write_fd_, kAllEvents, &listener));
+
+ EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable));
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+
+ int io_result;
+ std::string data(2048, 'a');
+ do {
+ io_result = write(write_fd_, data.data(), data.size());
+ } while (io_result > 0);
+ ASSERT_EQ(errno, EAGAIN);
+
+ // Rearm if necessary and expect no immediate calls.
+ if (!loop_->SupportsEdgeTriggered()) {
+ ASSERT_TRUE(loop_->RearmSocket(write_fd_, kSocketEventWritable));
+ }
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+
+ EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable));
+ do {
+ io_result = read(read_fd_, data.data(), data.size());
+ } while (io_result > 0);
+ ASSERT_EQ(errno, EAGAIN);
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+}
+
+TEST_P(QuicEventLoopFactoryTest, ArtificialEvent) {
+ testing::StrictMock<MockQuicSocketEventListener> listener;
+ ASSERT_TRUE(loop_->RegisterSocket(read_fd_, kAllEvents, &listener));
+ ASSERT_TRUE(loop_->RegisterSocket(write_fd_, kAllEvents, &listener));
+
+ ASSERT_TRUE(loop_->ArtificiallyNotifyEvent(read_fd_, kSocketEventReadable));
+
+ EXPECT_CALL(listener, OnSocketEvent(_, read_fd_, kSocketEventReadable));
+ EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable));
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+}
+
+TEST_P(QuicEventLoopFactoryTest, Unregister) {
+ testing::StrictMock<MockQuicSocketEventListener> listener;
+ ASSERT_TRUE(loop_->RegisterSocket(write_fd_, kAllEvents, &listener));
+ ASSERT_TRUE(loop_->UnregisterSocket(write_fd_));
+
+ // Expect nothing to happen.
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+
+ EXPECT_FALSE(loop_->UnregisterSocket(write_fd_));
+ if (!loop_->SupportsEdgeTriggered()) {
+ EXPECT_FALSE(loop_->RearmSocket(write_fd_, kSocketEventWritable));
+ }
+ EXPECT_FALSE(loop_->ArtificiallyNotifyEvent(write_fd_, kSocketEventWritable));
+}
+
+TEST_P(QuicEventLoopFactoryTest, UnregisterInsideEventHandler) {
+ testing::StrictMock<MockQuicSocketEventListener> listener;
+ ASSERT_TRUE(loop_->RegisterSocket(read_fd_, kAllEvents, &listener));
+ ASSERT_TRUE(loop_->RegisterSocket(write_fd_, kAllEvents, &listener));
+
+ // We are not guaranteed the order in which those events will happen, so we
+ // try to accommodate both possibilities.
+ int total_called = 0;
+ EXPECT_CALL(listener, OnSocketEvent(_, read_fd_, kSocketEventReadable))
+ .Times(AtMost(1))
+ .WillOnce([&]() {
+ ++total_called;
+ ASSERT_TRUE(loop_->UnregisterSocket(write_fd_));
+ });
+ EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable))
+ .Times(AtMost(1))
+ .WillOnce([&]() {
+ ++total_called;
+ ASSERT_TRUE(loop_->UnregisterSocket(read_fd_));
+ });
+ ASSERT_TRUE(loop_->ArtificiallyNotifyEvent(read_fd_, kSocketEventReadable));
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
+ EXPECT_EQ(total_called, 1);
+}
+
+TEST_P(QuicEventLoopFactoryTest, AlarmInFuture) {
+ constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
+ auto [alarm, delegate] = CreateAlarm();
+
+ alarm->Set(clock_.Now() + kAlarmTimeout);
+
+ bool alarm_called = false;
+ EXPECT_CALL(*delegate, OnAlarm()).WillOnce([&]() { alarm_called = true; });
+ RunEventLoopUntil([&]() { return alarm_called; },
+ QuicTime::Delta::FromMilliseconds(100));
+}
+
+TEST_P(QuicEventLoopFactoryTest, AlarmsInPast) {
+ constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
+ auto [alarm1, delegate1] = CreateAlarm();
+ auto [alarm2, delegate2] = CreateAlarm();
+
+ alarm1->Set(clock_.Now() - 2 * kAlarmTimeout);
+ alarm2->Set(clock_.Now() - kAlarmTimeout);
+
+ {
+ testing::InSequence s;
+ EXPECT_CALL(*delegate1, OnAlarm());
+ EXPECT_CALL(*delegate2, OnAlarm());
+ }
+ loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(100));
+}
+
+TEST_P(QuicEventLoopFactoryTest, AlarmCancelled) {
+ constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
+ auto [alarm, delegate] = CreateAlarm();
+
+ alarm->Set(clock_.Now() + kAlarmTimeout);
+ alarm->Cancel();
+
+ loop_->RunEventLoopOnce(kAlarmTimeout * 2);
+}
+
+TEST_P(QuicEventLoopFactoryTest, AlarmCancelledAndSetAgain) {
+ constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
+ auto [alarm, delegate] = CreateAlarm();
+
+ alarm->Set(clock_.Now() + kAlarmTimeout);
+ alarm->Cancel();
+ alarm->Set(clock_.Now() + 2 * kAlarmTimeout);
+
+ bool alarm_called = false;
+ EXPECT_CALL(*delegate, OnAlarm()).WillOnce([&]() { alarm_called = true; });
+ RunEventLoopUntil([&]() { return alarm_called; },
+ QuicTime::Delta::FromMilliseconds(100));
+}
+
+TEST_P(QuicEventLoopFactoryTest, AlarmCancelsAnotherAlarm) {
+ constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
+ auto [alarm1_ptr, delegate1] = CreateAlarm();
+ auto [alarm2_ptr, delegate2] = CreateAlarm();
+
+ QuicAlarm& alarm1 = *alarm1_ptr;
+ QuicAlarm& alarm2 = *alarm2_ptr;
+ alarm1.Set(clock_.Now() - kAlarmTimeout);
+ alarm2.Set(clock_.Now() - kAlarmTimeout);
+
+ int alarms_called = 0;
+ // Since the order in which alarms are cancelled is not well-determined, make
+ // each one cancel another.
+ EXPECT_CALL(*delegate1, OnAlarm()).Times(AtMost(1)).WillOnce([&]() {
+ alarm2.Cancel();
+ ++alarms_called;
+ });
+ EXPECT_CALL(*delegate2, OnAlarm()).Times(AtMost(1)).WillOnce([&]() {
+ alarm1.Cancel();
+ ++alarms_called;
+ });
+ // Run event loop twice to ensure the second alarm is not called after two
+ // iterations.
+ loop_->RunEventLoopOnce(kAlarmTimeout * 2);
+ loop_->RunEventLoopOnce(kAlarmTimeout * 2);
+ EXPECT_EQ(alarms_called, 1);
+}
+
+} // namespace
+} // namespace quic::test
diff --git a/quiche/quic/core/io/quic_default_event_loop.cc b/quiche/quic/core/io/quic_default_event_loop.cc
new file mode 100644
index 0000000..e384528
--- /dev/null
+++ b/quiche/quic/core/io/quic_default_event_loop.cc
@@ -0,0 +1,30 @@
+// Copyright 2022 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/io/quic_default_event_loop.h"
+
+#include <memory>
+
+#include "quiche/quic/core/io/quic_poll_event_loop.h"
+#include "quiche/common/platform/api/quiche_event_loop.h"
+
+namespace quic {
+
+QuicEventLoopFactory* GetDefaultEventLoop() {
+ if (QuicEventLoopFactory* factory =
+ quiche::GetOverrideForDefaultEventLoop()) {
+ return factory;
+ }
+ return QuicPollEventLoopFactory::Get();
+}
+
+std::vector<QuicEventLoopFactory*> GetAllSupportedEventLoops() {
+ std::vector<QuicEventLoopFactory*> loops = {QuicPollEventLoopFactory::Get()};
+ std::vector<QuicEventLoopFactory*> extra =
+ quiche::GetExtraEventLoopImplementations();
+ loops.insert(loops.end(), extra.begin(), extra.end());
+ return loops;
+}
+
+} // namespace quic
diff --git a/quiche/quic/core/io/quic_default_event_loop.h b/quiche/quic/core/io/quic_default_event_loop.h
new file mode 100644
index 0000000..6315b76
--- /dev/null
+++ b/quiche/quic/core/io/quic_default_event_loop.h
@@ -0,0 +1,27 @@
+// Copyright 2022 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_IO_QUIC_DEFAULT_EVENT_LOOP_H_
+#define QUICHE_QUIC_CORE_IO_QUIC_DEFAULT_EVENT_LOOP_H_
+
+#include <memory>
+
+#include "quiche/quic/core/io/quic_event_loop.h"
+#include "quiche/quic/core/quic_clock.h"
+
+namespace quic {
+
+// Returns the default implementation of QuicheEventLoop. The embedders can
+// override this using the platform API. The factory pointer returned is an
+// unowned static variable.
+QUICHE_NO_EXPORT QuicEventLoopFactory* GetDefaultEventLoop(QuicClock* clock);
+
+// Returns the factory objects for all event loops. This is particularly useful
+// for the unit tests. The factory pointers returned are unowned static
+// variables.
+QUICHE_NO_EXPORT std::vector<QuicEventLoopFactory*> GetAllSupportedEventLoops();
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_IO_QUIC_DEFAULT_EVENT_LOOP_H_
diff --git a/quiche/quic/core/io/quic_event_loop.h b/quiche/quic/core/io/quic_event_loop.h
index cc41392..be82ac7 100644
--- a/quiche/quic/core/io/quic_event_loop.h
+++ b/quiche/quic/core/io/quic_event_loop.h
@@ -10,6 +10,7 @@
#include "absl/base/attributes.h"
#include "quiche/quic/core/quic_alarm_factory.h"
+#include "quiche/quic/core/quic_clock.h"
#include "quiche/quic/core/quic_udp_socket.h"
namespace quic {
@@ -76,6 +77,21 @@
virtual QuicAlarmFactory* GetAlarmFactory() = 0;
};
+// A factory object for the event loop. Every implementation is expected to have
+// a static singleton instance.
+class QUICHE_NO_EXPORT QuicEventLoopFactory {
+ public:
+ virtual ~QuicEventLoopFactory() {}
+
+ // Creates an event loop. Note that |clock| may be ignored if the event loop
+ // implementation uses its own clock internally.
+ virtual std::unique_ptr<QuicEventLoop> Create(QuicClock* clock) = 0;
+
+ // A human-readable name of the event loop implementation used in diagnostics
+ // output.
+ virtual std::string GetName() const = 0;
+};
+
} // namespace quic
#endif // QUICHE_QUIC_IO_QUIC_EVENT_LOOP_H_
diff --git a/quiche/quic/core/io/quic_poll_event_loop.h b/quiche/quic/core/io/quic_poll_event_loop.h
index 1764918..f99f220 100644
--- a/quiche/quic/core/io/quic_poll_event_loop.h
+++ b/quiche/quic/core/io/quic_poll_event_loop.h
@@ -147,6 +147,20 @@
bool has_artificial_events_pending_ = false;
};
+class QUICHE_NO_EXPORT QuicPollEventLoopFactory : public QuicEventLoopFactory {
+ public:
+ static QuicPollEventLoopFactory* Get() {
+ static QuicPollEventLoopFactory* factory = new QuicPollEventLoopFactory();
+ return factory;
+ }
+
+ std::unique_ptr<QuicEventLoop> Create(QuicClock* clock) override {
+ return std::make_unique<QuicPollEventLoop>(clock);
+ }
+
+ std::string GetName() const override { return "poll(2)"; }
+};
+
} // namespace quic
#endif // QUICHE_QUIC_CORE_IO_QUIC_POLL_EVENT_LOOP_H_