Skip to content
Snippets Groups Projects
Commit 8d3cbbbe authored by Matej Kafka's avatar Matej Kafka
Browse files

Rewrite the template into a somewhat modern C++

parent 07a4f972
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,7 @@ MIT License
Copyright (c) 2022 Joel Matejka
Copyright (c) 2023 Jiri Vlasak
Copyright (c) 2023 Matej Kafka
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
......@@ -3,7 +3,8 @@ project('esw04-epoll', 'cpp',
'warning_level=3',
'werror=true',
'optimization=2',
'debug=true'
'debug=true',
'cpp_std=c++17'
])
executable('epoll_server', ['src/main.cpp'])
......
#pragma once
#include <stdint.h>
#include <cstdint>
#include <cassert>
#include <unistd.h>
#include <sys/epoll.h>
class EpollEntry {
private:
int fd;
uint32_t events;
/** Events this epoll entry reacts to. */
uint32_t events_;
/** Indicates if `fd_` should be closed when this epoll entry is destructed. */
const bool should_close_fd_;
protected:
/** The file descriptor this epoll entry is polling on. */
int fd_;
public:
/*! \brief How to react to epoll events?
*
* Return false if this should be removed from epoll instance.
*/
virtual bool handleEvent(uint32_t events) = 0;
/*! What file descriptor does this epoll entry wrap? */
void set_fd(int i) {
fd = i;
EpollEntry(int fd, uint32_t events, bool should_close_fd = true)
: events_{events}, should_close_fd_{should_close_fd}, fd_{fd} {
assert(fd >= 0);
}
int get_fd() const {
return fd;
virtual ~EpollEntry() {
if (should_close_fd_) {
close(fd_);
}
}
/*! To what events react? */
void set_events(uint32_t i) {
events = i;
}
// we have a file descriptor, prevent copy, allow move
EpollEntry(EpollEntry&) = delete;
EpollEntry(EpollEntry&&) noexcept = default;
/** This callback is called by `EpollEventLoop` when the entry is removed. */
virtual void on_remove() {}
uint32_t get_events() const {
return events;
/**
* Invoked when the epoll entry is signalled.
* Returns false if this entry should be removed from the invoking `EpollInstance`.
*/
virtual bool handle_event(uint32_t events) = 0;
[[nodiscard]] int fd() const {
return fd_;
}
virtual ~EpollEntry() = default;
};
[[nodiscard]] uint32_t events() const {
return events_;
}
};
\ No newline at end of file
#pragma once
#include <stdint.h>
#include "EpollEntry.hpp"
#include "error_util.hpp"
#include <cstddef>
#include <cstring>
#include <unistd.h>
#include <system_error>
#include <sys/epoll.h>
#include <cstring>
#include <stdexcept>
#include "EpollEntry.hpp"
#include <array>
class EpollEventLoop {
public:
static constexpr size_t MAX_EPOLL_EVENTS = 64;
private:
int fd;
bool should_stop = false;
int fd_;
bool should_stop_ = false;
public:
EpollEventLoop() {
this->fd = epoll_create1(0);
if (this->fd == -1) {
throw std::runtime_error(
std::string("epoll_create1: ") + std::strerror(errno));
}
}
EpollEventLoop() : fd_{CHECK_ERROR(epoll_create1(0))} {}
~EpollEventLoop() {
close(this->fd);
close(fd_);
}
void registerEpollEntry(EpollEntry& e) const {
struct epoll_event ev;
memset(&ev, 0, sizeof(ev));
// we have a file descriptor, prevent copy, allow move
EpollEventLoop(EpollEventLoop&) = delete;
EpollEventLoop(EpollEventLoop&&) noexcept = default;
ev.events = e.get_events();
/** Add an epoll entry to this epoll instance. */
void add(EpollEntry& e) const {
epoll_event ev{};
ev.events = e.events();
ev.data.ptr = &e;
if (epoll_ctl(this->fd, EPOLL_CTL_ADD, e.get_fd(), &ev) == -1) {
throw std::runtime_error(
std::string("epoll_ctl: ") + std::strerror(errno));
}
CHECK_ERROR(epoll_ctl(fd_, EPOLL_CTL_ADD, e.fd(), &ev));
}
void unregisterEpollEntry(EpollEntry& e) const {
struct epoll_event ev;
memset(&ev, 0, sizeof(ev));
/**
* Remove an epoll entry from this epoll instance. Invokes the `on_remove()`
* callback of the entry.
*/
void remove(EpollEntry& e) const {
epoll_event ev{};
ev.events = 0;
ev.data.ptr = &e;
if (epoll_ctl(this->fd, EPOLL_CTL_DEL, e.get_fd(), &ev) == -1) {
throw std::runtime_error(
std::string("epoll_ctl: ") + std::strerror(errno));
}
CHECK_ERROR(epoll_ctl(fd_, EPOLL_CTL_DEL, e.fd(), &ev));
e.on_remove();
}
/** Runs the event loop until `.stop()` is called. */
void run() {
should_stop = false;
while (!should_stop) {
waitAndHandleEvents();
should_stop_ = false;
while (!should_stop_) {
wait_and_handle_events();
}
}
/** Stops the event loop after all currently pending events are processed. */
void stop() {
should_stop = true;
should_stop_ = true;
}
private:
void waitAndHandleEvents() const {
struct epoll_event events[MAX_EPOLL_EVENTS];
int n = epoll_wait(this->fd, events, MAX_EPOLL_EVENTS, -1);
void wait_and_handle_events() const {
std::array<epoll_event, MAX_EPOLL_EVENTS> events{};
int n = CHECK_ERROR_EINTR(epoll_wait(fd_, events.data(), events.size(), -1));
if (n == -1) {
throw std::runtime_error(
std::string("epoll_wait: ") + std::strerror(errno));
return; // received a signal
}
for (int i = 0; i < n; i++) {
EpollEntry* e = static_cast<EpollEntry*>(events[i].data.ptr);
if (!e->handleEvent(events[i].events)) {
this->unregisterEpollEntry(*e);
auto* e = static_cast<EpollEntry*>(events[i].data.ptr);
if (!e->handle_event(events[i].events)) {
remove(*e);
}
}
}
};
};
\ No newline at end of file
#pragma once
#include <stdint.h>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <sys/timerfd.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <chrono>
#include <type_traits>
#include "EpollEntry.hpp"
#include "error_util.hpp"
template<typename Callback>
class EpollPeriodicTimer : public EpollEntry {
public:
EpollPeriodicTimer(uint32_t timeMs) {
struct itimerspec its;
memset(&its, 0, sizeof(its));
this->set_fd(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK));
if (this->get_fd() == -1) {
throw std::runtime_error(
std::string("timerfd_create: ") + std::strerror(errno));
}
its.it_interval.tv_sec = timeMs / 1000;
its.it_interval.tv_nsec = (timeMs % 1000) * 1000000;
its.it_value.tv_sec = timeMs / 1000;
its.it_value.tv_nsec = (timeMs % 1000) * 1000000;
private:
Callback cb_;
if (timerfd_settime(this->get_fd(), 0, &its, NULL)) {
throw std::runtime_error(
std::string("timerfd_settime: ") + std::strerror(errno));
}
this->set_events(EPOLLIN);
}
~EpollPeriodicTimer() {
close(this->get_fd());
public:
/**
* @param timeout - timer period
* @param cb - callback, invoked whenever the timer expires; it is invoked either
* as `cb(expiration_count)` or `cb()`, whichever is supported
*/
EpollPeriodicTimer(std::chrono::nanoseconds timeout, Callback cb)
: EpollEntry(CHECK_ERROR(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK)), EPOLLIN),
cb_{cb} {
using namespace std::chrono;
// set up the timerfd interval
itimerspec its{};
auto sec_part = duration_cast<seconds>(timeout);
its.it_interval.tv_sec = sec_part.count();
its.it_interval.tv_nsec = nanoseconds(timeout - sec_part).count();
its.it_value = its.it_interval;
CHECK_ERROR(timerfd_settime(fd_, 0, &its, nullptr));
}
bool handleEvent(uint32_t events) {
uint64_t value;
bool handle_event(uint32_t events) override {
if ((events & EPOLLERR) || (events & EPOLLHUP) || !(events & EPOLLIN)) {
return false;
} else {
auto len = read(this->get_fd(), &value, 8);
(void)len;
std::cout << "timer: " << this->get_fd() << std::endl;
uint64_t expiration_count;
check_error("timerfd read", read(fd_, &expiration_count, 8));
// support lambda without arguments
if constexpr (std::is_invocable_v<Callback, uint64_t>) {
cb_(expiration_count);
} else {
cb_();
}
return true;
}
}
};
};
\ No newline at end of file
#pragma once
#include <stdint.h>
#include <iostream>
#include <unistd.h>
#include <string>
#include "EpollEntry.hpp"
#include <iostream>
template<typename Callback>
class EpollStdInLine : public EpollEntry {
private:
Callback cb_;
public:
EpollStdInLine() {
this->set_fd(STDIN_FILENO);
this->set_events(EPOLLIN);
}
explicit EpollStdInLine(Callback cb) : EpollEntry(STDIN_FILENO, EPOLLIN, false), cb_{cb} {}
bool handleEvent(uint32_t events) {
std::string line;
bool handle_event(uint32_t events) override {
if ((events & EPOLLERR) || (events & EPOLLHUP) || !(events & EPOLLIN)) {
return false;
} else {
std::string line;
std::getline(std::cin, line);
std::cout << "stdin line: " << line << std::endl;
cb_(line);
return true;
}
}
};
};
\ No newline at end of file
#pragma once
#include <system_error>
#include <cerrno>
#include <string>
static inline std::system_error system_error_from_errno(const std::string& fn) {
throw std::system_error(std::error_code(errno, std::system_category()), fn);
}
template<typename T>
static inline T check_error(const std::string& fn, T return_value, bool allow_eintr = false) {
if (return_value == -1 && (!allow_eintr || errno != EINTR)) {
throw system_error_from_errno(fn);
}
return return_value;
}
#define CHECK_ERROR(fn) check_error(#fn, fn)
#define CHECK_ERROR_EINTR(fn) check_error(#fn, fn, true)
\ No newline at end of file
#include "EpollEventLoop.hpp"
#include "EpollStdInLine.hpp"
#include "EpollPeriodicTimer.hpp"
#include <iostream>
int main() {
EpollEventLoop ep{};
EpollPeriodicTimer tim1{1000};
EpollPeriodicTimer tim2{1500};
EpollStdInLine sin{};
EpollEventLoop loop{};
ep.registerEpollEntry(tim1);
ep.registerEpollEntry(tim2);
ep.registerEpollEntry(sin);
using namespace std::chrono_literals;
EpollPeriodicTimer tim1(1000ms, [&] { std::cout << "timer: 4" << std::endl; });
EpollPeriodicTimer tim2(1500ms, [&] { std::cout << "timer: 5" << std::endl; });
EpollStdInLine sin([&](const std::string& line) {
std::cout << "stdin: " << line << std::endl;
});
ep.run();
loop.add(sin);
loop.add(tim1);
loop.add(tim2);
ep.unregisterEpollEntry(tim1);
ep.unregisterEpollEntry(tim2);
ep.unregisterEpollEntry(sin);
loop.run();
loop.remove(tim2);
loop.remove(tim1);
loop.remove(sin);
return 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment