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());