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

Rewrite the template into a somewhat modern C++

parent 5750f452
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
......
project('esw04-epoll', 'cpp',
default_options: [
'warning_level=3'
'warning_level=3',
'cpp_std=c++17'
])
executable('epoll_server', ['src/main.cpp'])
......
#pragma once
#include <cstdint>
#include <cassert>
#include <unistd.h>
#include <sys/epoll.h>
#include <utility>
class EpollEntry {
protected:
private:
/** The file descriptor this epoll entry is polling on. */
int fd_;
/** Events this epoll entry reacts to. */
uint32_t events_;
protected:
/** The file descriptor this epoll entry is polling on. */
int fd_;
public:
EpollEntry(int fd, uint32_t events) : events_{events}, fd_{fd} {
assert(fd >= 0);
}
virtual ~EpollEntry() = default;
/** This callback is called by `EpollEventLoop` when the entry is removed. */
virtual void on_remove() {}
/**
* 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;
/*! What file descriptor does this epoll entry wrap? */
void fd(int i) {
fd_ = i;
}
[[nodiscard]] int fd() const {
assert(fd_ >= 0);
return fd_;
}
/*! To what events react? */
void events(uint32_t i) {
events_ = i;
}
[[nodiscard]] uint32_t events() const {
return events_;
}
virtual ~EpollEntry() = default;
};
/** A variant of `EpollEntry` which closes its file descriptor when destructed. */
class EpollEntryRAII : public EpollEntry {
public:
EpollEntryRAII(int fd, uint32_t events) : EpollEntry{fd, events} {}
~EpollEntryRAII() override {
// only close the FD if we haven't been moved from
if (fd_ != -1) {
close(fd_);
}
}
// we're destructing our FD – prevent copy
EpollEntryRAII(const EpollEntryRAII&) = delete;
EpollEntryRAII& operator=(const EpollEntryRAII&) = delete;
// allow move
EpollEntryRAII(EpollEntryRAII&& e) noexcept: EpollEntry(e) {
e.fd_ = -1; // make the moved-from FD invalid
}
EpollEntryRAII& operator=(EpollEntryRAII&& e) noexcept {
if (this != &e) {
// make the moved-from FD invalid
fd_ = std::exchange(e.fd_, -1);
}
return *this;
}
};
\ No newline at end of file
#pragma once
#include <cstdint>
#include <cstddef>
#include <unistd.h>
#include <sys/epoll.h>
#include <cstring>
#include <stdexcept>
#include <array>
#include "EpollEntry.hpp"
#include "error_util.hpp"
......@@ -22,10 +22,13 @@ public:
close(fd_);
}
// we have a file descriptor, prevent copy, allow move
EpollEventLoop(EpollEventLoop&) = delete;
EpollEventLoop(EpollEventLoop&&) noexcept = default;
// we have a file descriptor, prevent copy & move
EpollEventLoop(const EpollEventLoop&) = delete;
EpollEventLoop& operator=(const EpollEventLoop&) = delete;
EpollEventLoop(EpollEventLoop&&) = delete;
EpollEventLoop& operator=(EpollEventLoop&&) = delete;
/** Add an epoll entry to this epoll instance. */
void add(EpollEntry& e) const {
epoll_event ev{};
ev.events = e.events();
......@@ -33,13 +36,19 @@ public:
CHECK_ERROR(epoll_ctl(fd_, EPOLL_CTL_ADD, e.fd(), &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;
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_) {
......@@ -47,14 +56,15 @@ public:
}
}
/** Stops the event loop after all currently pending events are processed. */
void stop() {
should_stop_ = true;
}
private:
void wait_and_handle_events() const {
struct epoll_event events[MAX_EPOLL_EVENTS];
int n = CHECK_ERROR_EINTR(epoll_wait(fd_, events, MAX_EPOLL_EVENTS, -1));
std::array<epoll_event, MAX_EPOLL_EVENTS> events{};
int n = CHECK_ERROR_EINTR(epoll_wait(fd_, events.data(), events.size(), -1));
if (n == -1) {
return; // EINTR, received a signal
}
......@@ -66,4 +76,4 @@ private:
}
}
}
};
};
\ No newline at end of file
......@@ -2,39 +2,51 @@
#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"
class EpollPeriodicTimer : public EpollEntry {
template<typename Callback>
class EpollPeriodicTimer : public EpollEntryRAII {
private:
Callback cb_;
public:
explicit EpollPeriodicTimer(uint32_t timeMs) {
this->fd(CHECK_ERROR(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK)));
this->events(EPOLLIN);
/**
* @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)
: EpollEntryRAII(CHECK_ERROR(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK)), EPOLLIN),
cb_{cb} {
using namespace std::chrono;
// set up the timerfd interval
itimerspec its{};
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;
CHECK_ERROR(timerfd_settime(fd(), 0, &its, nullptr));
}
~EpollPeriodicTimer() override {
close(this->fd());
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 handle_event(uint32_t events) override {
uint64_t value;
if ((events & EPOLLERR) || (events & EPOLLHUP) || !(events & EPOLLIN)) {
return false;
} else {
CHECK_ERROR(read(this->fd(), &value, 8));
std::cout << "timer: " << this->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 <cstdint>
#include <iostream>
#include <unistd.h>
#include <string>
#include "EpollEntry.hpp"
template<typename Callback>
class EpollStdInLine : public EpollEntry {
private:
Callback cb_;
public:
EpollStdInLine() {
this->fd(STDIN_FILENO);
this->events(EPOLLIN);
}
explicit EpollStdInLine(Callback cb) : EpollEntry(STDIN_FILENO, EPOLLIN), cb_{cb} {}
bool handle_event(uint32_t events) override {
std::string line;
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
#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.add(tim1);
ep.add(tim2);
ep.add(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.remove(tim1);
ep.remove(tim2);
ep.remove(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