-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add epoll-waitable queues #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #pragma once | ||
|
|
||
| // Keep all upstream moodycamel symbols below the project-owned namespace. Do | ||
| // not include these upstream headers directly in project or consumer code. | ||
| #define moodycamel exasol::udf::v2::third_party::moodycamel | ||
| #include <blockingconcurrentqueue.h> | ||
| #include <concurrentqueue.h> | ||
| #undef moodycamel | ||
|
|
||
| namespace exasol::udf::v2 { | ||
|
|
||
| template <typename T> | ||
| using MpmcQueue = third_party::moodycamel::ConcurrentQueue<T>; | ||
|
|
||
| template <typename T> | ||
| using BlockingMpmcQueue = | ||
| third_party::moodycamel::BlockingConcurrentQueue<T>; | ||
|
|
||
| } // namespace exasol::udf::v2 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #pragma once | ||
|
|
||
| // Moodycamel is header-only. Rewrite its namespace while parsing the upstream | ||
| // headers so the resulting symbols cannot collide with an unrelated copy that | ||
| // may be loaded into the same process. | ||
| #define moodycamel exasol::udf::v2::third_party::moodycamel | ||
| #include <readerwritercircularbuffer.h> | ||
| #include <readerwriterqueue.h> | ||
| #undef moodycamel | ||
|
|
||
| namespace exasol::udf::v2 { | ||
|
|
||
| template <typename T> | ||
| using SpscQueue = third_party::moodycamel::ReaderWriterQueue<T>; | ||
|
|
||
| template <typename T> | ||
| using SpscCircularBuffer = | ||
| third_party::moodycamel::BlockingReaderWriterCircularBuffer<T>; | ||
|
|
||
| } // namespace exasol::udf::v2 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| #pragma once | ||
|
|
||
| #if !defined(__linux__) | ||
| #error "exasol::udf::v2::WaitableQueue requires Linux eventfd" | ||
| #endif | ||
|
|
||
| #include <sys/eventfd.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include <cerrno> | ||
| #include <cstdint> | ||
| #include <iterator> | ||
| #include <system_error> | ||
| #include <utility> | ||
|
|
||
| #include <exasol/udf/v2/mpmc_queue.hpp> | ||
| #include <exasol/udf/v2/spsc_queue.hpp> | ||
|
|
||
| namespace exasol::udf::v2 { | ||
|
|
||
| // Adds an epoll-compatible readiness descriptor to a queue. The descriptor | ||
| // signals that one or more queue elements may be available; it is not a | ||
| // one-to-one mapping between eventfd counter values and queue elements. | ||
| template <typename Queue> | ||
| class WaitableQueue { | ||
| public: | ||
| using queue_type = Queue; | ||
|
|
||
| WaitableQueue() | ||
| : notification_fd_(::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)) { | ||
| if (notification_fd_ == -1) { | ||
| throw std::system_error(errno, std::generic_category(), | ||
| "eventfd"); | ||
| } | ||
| } | ||
|
|
||
| explicit WaitableQueue(Queue queue) | ||
| : queue_(std::move(queue)), | ||
| notification_fd_(::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)) { | ||
| if (notification_fd_ == -1) { | ||
| throw std::system_error(errno, std::generic_category(), | ||
| "eventfd"); | ||
| } | ||
| } | ||
|
|
||
| ~WaitableQueue() { | ||
| if (notification_fd_ != -1) { | ||
| ::close(notification_fd_); | ||
| } | ||
| } | ||
|
|
||
| WaitableQueue(const WaitableQueue&) = delete; | ||
| WaitableQueue& operator=(const WaitableQueue&) = delete; | ||
|
|
||
| WaitableQueue(WaitableQueue&& other) noexcept | ||
| : queue_(std::move(other.queue_)), | ||
| notification_fd_(std::exchange(other.notification_fd_, -1)) {} | ||
|
|
||
| WaitableQueue& operator=(WaitableQueue&& other) noexcept { | ||
| if (this != &other) { | ||
| if (notification_fd_ != -1) { | ||
| ::close(notification_fd_); | ||
| } | ||
| queue_ = std::move(other.queue_); | ||
| notification_fd_ = std::exchange(other.notification_fd_, -1); | ||
| } | ||
| return *this; | ||
| } | ||
|
|
||
| [[nodiscard]] int native_handle() const noexcept { | ||
| return notification_fd_; | ||
| } | ||
|
|
||
| template <typename T> | ||
| [[nodiscard]] bool enqueue(T&& value) { | ||
| if (!queue_.enqueue(std::forward<T>(value))) { | ||
| return false; | ||
| } | ||
| notify(); | ||
| return true; | ||
| } | ||
|
|
||
| template <typename InputIt> | ||
| std::size_t enqueue_batch(InputIt first, InputIt last) { | ||
| std::size_t enqueued = 0; | ||
| for (; first != last; ++first) { | ||
| if (!queue_.enqueue(*first)) { | ||
| break; | ||
| } | ||
| ++enqueued; | ||
| } | ||
| if (enqueued != 0) { | ||
| notify(); | ||
| } | ||
| return enqueued; | ||
| } | ||
|
|
||
| template <typename Output> | ||
| [[nodiscard]] bool try_dequeue(Output& value) { | ||
| return queue_.try_dequeue(value); | ||
| } | ||
|
|
||
| // Drains all eventfd notifications and returns their accumulated count. | ||
| // Callers should then dequeue until the queue is empty and recheck it | ||
| // before going back to epoll_wait(). | ||
| std::uint64_t drain_notifications() { | ||
| std::uint64_t total = 0; | ||
| for (;;) { | ||
| std::uint64_t value = 0; | ||
| const ssize_t result = ::read(notification_fd_, &value, | ||
| sizeof(value)); | ||
| if (result == sizeof(value)) { | ||
| total += value; | ||
| continue; | ||
| } | ||
| if (result == -1 && errno == EINTR) { | ||
| continue; | ||
| } | ||
| if (result == -1 && errno == EAGAIN) { | ||
| return total; | ||
| } | ||
| if (result == -1) { | ||
| throw std::system_error(errno, std::generic_category(), | ||
| "read eventfd"); | ||
| } | ||
| throw std::system_error(EIO, std::generic_category(), | ||
| "short read from eventfd"); | ||
| } | ||
| } | ||
|
|
||
| Queue& queue() noexcept { return queue_; } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The mutable accessor allows callers to invoke Useful? React with 👍 / 👎. |
||
| const Queue& queue() const noexcept { return queue_; } | ||
|
|
||
| private: | ||
| void notify() { | ||
| constexpr std::uint64_t signal = 1; | ||
| for (;;) { | ||
| const ssize_t result = | ||
| ::write(notification_fd_, &signal, sizeof(signal)); | ||
| if (result == sizeof(signal)) { | ||
| return; | ||
| } | ||
| if (result == -1 && errno == EINTR) { | ||
| continue; | ||
| } | ||
| // A saturated eventfd is already readable. The queue item remains | ||
| // available, so no additional notification is needed. | ||
| if (result == -1 && errno == EAGAIN) { | ||
| return; | ||
| } | ||
| if (result == -1) { | ||
| throw std::system_error(errno, std::generic_category(), | ||
| "write eventfd"); | ||
| } | ||
| throw std::system_error(EIO, std::generic_category(), | ||
| "short write to eventfd"); | ||
| } | ||
| } | ||
|
|
||
| Queue queue_; | ||
| int notification_fd_; | ||
| }; | ||
|
|
||
| template <typename T> | ||
| using WaitableSpscQueue = WaitableQueue<SpscQueue<T>>; | ||
|
|
||
| template <typename T> | ||
| using WaitableMpmcQueue = WaitableQueue<MpmcQueue<T>>; | ||
|
|
||
| } // namespace exasol::udf::v2 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| #include <exasol/udf/v2/mpmc_queue.hpp> | ||
| #include <exasol/udf/v2/spsc_queue.hpp> | ||
|
|
||
| extern "C" void exasol_udf_v2_moodycamel_queue_anchor() { | ||
| exasol::udf::v2::SpscQueue<int> spsc; | ||
| spsc.enqueue(1); | ||
|
|
||
| exasol::udf::v2::MpmcQueue<int> mpmc; | ||
| mpmc.enqueue(2); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #include <cassert> | ||
|
|
||
| #include <exasol/udf/v2/mpmc_queue.hpp> | ||
| #include <exasol/udf/v2/spsc_queue.hpp> | ||
|
|
||
| int main() { | ||
| exasol::udf::v2::SpscQueue<int> spsc; | ||
| assert(spsc.enqueue(7)); | ||
| int value = 0; | ||
| assert(spsc.try_dequeue(value)); | ||
| assert(value == 7); | ||
|
|
||
| exasol::udf::v2::SpscCircularBuffer<int> circular(2); | ||
| assert(circular.try_enqueue(8)); | ||
| assert(circular.try_dequeue(value)); | ||
| assert(value == 8); | ||
|
|
||
| exasol::udf::v2::MpmcQueue<int> mpmc; | ||
| assert(mpmc.enqueue(9)); | ||
| assert(mpmc.try_dequeue(value)); | ||
| assert(value == 9); | ||
|
|
||
| exasol::udf::v2::BlockingMpmcQueue<int> blocking; | ||
| assert(blocking.enqueue(10)); | ||
| assert(blocking.try_dequeue(value)); | ||
| assert(value == 10); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a caller moves an already populated queue into this constructor, the elements remain available but the newly created eventfd starts at zero, so
epoll_wait()can block indefinitely until some later enqueue generates a notification. Either restrict this constructor to empty queues or initialize the notification state when the moved-in queue contains data.Useful? React with 👍 / 👎.