// SPDX-FileCopyrightText: 2024 Yury Gubich // SPDX-License-Identifier: GPL-3.0-or-later #include "loop.h" #include #include #include #include #include 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& 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& 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& 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(); }