daemon: Phase 0 platform seams (ADR 0020) — eventfd/SO_PEERCRED/instance-lock/XDG behind platform/, zero behaviour change
Introduces the four rpc-layer seams docs/08-porting.md calls for this lane: platform::Wakeup (eventfd), platform::peer_of (SO_PEERCRED/struct ucred, same-UID check preserved unchanged), platform::acquire_instance_lock (abstract-namespace socket), platform::runtime_base_dir/data_base_dir (XDG lookups). Today's Linux code moves unchanged into daemon/src/rpc/platform/linux/; the seam headers carry no OS types and no #ifdef. No fifth interface for timerfd: EventLoop gains a portable add_timer() that folds the next deadline into poll()'s own timeout, replacing both timerfd instances in main.cpp — the loop already computes a deadline, so this needs no per-OS backend at all. Full suite 59/59 green; no #ifdef outside platform/linux/, no behaviour change. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
#include "rpc/event_loop.hpp"
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
@@ -12,14 +11,10 @@
|
||||
namespace velox::daemon::rpc {
|
||||
|
||||
EventLoop::EventLoop() {
|
||||
wake_fd_ = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
|
||||
if (wake_fd_ < 0) throw std::runtime_error("eventfd() failed");
|
||||
fds_.emplace(wake_fd_, Entry{kRead, [this](int, unsigned) { drain_wakeup(); }});
|
||||
fds_.emplace(wakeup_.pollfd(), Entry{kRead, [this](int, unsigned) { wakeup_.drain(); }});
|
||||
}
|
||||
|
||||
EventLoop::~EventLoop() {
|
||||
if (wake_fd_ >= 0) ::close(wake_fd_);
|
||||
}
|
||||
EventLoop::~EventLoop() = default;
|
||||
|
||||
void EventLoop::add_fd(int fd, unsigned interest, Callback cb) {
|
||||
fds_[fd] = Entry{interest, std::move(cb)};
|
||||
@@ -30,15 +25,11 @@ void EventLoop::mod_fd(int fd, unsigned interest) {
|
||||
}
|
||||
|
||||
void EventLoop::del_fd(int fd) {
|
||||
if (fd == wake_fd_) return; // internal, never removed
|
||||
if (fd == wakeup_.pollfd()) return; // internal, never removed
|
||||
fds_.erase(fd);
|
||||
}
|
||||
|
||||
void EventLoop::wake() noexcept {
|
||||
const std::uint64_t one = 1;
|
||||
// Best-effort: an EAGAIN here means a wakeup is already pending, which is fine.
|
||||
[[maybe_unused]] ssize_t n = ::write(wake_fd_, &one, sizeof(one));
|
||||
}
|
||||
void EventLoop::wake() noexcept { wakeup_.signal(); }
|
||||
|
||||
void EventLoop::stop() noexcept {
|
||||
stop_requested_ = true;
|
||||
@@ -62,9 +53,39 @@ void EventLoop::drain_posts() {
|
||||
for (auto& fn : batch) fn();
|
||||
}
|
||||
|
||||
void EventLoop::drain_wakeup() noexcept {
|
||||
std::uint64_t sink = 0;
|
||||
while (::read(wake_fd_, &sink, sizeof(sink)) > 0) {
|
||||
EventLoop::TimerId EventLoop::add_timer(std::chrono::milliseconds interval,
|
||||
std::function<void()> cb) {
|
||||
const TimerId id = next_timer_id_++;
|
||||
timers_.emplace(id, Timer{std::chrono::steady_clock::now() + interval, interval,
|
||||
std::move(cb)});
|
||||
wake(); // the loop may already be blocked on a longer timeout
|
||||
return id;
|
||||
}
|
||||
|
||||
void EventLoop::remove_timer(TimerId id) { timers_.erase(id); }
|
||||
|
||||
int EventLoop::next_timeout_ms() const {
|
||||
if (timers_.empty()) return -1;
|
||||
auto soonest = timers_.begin()->second.next;
|
||||
for (const auto& [id, t] : timers_) soonest = std::min(soonest, t.next);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(soonest - now);
|
||||
return ms.count() > 0 ? static_cast<int>(ms.count()) : 0;
|
||||
}
|
||||
|
||||
void EventLoop::run_due_timers() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
// Snapshot ids first: a callback may add/remove timers, which would invalidate
|
||||
// iteration over timers_ directly.
|
||||
std::vector<TimerId> due;
|
||||
for (auto& [id, t] : timers_) {
|
||||
if (t.next <= now) due.push_back(id);
|
||||
}
|
||||
for (const TimerId id : due) {
|
||||
const auto it = timers_.find(id);
|
||||
if (it == timers_.end()) continue; // removed by an earlier callback this pass
|
||||
it->second.next = now + it->second.interval;
|
||||
it->second.cb();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,18 +104,22 @@ void EventLoop::run() {
|
||||
short ev = 0;
|
||||
if (e.interest & kRead) ev |= POLLIN;
|
||||
if (e.interest & kWrite) ev |= POLLOUT;
|
||||
if (ev == 0 && fd != wake_fd_) continue;
|
||||
if (ev == 0 && fd != wakeup_.pollfd()) continue;
|
||||
pollfd p{};
|
||||
p.fd = fd;
|
||||
p.events = ev;
|
||||
pfds.push_back(p);
|
||||
}
|
||||
|
||||
const int rc = ::poll(pfds.data(), pfds.size(), -1);
|
||||
const int rc = ::poll(pfds.data(), pfds.size(), next_timeout_ms());
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
throw std::runtime_error("poll() failed");
|
||||
}
|
||||
|
||||
drain_posts();
|
||||
run_due_timers();
|
||||
|
||||
if (rc == 0) continue;
|
||||
|
||||
// Snapshot the fds that fired before invoking any callback: a callback may erase
|
||||
@@ -104,8 +129,6 @@ void EventLoop::run() {
|
||||
if (p.revents != 0) fired.push_back(p.fd);
|
||||
}
|
||||
|
||||
drain_posts();
|
||||
|
||||
for (const int fd : fired) {
|
||||
const auto it = fds_.find(fd);
|
||||
if (it == fds_.end()) continue; // removed by an earlier callback this pass
|
||||
|
||||
Reference in New Issue
Block a user