diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2025-10-08 00:58:42 +0200 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2025-10-19 00:13:47 +0200 |
| commit | 86ec0b5386fc2078891a829026844d2ec21ea7db (patch) | |
| tree | 5f3ed650bc2957e06fd3c8c1ecfa7c6e7fc825b6 /src/looper_poll.cc | |
| parent | 3a002fb9c23fc9a6384bc1b30a8e364924bb574e (diff) | |
Add http module and implement basic http server
Diffstat (limited to 'src/looper_poll.cc')
| -rw-r--r-- | src/looper_poll.cc | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/src/looper_poll.cc b/src/looper_poll.cc new file mode 100644 index 0000000..2fed7d2 --- /dev/null +++ b/src/looper_poll.cc @@ -0,0 +1,238 @@ +#include "logger.hh" +#include "looper.hh" + +#include <algorithm> +#include <cassert> +#include <cerrno> +#include <chrono> +#include <cstdint> +#include <cstring> +#include <deque> +#include <format> +#include <functional> +#include <limits> +#include <memory> +#include <poll.h> +#include <unordered_map> +#include <utility> +#include <vector> + +namespace looper { + +namespace { + +class LooperPoll : public Looper { + public: + LooperPoll() = default; + + void add(int fd, uint8_t events, + std::function<void(uint8_t)> callback) override { + if (fd < 0) + return; + auto ret = entry_.emplace(fd, Entry(events)); + if (!ret.second) { + assert(ret.first->second.delete_); + ret.first->second.delete_ = false; + ret.first->second.events_ = events; + } + ret.first->second.callback_ = std::move(callback); + } + + void update(int fd, uint8_t events) override { + if (fd < 0) + return; + auto it = entry_.find(fd); + if (it == entry_.end() || it->second.delete_) { + assert(false); + return; + } + it->second.events_ = events; + } + + void remove(int fd) override { + if (fd < 0) + return; + auto it = entry_.find(fd); + if (it == entry_.end()) + return; + it->second.delete_ = true; + } + + bool run(logger::Logger& logger) override { + while (!quit_) { + int timeout; + if (scheduled_.empty()) { + timeout = -1; + } else { + auto now = std::chrono::steady_clock::now(); + while (true) { + if (now < scheduled_.front().target_) { + auto delay = std::chrono::duration_cast<std::chrono::milliseconds>( + scheduled_.front().target_ - now); + if (delay.count() <= std::numeric_limits<int>::max()) + timeout = static_cast<int>(delay.count()); + else + timeout = std::numeric_limits<int>::max(); + break; + } + auto id = scheduled_.front().id_; + auto callback = std::move(scheduled_.front().callback_); + scheduled_.pop_front(); + callback(id); + if (scheduled_.empty()) { + timeout = -1; + break; + } + } + // Scheduled callbacks might call quit(). + if (quit_) + break; + } + // NOLINTNEXTLINE(misc-include-cleaner) + std::vector<struct pollfd> pollfd; + pollfd.reserve(entry_.size()); + auto it = entry_.begin(); + while (it != entry_.end()) { + if (it->second.delete_) { + it = entry_.erase(it); + } else { + // NOLINTNEXTLINE(misc-include-cleaner) + struct pollfd tmp; + tmp.fd = it->first; + tmp.events = events_looper2poll(it->second.events_); + pollfd.push_back(tmp); + ++it; + } + } + // NOLINTNEXTLINE(misc-include-cleaner) + int active = poll(pollfd.data(), pollfd.size(), timeout); + if (active < 0) { + if (errno == EINTR) + continue; + logger.err(std::format("Poll failed: {}", strerror(errno))); + return false; + } + for (auto it2 = pollfd.begin(); active; ++it2) { + if (it2->revents == 0) + continue; + --active; + auto events = events_poll2looper(it2->revents); + if (events) { + it = entry_.find(it2->fd); + if (!it->second.delete_) { + events &= (it->second.events_ | EVENT_ERROR); + if (events) { + it->second.callback_(events); + } + } + } + } + } + // Reset quit_ so run() can be called again + quit_ = false; + return true; + } + + void quit() override { quit_ = true; } + + uint32_t schedule(double delay, + std::function<void(uint32_t)> callback) override { + assert(delay >= 0.0); + uint32_t id = next_schedule_id(); + auto target = + std::chrono::steady_clock::now() + + std::chrono::duration_cast<std::chrono::steady_clock::duration>( + std::chrono::duration<double>(delay)); + auto insert = scheduled_.end(); + while (insert != scheduled_.begin()) { + auto prev = insert - 1; + if (prev->target_ < target) + break; + insert = prev; + } + scheduled_.emplace(insert, std::move(callback), id, target); + return id; + } + + void cancel(uint32_t id) override { + auto it = std::ranges::find_if(scheduled_, [id](auto const& scheduled) { + return scheduled.id_ == id; + }); + if (it != scheduled_.end()) { + scheduled_.erase(it); + } else { + assert(false); + } + } + + private: + struct Entry { + uint8_t events_; + std::function<void(uint8_t)> callback_; + bool delete_{false}; + + explicit Entry(uint8_t events) : events_(events) {} + }; + + struct Scheduled { + std::function<void(uint32_t)> callback_; + uint32_t id_; + std::chrono::steady_clock::time_point target_; + + Scheduled(std::function<void(uint32_t)> callback, uint32_t id, + std::chrono::steady_clock::time_point target) + : callback_(std::move(callback)), id_(id), target_(target) {} + }; + + static short events_looper2poll(uint8_t events) { + short ret = 0; + if (events & EVENT_READ) + // NOLINTNEXTLINE(misc-include-cleaner) + ret |= POLLIN | POLLPRI; + if (events & EVENT_WRITE) + // NOLINTNEXTLINE(misc-include-cleaner) + ret |= POLLOUT; + return ret; + } + + static uint8_t events_poll2looper(short events) { + uint8_t ret = 0; + // NOLINTNEXTLINE(misc-include-cleaner) + if (events & (POLLIN | POLLPRI | POLLHUP)) + ret |= EVENT_READ; + // NOLINTNEXTLINE(misc-include-cleaner) + if (events & POLLOUT) + ret |= EVENT_WRITE; + // NOLINTNEXTLINE(misc-include-cleaner) + if (events & (POLLERR | POLLNVAL)) + ret |= EVENT_ERROR; + return ret; + } + + uint32_t next_schedule_id() { + while (true) { + uint32_t ret = next_schedule_id_++; + if (ret) { + bool found = std::ranges::any_of( + scheduled_, + [ret](auto const& scheduled) { return scheduled.id_ == ret; }); + if (!found) + return ret; + } + } + } + + bool quit_{false}; + std::unordered_map<int, Entry> entry_; + uint32_t next_schedule_id_{1}; + std::deque<Scheduled> scheduled_; +}; + +} // namespace + +[[nodiscard]] +std::unique_ptr<Looper> create() { + return std::make_unique<LooperPoll>(); +} + +} // namespace looper |
