Provide a libevent-based version of QuicEventLoop

Rationale:
- libevent has scalable backends for all platforms, including Windows
- libevent has a feature to wake up an event loop from different threads; this is really hard to implement consistently across all platforms.

Also fix some build failures under --config=gce

PiperOrigin-RevId: 459268214
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 0bb50d2..673c5b7 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -1002,6 +1002,7 @@
     "epoll_server/platform/api/epoll_address_test_utils.h",
     "epoll_server/platform/api/epoll_expect_bug.h",
     "epoll_server/platform/api/epoll_test.h",
+    "quic/bindings/quic_libevent.h",
     "quic/test_tools/quic_client_peer.h",
     "quic/test_tools/quic_mock_syscall_wrapper.h",
     "quic/test_tools/quic_server_peer.h",
@@ -1011,6 +1012,7 @@
 ]
 epoll_test_support_srcs = [
     "epoll_server/fake_simple_epoll_server.cc",
+    "quic/bindings/quic_libevent.cc",
     "quic/test_tools/quic_client_peer.cc",
     "quic/test_tools/quic_mock_syscall_wrapper.cc",
     "quic/test_tools/quic_server_peer.cc",
@@ -1098,6 +1100,7 @@
     "http2/test_tools/http2_frame_builder_test.cc",
     "http2/test_tools/http2_random_test.cc",
     "http2/test_tools/random_decoder_test_base_test.cc",
+    "quic/bindings/quic_libevent_test.cc",
     "quic/core/congestion_control/bandwidth_sampler_test.cc",
     "quic/core/congestion_control/bbr2_simulator_test.cc",
     "quic/core/congestion_control/bbr_sender_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 018f0fe..1b35497 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -1002,6 +1002,7 @@
     "src/quiche/epoll_server/platform/api/epoll_address_test_utils.h",
     "src/quiche/epoll_server/platform/api/epoll_expect_bug.h",
     "src/quiche/epoll_server/platform/api/epoll_test.h",
+    "src/quiche/quic/bindings/quic_libevent.h",
     "src/quiche/quic/test_tools/quic_client_peer.h",
     "src/quiche/quic/test_tools/quic_mock_syscall_wrapper.h",
     "src/quiche/quic/test_tools/quic_server_peer.h",
@@ -1011,6 +1012,7 @@
 ]
 epoll_test_support_srcs = [
     "src/quiche/epoll_server/fake_simple_epoll_server.cc",
+    "src/quiche/quic/bindings/quic_libevent.cc",
     "src/quiche/quic/test_tools/quic_client_peer.cc",
     "src/quiche/quic/test_tools/quic_mock_syscall_wrapper.cc",
     "src/quiche/quic/test_tools/quic_server_peer.cc",
@@ -1098,6 +1100,7 @@
     "src/quiche/http2/test_tools/http2_frame_builder_test.cc",
     "src/quiche/http2/test_tools/http2_random_test.cc",
     "src/quiche/http2/test_tools/random_decoder_test_base_test.cc",
+    "src/quiche/quic/bindings/quic_libevent_test.cc",
     "src/quiche/quic/core/congestion_control/bandwidth_sampler_test.cc",
     "src/quiche/quic/core/congestion_control/bbr2_simulator_test.cc",
     "src/quiche/quic/core/congestion_control/bbr_sender_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 8566044..b6386c2 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -1001,6 +1001,7 @@
     "quiche/epoll_server/platform/api/epoll_address_test_utils.h",
     "quiche/epoll_server/platform/api/epoll_expect_bug.h",
     "quiche/epoll_server/platform/api/epoll_test.h",
+    "quiche/quic/bindings/quic_libevent.h",
     "quiche/quic/test_tools/quic_client_peer.h",
     "quiche/quic/test_tools/quic_mock_syscall_wrapper.h",
     "quiche/quic/test_tools/quic_server_peer.h",
@@ -1010,6 +1011,7 @@
   ],
   "epoll_test_support_srcs": [
     "quiche/epoll_server/fake_simple_epoll_server.cc",
+    "quiche/quic/bindings/quic_libevent.cc",
     "quiche/quic/test_tools/quic_client_peer.cc",
     "quiche/quic/test_tools/quic_mock_syscall_wrapper.cc",
     "quiche/quic/test_tools/quic_server_peer.cc",
@@ -1097,6 +1099,7 @@
     "quiche/http2/test_tools/http2_frame_builder_test.cc",
     "quiche/http2/test_tools/http2_random_test.cc",
     "quiche/http2/test_tools/random_decoder_test_base_test.cc",
+    "quiche/quic/bindings/quic_libevent_test.cc",
     "quiche/quic/core/congestion_control/bandwidth_sampler_test.cc",
     "quiche/quic/core/congestion_control/bbr2_simulator_test.cc",
     "quiche/quic/core/congestion_control/bbr_sender_test.cc",
diff --git a/quiche/quic/bindings/quic_libevent.cc b/quiche/quic/bindings/quic_libevent.cc
new file mode 100644
index 0000000..083236d
--- /dev/null
+++ b/quiche/quic/bindings/quic_libevent.cc
@@ -0,0 +1,233 @@
+// 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/bindings/quic_libevent.h"
+
+#include <memory>
+
+#include "absl/time/time.h"
+#include "event2/event.h"
+#include "event2/event_struct.h"
+#include "event2/thread.h"
+#include "quiche/quic/core/io/quic_event_loop.h"
+#include "quiche/quic/core/quic_alarm.h"
+#include "quiche/quic/core/quic_clock.h"
+#include "quiche/quic/core/quic_default_clock.h"
+#include "quiche/quic/core/quic_time.h"
+
+namespace quic {
+
+using LibeventEventMask = short;  // NOLINT(runtime/int)
+
+QuicSocketEventMask LibeventEventMaskToQuicEvents(int events) {
+  return ((events & EV_READ) ? kSocketEventReadable : 0) |
+         ((events & EV_WRITE) ? kSocketEventWritable : 0);
+}
+
+LibeventEventMask QuicEventsToLibeventEventMask(QuicSocketEventMask events) {
+  return ((events & kSocketEventReadable) ? EV_READ : 0) |
+         ((events & kSocketEventWritable) ? EV_WRITE : 0);
+}
+
+class LibeventAlarm : public QuicAlarm {
+ public:
+  LibeventAlarm(LibeventQuicEventLoop* loop,
+                QuicArenaScopedPtr<QuicAlarm::Delegate> delegate)
+      : QuicAlarm(std::move(delegate)), clock_(loop->clock()) {
+    evtimer_assign(
+        &event_, loop->base(),
+        [](evutil_socket_t, LibeventEventMask, void* arg) {
+          LibeventAlarm* self = reinterpret_cast<LibeventAlarm*>(arg);
+          self->Fire();
+        },
+        this);
+  }
+  ~LibeventAlarm() { event_del(&event_); }
+
+ protected:
+  void SetImpl() override {
+    absl::Duration timeout =
+        absl::Microseconds((deadline() - clock_->Now()).ToMicroseconds());
+    timeval unix_time = absl::ToTimeval(timeout);
+    event_add(&event_, &unix_time);
+  }
+
+  void CancelImpl() override { event_del(&event_); }
+
+ private:
+  // While this decreases ABI portability, we use event inline, rather than
+  // allocating it with event_new(), since this improves cache locality.
+  event event_;
+  QuicClock* clock_;
+};
+
+LibeventQuicEventLoop::LibeventQuicEventLoop(event_base* base, QuicClock* clock)
+    : base_(base),
+      edge_triggered_(event_base_get_features(base) & EV_FEATURE_ET),
+      clock_(clock),
+      alarm_factory_(this) {
+  QUICHE_CHECK_LE(sizeof(event), event_get_struct_event_size())
+      << "libevent ABI mismatch: sizeof(event) is bigger than the one QUICHE "
+         "has been compiled with";
+}
+
+bool LibeventQuicEventLoop::RegisterSocket(QuicUdpSocketFd fd,
+                                           QuicSocketEventMask events,
+                                           QuicSocketEventListener* listener) {
+  auto [it, success] =
+      registration_map_.try_emplace(fd, this, fd, events, listener);
+  return success;
+}
+
+bool LibeventQuicEventLoop::UnregisterSocket(QuicUdpSocketFd fd) {
+  return registration_map_.erase(fd);
+}
+
+bool LibeventQuicEventLoop::RearmSocket(QuicUdpSocketFd fd,
+                                        QuicSocketEventMask events) {
+  if (edge_triggered_) {
+    QUICHE_BUG(LibeventQuicEventLoop_RearmSocket_called_on_ET)
+        << "RearmSocket() called on an edge-triggered event loop";
+    return false;
+  }
+  auto it = registration_map_.find(fd);
+  if (it == registration_map_.end()) {
+    return false;
+  }
+  it->second.Rearm(events);
+  return true;
+}
+
+bool LibeventQuicEventLoop::ArtificiallyNotifyEvent(
+    QuicUdpSocketFd fd, QuicSocketEventMask events) {
+  auto it = registration_map_.find(fd);
+  if (it == registration_map_.end()) {
+    return false;
+  }
+  it->second.ArtificiallyNotify(events);
+  return true;
+}
+
+void LibeventQuicEventLoop::RunEventLoopOnce(QuicTime::Delta default_timeout) {
+  timeval timeout =
+      absl::ToTimeval(absl::Microseconds(default_timeout.ToMicroseconds()));
+  event_base_loopexit(base_, &timeout);
+  event_base_loop(base_, EVLOOP_ONCE);
+}
+
+void LibeventQuicEventLoop::WakeUp() { event_base_loopbreak(base_); }
+
+LibeventQuicEventLoop::Registration::Registration(
+    LibeventQuicEventLoop* loop, QuicUdpSocketFd fd, QuicSocketEventMask events,
+    QuicSocketEventListener* listener)
+    : loop_(loop), listener_(listener) {
+  event_callback_fn callback = [](evutil_socket_t fd, LibeventEventMask events,
+                                  void* arg) {
+    auto* self = reinterpret_cast<LibeventQuicEventLoop::Registration*>(arg);
+    self->listener_->OnSocketEvent(self->loop_, fd,
+                                   LibeventEventMaskToQuicEvents(events));
+  };
+
+  if (loop_->SupportsEdgeTriggered()) {
+    LibeventEventMask mask =
+        QuicEventsToLibeventEventMask(events) | EV_PERSIST | EV_ET;
+    event_assign(&both_events_, loop_->base(), fd, mask, callback, this);
+    event_add(&both_events_, nullptr);
+  } else {
+    event_assign(&read_event_, loop_->base(), fd, EV_READ, callback, this);
+    event_assign(&write_event_, loop_->base(), fd, EV_WRITE, callback, this);
+    Rearm(events);
+  }
+}
+
+LibeventQuicEventLoop::Registration::~Registration() {
+  if (loop_->SupportsEdgeTriggered()) {
+    event_del(&both_events_);
+  } else {
+    event_del(&read_event_);
+    event_del(&write_event_);
+  }
+}
+
+void LibeventQuicEventLoop::Registration::ArtificiallyNotify(
+    QuicSocketEventMask events) {
+  if (loop_->SupportsEdgeTriggered()) {
+    event_active(&both_events_, QuicEventsToLibeventEventMask(events), 0);
+    return;
+  }
+
+  if (events & kSocketEventReadable) {
+    event_active(&read_event_, EV_READ, 0);
+  }
+  if (events & kSocketEventWritable) {
+    event_active(&write_event_, EV_WRITE, 0);
+  }
+}
+
+void LibeventQuicEventLoop::Registration::Rearm(QuicSocketEventMask events) {
+  QUICHE_DCHECK(!loop_->SupportsEdgeTriggered());
+  if (events & kSocketEventReadable) {
+    event_add(&read_event_, nullptr);
+  }
+  if (events & kSocketEventWritable) {
+    event_add(&write_event_, nullptr);
+  }
+}
+
+QuicAlarm* LibeventQuicEventLoop::AlarmFactory::CreateAlarm(
+    QuicAlarm::Delegate* delegate) {
+  return new LibeventAlarm(loop_,
+                           QuicArenaScopedPtr<QuicAlarm::Delegate>(delegate));
+}
+
+QuicArenaScopedPtr<QuicAlarm> LibeventQuicEventLoop::AlarmFactory::CreateAlarm(
+    QuicArenaScopedPtr<QuicAlarm::Delegate> delegate,
+    QuicConnectionArena* arena) {
+  if (arena != nullptr) {
+    return arena->New<LibeventAlarm>(loop_, std::move(delegate));
+  }
+  return QuicArenaScopedPtr<QuicAlarm>(
+      new LibeventAlarm(loop_, std::move(delegate)));
+}
+
+QuicLibeventEventLoopFactory::QuicLibeventEventLoopFactory(
+    bool force_level_triggered)
+    : force_level_triggered_(force_level_triggered) {
+  std::unique_ptr<QuicEventLoop> event_loop = Create(QuicDefaultClock::Get());
+  name_ = absl::StrFormat(
+      "libevent(%s)",
+      event_base_get_method(
+          static_cast<LibeventQuicEventLoopWithOwnership*>(event_loop.get())
+              ->base()));
+}
+
+struct LibeventConfigDeleter {
+  void operator()(event_config* config) { event_config_free(config); }
+};
+
+std::unique_ptr<QuicEventLoop> QuicLibeventEventLoopFactory::Create(
+    QuicClock* clock) {
+  // Required for event_base_loopbreak() to actually work.
+  static int threads_initialized = []() {
+#ifdef _WIN32
+    return evthread_use_windows_threads();
+#else
+    return evthread_use_pthreads();
+#endif
+  }();
+  QUICHE_DCHECK_EQ(threads_initialized, 0);
+
+  std::unique_ptr<event_config, LibeventConfigDeleter> config(
+      event_config_new());
+  if (force_level_triggered_) {
+    // epoll and kqueue are the two only current libevent backends that support
+    // edge-triggered I/O.
+    event_config_avoid_method(config.get(), "epoll");
+    event_config_avoid_method(config.get(), "kqueue");
+  }
+  return std::make_unique<LibeventQuicEventLoopWithOwnership>(
+      event_base_new_with_config(config.get()), clock);
+}
+
+}  // namespace quic
diff --git a/quiche/quic/bindings/quic_libevent.h b/quiche/quic/bindings/quic_libevent.h
new file mode 100644
index 0000000..1f46e71
--- /dev/null
+++ b/quiche/quic/bindings/quic_libevent.h
@@ -0,0 +1,148 @@
+// 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_BINDINGS_QUIC_LIBEVENT_H_
+#define QUICHE_QUIC_BINDINGS_QUIC_LIBEVENT_H_
+
+#include <memory>
+
+#include "absl/container/node_hash_map.h"
+#include "event2/event.h"
+#include "event2/event_struct.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_udp_socket.h"
+
+namespace quic {
+
+// Provides a libevent-based implementation of QuicEventLoop.  Since libevent
+// uses relative time for all timeouts, the provided clock does not need to use
+// the UNIX time.
+class QUICHE_EXPORT_PRIVATE LibeventQuicEventLoop : public QuicEventLoop {
+ public:
+  explicit LibeventQuicEventLoop(event_base* base, QuicClock* clock);
+
+  // QuicEventLoop implementation.
+  bool SupportsEdgeTriggered() const override { return edge_triggered_; }
+  QuicAlarmFactory* GetAlarmFactory() override { return &alarm_factory_; }
+  bool RegisterSocket(QuicUdpSocketFd fd, QuicSocketEventMask events,
+                      QuicSocketEventListener* listener) override;
+  bool UnregisterSocket(QuicUdpSocketFd fd) override;
+  bool RearmSocket(QuicUdpSocketFd fd, QuicSocketEventMask events) override;
+  bool ArtificiallyNotifyEvent(QuicUdpSocketFd fd,
+                               QuicSocketEventMask events) override;
+  void RunEventLoopOnce(QuicTime::Delta default_timeout) override;
+
+  // Can be called from another thread to wake up the event loop from a blocking
+  // RunEventLoopOnce() call.
+  void WakeUp();
+
+  event_base* base() { return base_; }
+  QuicClock* clock() const { return clock_; }
+
+ private:
+  class AlarmFactory : public QuicAlarmFactory {
+   public:
+    AlarmFactory(LibeventQuicEventLoop* loop) : loop_(loop) {}
+
+    // QuicAlarmFactory interface.
+    QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) override;
+    QuicArenaScopedPtr<QuicAlarm> CreateAlarm(
+        QuicArenaScopedPtr<QuicAlarm::Delegate> delegate,
+        QuicConnectionArena* arena) override;
+
+   private:
+    LibeventQuicEventLoop* loop_;
+  };
+
+  class Registration {
+   public:
+    Registration(LibeventQuicEventLoop* loop, QuicUdpSocketFd fd,
+                 QuicSocketEventMask events, QuicSocketEventListener* listener);
+    ~Registration();
+
+    void ArtificiallyNotify(QuicSocketEventMask events);
+    void Rearm(QuicSocketEventMask events);
+
+   private:
+    LibeventQuicEventLoop* loop_;
+    QuicSocketEventListener* listener_;
+
+    // Used for edge-triggered backends.
+    event both_events_;
+    // Used for level-triggered backends, since we may have to re-arm read
+    // events and write events separately.
+    event read_event_;
+    event write_event_;
+  };
+
+  using RegistrationMap = absl::node_hash_map<QuicUdpSocketFd, Registration>;
+
+  event_base* base_;
+  const bool edge_triggered_;
+  QuicClock* clock_;
+
+  RegistrationMap registration_map_;
+  AlarmFactory alarm_factory_;
+};
+
+// RAII-style wrapper around event_base.
+class QUICHE_EXPORT_PRIVATE LibeventLoop {
+ public:
+  LibeventLoop(struct event_base* base) : event_base_(base) {}
+  ~LibeventLoop() { event_base_free(event_base_); }
+
+  struct event_base* event_base() { return event_base_; }
+
+ private:
+  struct event_base* event_base_;
+};
+
+// A version of LibeventQuicEventLoop that owns the supplied `event_base`.  Note
+// that the inheritance order here matters, since otherwise the `event_base` in
+// question will be deleted before the LibeventQuicEventLoop object referencing
+// it.
+class QUICHE_EXPORT_PRIVATE LibeventQuicEventLoopWithOwnership
+    : public LibeventLoop,
+      public LibeventQuicEventLoop {
+ public:
+  // Takes ownership of |base|.
+  explicit LibeventQuicEventLoopWithOwnership(struct event_base* base,
+                                              QuicClock* clock)
+      : LibeventLoop(base), LibeventQuicEventLoop(base, clock) {}
+};
+
+class QUICHE_EXPORT_PRIVATE QuicLibeventEventLoopFactory
+    : public QuicEventLoopFactory {
+ public:
+  // Provides the preferred libevent backend.
+  static QuicLibeventEventLoopFactory* Get() {
+    static QuicLibeventEventLoopFactory* factory =
+        new QuicLibeventEventLoopFactory(/*force_level_triggered=*/false);
+    return factory;
+  }
+
+  // Provides the libevent backend that does not support edge-triggered
+  // notifications.  Those are useful for tests, since we can test
+  // level-triggered I/O even on platforms where edge-triggered is the default.
+  static QuicLibeventEventLoopFactory* GetLevelTriggeredBackendForTests() {
+    static QuicLibeventEventLoopFactory* factory =
+        new QuicLibeventEventLoopFactory(/*force_level_triggered=*/true);
+    return factory;
+  }
+
+  std::unique_ptr<QuicEventLoop> Create(QuicClock* clock) override;
+  std::string GetName() const override { return name_; }
+
+ private:
+  explicit QuicLibeventEventLoopFactory(bool force_level_triggered);
+
+  bool force_level_triggered_;
+  std::string name_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_BINDINGS_QUIC_LIBEVENT_H_
diff --git a/quiche/quic/bindings/quic_libevent_test.cc b/quiche/quic/bindings/quic_libevent_test.cc
new file mode 100644
index 0000000..20dcbad
--- /dev/null
+++ b/quiche/quic/bindings/quic_libevent_test.cc
@@ -0,0 +1,66 @@
+// 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/bindings/quic_libevent.h"
+
+#include <memory>
+
+#include "absl/memory/memory.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "quiche/quic/core/quic_alarm.h"
+#include "quiche/quic/core/quic_default_clock.h"
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/platform/api/quic_thread.h"
+
+namespace quic::test {
+namespace {
+
+class FailureAlarmDelegate : public QuicAlarm::Delegate {
+ public:
+  QuicConnectionContext* GetConnectionContext() override { return nullptr; }
+  void OnAlarm() override { ADD_FAILURE() << "Test timed out"; }
+};
+
+class LoopBreakThread : public QuicThread {
+ public:
+  LoopBreakThread(LibeventQuicEventLoop* loop)
+      : QuicThread("LoopBreakThread"), loop_(loop) {}
+
+  void Run() override {
+    // Make sure the other thread has actually made the blocking poll/epoll/etc
+    // call before calling WakeUp().
+    absl::SleepFor(absl::Milliseconds(250));
+
+    loop_broken_.store(true);
+    loop_->WakeUp();
+  }
+
+  std::atomic<int>& loop_broken() { return loop_broken_; }
+
+ private:
+  LibeventQuicEventLoop* loop_;
+  std::atomic<int> loop_broken_ = 0;
+};
+
+TEST(QuicLibeventTest, WakeUpFromAnotherThread) {
+  QuicClock* clock = QuicDefaultClock::Get();
+  auto event_loop_owned = QuicLibeventEventLoopFactory::Get()->Create(clock);
+  LibeventQuicEventLoop* event_loop =
+      static_cast<LibeventQuicEventLoop*>(event_loop_owned.get());
+  std::unique_ptr<QuicAlarm> timeout_alarm = absl::WrapUnique(
+      event_loop->GetAlarmFactory()->CreateAlarm(new FailureAlarmDelegate()));
+
+  const QuicTime kTimeoutAt = clock->Now() + QuicTime::Delta::FromSeconds(10);
+  timeout_alarm->Set(kTimeoutAt);
+
+  LoopBreakThread thread(event_loop);
+  thread.Start();
+  event_loop->RunEventLoopOnce(QuicTime::Delta::FromSeconds(5 * 60));
+  EXPECT_TRUE(thread.loop_broken().load());
+  thread.Join();
+}
+
+}  // namespace
+}  // namespace quic::test
diff --git a/quiche/quic/core/io/quic_all_event_loops_test.cc b/quiche/quic/core/io/quic_all_event_loops_test.cc
index 19dafee..c9e4dab 100644
--- a/quiche/quic/core/io/quic_all_event_loops_test.cc
+++ b/quiche/quic/core/io/quic_all_event_loops_test.cc
@@ -14,6 +14,7 @@
 #include <fcntl.h>
 #include <unistd.h>
 
+#include "absl/cleanup/cleanup.h"
 #include "absl/memory/memory.h"
 #include "absl/strings/ascii.h"
 #include "absl/strings/string_view.h"
@@ -31,6 +32,10 @@
 using testing::_;
 using testing::AtMost;
 
+MATCHER_P(HasFlagSet, value, "Checks a flag in a bit mask") {
+  return (arg & value) != 0;
+}
+
 constexpr QuicSocketEventMask kAllEvents =
     kSocketEventReadable | kSocketEventWritable | kSocketEventError;
 
@@ -48,6 +53,11 @@
   MOCK_METHOD(void, OnAlarm, (), (override));
 };
 
+void SetNonBlocking(int fd) {
+  QUICHE_CHECK(::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL) | O_NONBLOCK) == 0)
+      << "Failed to mark FD non-blocking, errno: " << errno;
+}
+
 class QuicEventLoopFactoryTest
     : public QuicTestWithParam<QuicEventLoopFactory*> {
  public:
@@ -59,12 +69,8 @@
     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;
+    SetNonBlocking(read_fd_);
+    SetNonBlocking(write_fd_);
   }
 
   ~QuicEventLoopFactoryTest() {
@@ -267,6 +273,52 @@
   EXPECT_EQ(total_called, 1);
 }
 
+// Creates a bidirectional socket and tests its behavior when it's both readable
+// and writable.
+TEST_P(QuicEventLoopFactoryTest, ReadWriteSocket) {
+  int sockets[2];
+  ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), 0);
+  auto close_sockets = absl::MakeCleanup([&]() {
+    close(sockets[0]);
+    close(sockets[1]);
+  });
+  SetNonBlocking(sockets[0]);
+  SetNonBlocking(sockets[1]);
+
+  testing::StrictMock<MockQuicSocketEventListener> listener;
+  ASSERT_TRUE(loop_->RegisterSocket(sockets[0], kAllEvents, &listener));
+  EXPECT_CALL(listener, OnSocketEvent(_, sockets[0], kSocketEventWritable));
+  loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(4));
+
+  int io_result;
+  std::string data(2048, 'a');
+  do {
+    io_result = write(sockets[0], data.data(), data.size());
+  } while (io_result > 0);
+  ASSERT_EQ(errno, EAGAIN);
+
+  if (!loop_->SupportsEdgeTriggered()) {
+    ASSERT_TRUE(loop_->RearmSocket(sockets[0], kSocketEventWritable));
+  }
+  // We are not write-blocked, so this should not notify.
+  loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(4));
+
+  EXPECT_GT(write(sockets[1], data.data(), data.size()), 0);
+  EXPECT_CALL(listener, OnSocketEvent(_, sockets[0], kSocketEventReadable));
+  loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(4));
+
+  do {
+    char buffer[2048];
+    io_result = read(sockets[1], buffer, sizeof(buffer));
+  } while (io_result > 0);
+  ASSERT_EQ(errno, EAGAIN);
+  // Here, we can receive either "writable" or "readable and writable"
+  // notification depending on the backend in question.
+  EXPECT_CALL(listener,
+              OnSocketEvent(_, sockets[0], HasFlagSet(kSocketEventWritable)));
+  loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(4));
+}
+
 TEST_P(QuicEventLoopFactoryTest, AlarmInFuture) {
   constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
   auto [alarm, delegate] = CreateAlarm();
@@ -347,5 +399,14 @@
   EXPECT_EQ(alarms_called, 1);
 }
 
+TEST_P(QuicEventLoopFactoryTest, DestructorWithPendingAlarm) {
+  constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
+  auto [alarm1_ptr, delegate1] = CreateAlarm();
+
+  alarm1_ptr->Set(clock_.Now() + kAlarmTimeout);
+  // Expect destructor to cleanly unregister itself before the event loop is
+  // gone.
+}
+
 }  // 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
index e384528..8c1877c 100644
--- a/quiche/quic/core/io/quic_default_event_loop.cc
+++ b/quiche/quic/core/io/quic_default_event_loop.cc
@@ -9,6 +9,10 @@
 #include "quiche/quic/core/io/quic_poll_event_loop.h"
 #include "quiche/common/platform/api/quiche_event_loop.h"
 
+#ifdef QUICHE_ENABLE_LIBEVENT
+#include "quiche/quic/bindings/quic_libevent.h"
+#endif
+
 namespace quic {
 
 QuicEventLoopFactory* GetDefaultEventLoop() {
@@ -16,11 +20,20 @@
           quiche::GetOverrideForDefaultEventLoop()) {
     return factory;
   }
+#ifdef QUICHE_ENABLE_LIBEVENT
+  return QuicLibeventEventLoopFactory::Get();
+#else
   return QuicPollEventLoopFactory::Get();
+#endif
 }
 
 std::vector<QuicEventLoopFactory*> GetAllSupportedEventLoops() {
-  std::vector<QuicEventLoopFactory*> loops = {QuicPollEventLoopFactory::Get()};
+  std::vector<QuicEventLoopFactory*> loops = {
+#ifdef QUICHE_ENABLE_LIBEVENT
+      QuicLibeventEventLoopFactory::Get(),
+      QuicLibeventEventLoopFactory::GetLevelTriggeredBackendForTests(),
+#endif
+      QuicPollEventLoopFactory::Get()};
   std::vector<QuicEventLoopFactory*> extra =
       quiche::GetExtraEventLoopImplementations();
   loops.insert(loops.end(), extra.begin(), extra.end());