backend: poll/wake impl

This commit is contained in:
Theis Pieter Hollebeek 2026-03-25 14:08:43 +01:00
parent bd09668dc1
commit f81b0811a8
9 changed files with 218 additions and 114 deletions

View File

@ -0,0 +1,12 @@
#pragma once
#include <cerrno>
#include <cstring>
#include <format>
#include <string_view>
namespace mst {
auto inline errno_shim(std::string_view message) -> std::string
{
return std::format("{} ({})", message, strerror(errno));
}
}

View File

@ -0,0 +1,53 @@
#include "event_loop.hpp"
#include "errno_shim.hpp"
#include "server.hpp"
#include <print>
#include <sys/epoll.h>
namespace mst::event {
auto Manager::start() -> Result<void>
{
epoll_event events[128] = { };
while (true) {
auto events_len = ::epoll_wait(this->epoll_fd, events, 128, -1);
if (events_len < 0) {
return std::unexpected(errno_shim("could not poll"));
}
for (int i = 0; i < events_len; ++i) {
auto event = (event::Event*)events[i].data.ptr;
switch (event->variant) {
case event::Server: {
auto ptr = (mst::Server*)event->data;
auto res = ptr->wake(*this);
if (!res) {
return std::unexpected(res.error());
}
break;
}
case event::Client: {
std::println("client call scheduled");
auto ptr = (mst::Client*)event->data;
auto res = ptr->wake();
if (!res) {
return std::unexpected(res.error());
}
break;
}
}
}
}
}
auto Manager::create() -> Result<Manager>
{
auto epoll_fd = ::epoll_create1(0);
if (epoll_fd < 0) {
std::unexpected(mst::errno_shim("could not create epoll"));
}
return Manager(epoll_fd);
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "result.hpp"
namespace mst::event {
enum Variant { Server, Client };
struct Event {
Variant variant;
int fd;
void* data;
};
template <typename Data> auto make_event(Variant variant, Data data) -> Event*
{
auto event = (Event*)malloc(sizeof(Event));
auto ptr = (Data*)std::malloc(sizeof(Data));
*ptr = data;
event->variant = variant;
event->data = ptr;
return event;
}
class Manager {
public:
auto start() -> Result<void>;
int epoll_fd;
static auto create() -> Result<Manager>;
private:
Manager(int epoll_fd)
: epoll_fd(epoll_fd) { };
};
}

View File

@ -1,6 +1,6 @@
#include "tcp.hpp" #include "event_loop.hpp"
#include "server.hpp"
#include <print> #include <print>
#include <stdio.h>
#include <sys/select.h> #include <sys/select.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
@ -9,15 +9,15 @@
int main(void) int main(void)
{ {
auto x = mst::TcpListener::bind("0.0.0.0", PORT); auto mgr = mst::event::Manager::create().value();
auto x = mst::Server::bind(mgr, "0.0.0.0", PORT);
if (!x) { if (!x) {
std::println("{}", x.error()); std::println("{}", x.error());
return 1; return 1;
} }
std::println("starting"); std::println("starting");
auto listener = x.value();
{ {
auto x = listener.loop(); auto x = mgr.start();
} }
return 0; return 0;
} }

5
backend/src/result.hpp Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include <expected>
#include <string>
template <typename T> using Result = std::expected<T, std::string>;

View File

@ -0,0 +1,61 @@
#include "server.hpp"
#include "errno_shim.hpp"
#include "event_loop.hpp"
#include <print>
#include <sys/epoll.h>
namespace mst {
auto Client::wake() -> Result<void>
{
uint8_t buffer[128] = { };
auto x = this->connection.read(buffer, 128);
if (!x) {
return std::unexpected(x.error());
}
auto bytes_read = x.value();
for (size_t i = 0; i < bytes_read; ++i) {
std::println("{:c}", buffer[i]);
}
return { };
}
auto Server::bind(mst::event::Manager& mgr, const std::string& host,
uint16_t port) -> Result<void>
{
auto x = TcpListener::bind(host, port);
if (!x) {
return std::unexpected(x.error());
}
auto listener = x.value();
auto context = event::make_event(event::Server, Server(listener));
auto poll_event
= epoll_event { .events = EPOLLIN, .data = { .ptr = context } };
if (::epoll_ctl(mgr.epoll_fd, EPOLL_CTL_ADD, listener.fd, &poll_event)
< 0) {
return std::unexpected(errno_shim("could not add listener to epoll"));
}
return { };
}
auto Server::wake(event::Manager& mgr) -> Result<void>
{
auto x = this->listener.accept();
auto connection = x.value();
auto context
= event::make_event(event::Client, mst::Client(*this, connection));
auto poll_event
= epoll_event { .events = EPOLLIN, .data = { .ptr = context } };
if (::epoll_ctl(mgr.epoll_fd, EPOLL_CTL_ADD, connection.fd, &poll_event)
< 0) {
return std::unexpected(
mst::errno_shim("could not add connection to epoll"));
}
return { };
}
}

View File

@ -1 +1,36 @@
#pragma once #pragma once
#include "event_loop.hpp"
#include "tcp.hpp"
namespace mst {
class Server {
public:
auto wake(mst::event::Manager& mgr) -> Result<void>;
static auto bind(mst::event::Manager& mgr, const std::string& host,
uint16_t port) -> Result<void>;
private:
TcpListener listener;
Server(TcpListener listener)
: listener(listener)
{
}
};
class Client {
public:
auto wake() -> Result<void>;
Client(Server&, TcpConnection connection)
: connection(connection)
{
}
private:
TcpConnection connection;
};
}

View File

@ -1,50 +1,19 @@
#include "tcp.hpp" #include "tcp.hpp"
#include "errno_shim.hpp"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <cstdlib> #include <cstdlib>
#include <errno.h>
#include <expected> #include <expected>
#include <format>
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <string> #include <string>
#include <string_view>
#include <sys/epoll.h> #include <sys/epoll.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
namespace mst { namespace mst {
auto event::make_listener_event(int fd) -> Event*
{
auto ev = (Event*)malloc(sizeof(Event));
ev->variant = Listener;
ev->data.listener_fd = fd;
return ev;
}
auto event::make_connection_event(TcpConnection connection) -> Event*
{
auto ev = (Event*)malloc(sizeof(Event));
auto ptr = (TcpConnection*)std::malloc(sizeof(TcpConnection));
*ptr = connection;
ev->variant = Connection;
ev->data.connection = ptr;
return ev;
}
auto errno_shim(std::string_view message) -> std::string
{
auto x = strerror(errno);
return std::format("{} ({})", message, x);
}
auto TcpConnection::write(uint8_t* buffer, size_t len) -> Result<size_t> auto TcpConnection::write(uint8_t* buffer, size_t len) -> Result<size_t>
{ {
ssize_t bytes_written = ::write(this->fd, buffer, len); ssize_t bytes_written = ::write(this->fd, buffer, len);
@ -86,61 +55,18 @@ auto TcpListener::bind(const std::string& host, uint16_t port)
return std::unexpected(errno_shim("could not listen")); return std::unexpected(errno_shim("could not listen"));
} }
auto epoll_fd = ::epoll_create1(0); return TcpListener(address, socket_fd);
auto context = event::make_listener_event(socket_fd);
auto event = epoll_event { .events = EPOLLIN, .data = { .ptr = context } };
if (::epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event) < 0) {
return std::unexpected(errno_shim("could not connect to epoll"));
}
return TcpListener(epoll_fd, address);
} }
auto TcpListener::start() -> Result<void> auto TcpListener::accept() -> Result<TcpConnection>
{ {
epoll_event events[128] = { }; socklen_t size = sizeof(this->address);
while (true) { int client = ::accept(this->fd, (struct sockaddr*)&address, &size);
if (client < 0) {
auto events_len = ::epoll_wait(this->epoll_fd, events, 128, -1); return std::unexpected(errno_shim("could not accept"));
if (events_len < 0) {
return std::unexpected(errno_shim("could not poll"));
}
for (int i = 0; i < events_len; ++i) {
auto event = (event::Event*)events[i].data.ptr;
switch (event->variant) {
case event::Listener: {
socklen_t size = sizeof(address);
int client = ::accept(
events[0].data.fd, (struct sockaddr*)&address, &size);
if (client < 0) {
return std::unexpected(errno_shim("could not accept"));
}
auto context = event::make_connection_event(
TcpConnection(*this, client));
auto poll_event = epoll_event { .events = EPOLLIN,
.data = { .ptr = context } };
if (::epoll_ctl(
epoll_fd, EPOLL_CTL_ADD, client, &poll_event)
< 0) {
return std::unexpected(
errno_shim("could not add connection to epoll"));
}
break;
}
case event::Connection: {
event->data.connection.wake();
break;
}
}
}
} }
return TcpConnection(*this, client);
} }
} }

View File

@ -1,32 +1,13 @@
#pragma once #pragma once
#include "result.hpp"
#include <cstdint> #include <cstdint>
#include <expected>
#include <netinet/in.h> #include <netinet/in.h>
#include <string> #include <string>
#include <unistd.h> #include <unistd.h>
namespace mst { namespace mst {
template <typename T> using Result = std::expected<T, std::string>;
class TcpConnection;
namespace event {
enum Variant { Listener, Connection };
typedef union {
int listener_fd;
TcpConnection* connection;
} Data;
struct Event {
Variant variant;
Data data;
};
auto make_listener_event(int fd) -> Event*;
auto make_connection_event(TcpConnection connection) -> Event*;
}
class TcpListener; class TcpListener;
class TcpConnection { class TcpConnection {
@ -36,9 +17,7 @@ public:
TcpConnection(TcpListener&, int fd) TcpConnection(TcpListener&, int fd)
: fd(fd) { }; : fd(fd) { };
private:
int fd; int fd;
event::Event event;
}; };
class TcpListener { class TcpListener {
@ -46,13 +25,13 @@ class TcpListener {
public: public:
static auto bind(const std::string& host, uint16_t port) static auto bind(const std::string& host, uint16_t port)
-> Result<TcpListener>; -> Result<TcpListener>;
auto start() -> Result<void>; auto accept() -> Result<TcpConnection>;
int fd;
private: private:
TcpListener(int epoll_fd, sockaddr_in address) TcpListener(sockaddr_in address, int fd)
: epoll_fd(epoll_fd) : fd(fd)
, address(address) { }; , address(address) { };
int epoll_fd;
sockaddr_in address; sockaddr_in address;
}; };
} }