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_