jay/component/loop.cpp
2025-04-12 13:12:38 +03:00

186 lines
4.4 KiB
C++

// SPDX-FileCopyrightText: 2024 Yury Gubich <blue@macaw.me>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "loop.h"
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <csignal>
Loop::Loop(const Shared::Logger& logger):
Shared::Loggable(logger, {"Loop"}),
wakePipe(),
handlers(),
mutex(),
running(false)
{
if (pipe(wakePipe) < 0) {
fatal(std::string("pipe: ") + strerror(errno));
std::exit(1);
}
fcntl(wakePipe[0], F_SETFL, O_NONBLOCK);
fcntl(wakePipe[1], F_SETFL, O_NONBLOCK);
registerInstance(this);
}
Loop::~Loop() {
unregisterInstance(this);
close(wakePipe[0]);
close(wakePipe[1]);
}
void Loop::run() {
if (running.exchange(true)) {
warn("an attempt to run already running event loop, skipping");
return;
}
debug("entering the loop");
while (running) {
std::unique_lock lock(mutex);
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(wakePipe[0], &readfds);
int maxFD = wakePipe[0];
for (const std::pair<const int, Callback>& pair : handlers) {
FD_SET(pair.first, &readfds);
maxFD = std::max(maxFD, pair.first);
}
int result = select(maxFD + 1, &readfds, nullptr, nullptr, nullptr);
if (result < 0) {
drain(&readfds);
if (errno == EAGAIN) {
debug("woke up");
continue;
};
if (errno == EINTR) {
debug("interrupted");
continue;
};
fatal(std::string("select: ") + strerror(errno));
running = false;
break;
}
if (drain(&readfds))
continue;
Handlers copy;
for (const std::pair<const int, Callback>& pair : handlers)
if (FD_ISSET(pair.first, &readfds))
copy.insert(pair);
lock.unlock();
if (runCallbacks(copy))
continue;
}
debug("left the loop");
}
void Loop::stop() {
if (!running.exchange(false))
return;
debug("stopping the loop");
wake();
}
void Loop::addDescriptor(int descriptor, const Callback& handler) {
wake();
std::lock_guard lock(mutex);
if (!handlers.emplace(descriptor, handler).second)
warn("an attempt to add descriptor " + std::to_string(descriptor) + " for the second time");
}
void Loop::removeDescriptor(int descriptor) {
wake();
std::lock_guard lock(mutex);
if (handlers.erase(descriptor) == 0)
warn("an attempt to remove unknown descriptor " + std::to_string(descriptor));
}
void Loop::wake() {
char w = 'w';
if (write(wakePipe[1], &w, 1) < 0 && errno != EAGAIN)
error("failed to wake up event loop: " + std::string(strerror(errno)));
}
bool Loop::drain(fd_set* readfds) {
if (!FD_ISSET(wakePipe[0], readfds))
return false;
char buf[64];
while (true) {
ssize_t r = read(wakePipe[0], buf, sizeof(buf));
if (r > 0)
continue;
if (r == -1) {
if (errno == EAGAIN)
break;
error("failed to drain wake pipe: " + std::string(strerror(errno)));
break;
}
if (r == 0)
break; // pipe closed
}
return true;
}
bool Loop::runCallbacks(const Handlers& copy) {
for (const std::pair<const int, Callback>& pair : copy)
try {
pair.second();
return true;
} catch (const std::exception& e) {
error("exception in event loop from handler with file descriptor " + std::to_string(pair.first) + ":\n" + e.what());
} catch (...) {
error("unhandled throw in event loop from handler with file descriptor " + std::to_string(pair.first));
}
return false;
}
void Loop::registerInstance(Loop* loop) {
std::lock_guard lock(instanceMutex);
if (instances.size() == 0) {
std::signal(SIGINT, signalHandler);
std::signal(SIGTERM, signalHandler);
}
instances.insert(loop);
}
void Loop::unregisterInstance(Loop* loop) {
std::lock_guard lock(instanceMutex);
instances.erase(loop);
if (instances.size() == 0) {
std::signal(SIGINT, SIG_DFL);
std::signal(SIGTERM, SIG_DFL);
}
}
void Loop::signalHandler(int signum) {
for (Loop* loop : instances)
loop->stop();
}