Use libuv for the controller's event loop

This commit is contained in:
patrick96 2021-02-17 19:48:46 +01:00 committed by Patrick Ziegler
parent 0bf45f3bd6
commit 249c3ec141
7 changed files with 118 additions and 138 deletions

View File

@ -98,6 +98,8 @@ endif()
find_package(Xcb ${XRANDR_VERSION} REQUIRED COMPONENTS RANDR) find_package(Xcb ${XRANDR_VERSION} REQUIRED COMPONENTS RANDR)
find_package(Xcb REQUIRED COMPONENTS ${XORG_EXTENSIONS}) find_package(Xcb REQUIRED COMPONENTS ${XORG_EXTENSIONS})
find_package(LibUV 1.10.0 REQUIRED)
# FreeBSD Support # FreeBSD Support
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
find_package(LibInotify REQUIRED) find_package(LibInotify REQUIRED)

View File

@ -0,0 +1,13 @@
# This module defines
# LIBUV_FOUND
# LIBUV_INCLUDE_DIR
# LIBUV_INCLUDE_DIRS
# LIBUV_LIBRARY
# LIBUV_LIBRARIES
# LIBUV_VERSION
find_package_impl("libuv" "LIBUV" "")
if(LIBUV_FOUND AND NOT TARGET LibUV::LibUV)
create_imported_target("LibUV::LibUV" "${LIBUV_INCLUDE_DIR}" "${LIBUV_LIBRARY}")
endif()

View File

@ -52,6 +52,9 @@ class controller
bool enqueue(event&& evt); bool enqueue(event&& evt);
bool enqueue(string&& input_data); bool enqueue(string&& input_data);
void conn_cb(int status, int events);
void ipc_cb(int status, int events);
protected: protected:
void read_events(); void read_events();
void process_eventqueue(); void process_eventqueue();
@ -84,8 +87,6 @@ class controller
unique_ptr<ipc> m_ipc; unique_ptr<ipc> m_ipc;
unique_ptr<inotify_watch> m_confwatch; unique_ptr<inotify_watch> m_confwatch;
array<unique_ptr<file_descriptor>, 2> m_queuefd{};
/** /**
* \brief State flag * \brief State flag
*/ */

View File

@ -169,6 +169,19 @@ namespace modules {
void sleep(chrono::duration<double> duration); void sleep(chrono::duration<double> duration);
template <class Clock, class Duration> template <class Clock, class Duration>
void sleep_until(chrono::time_point<Clock, Duration> point); void sleep_until(chrono::time_point<Clock, Duration> point);
/**
* Wakes up the module.
*
* It should be possible to interrupt any blocking operation inside a
* module using this function.
*
* In addition, after a wake up whatever was woken up should immediately
* check whether the module is still running.
*
* Modules that don't follow this, could stall the operation of whatever
* code called this function.
*/
void wakeup(); void wakeup();
string get_format() const; string get_format() const;
string get_output(); string get_output();

View File

@ -151,6 +151,7 @@ if(BUILD_LIBPOLY)
Cairo::CairoFC Cairo::CairoFC
moodycamel moodycamel
xpp xpp
LibUV::LibUV
) )
if (TARGET i3ipc++) if (TARGET i3ipc++)

View File

@ -1,5 +1,7 @@
#include "components/controller.hpp" #include "components/controller.hpp"
#include <uv.h>
#include <csignal> #include <csignal>
#include <utility> #include <utility>
@ -25,22 +27,9 @@
POLYBAR_NS POLYBAR_NS
array<int, 2> g_eventpipe{{-1, -1}};
sig_atomic_t g_reload{0}; sig_atomic_t g_reload{0};
sig_atomic_t g_terminate{0}; sig_atomic_t g_terminate{0};
void interrupt_handler(int signum) {
if (g_reload || g_terminate) {
return;
}
g_terminate = 1;
g_reload = (signum == SIGUSR1);
if (write(g_eventpipe[PIPE_WRITE], &g_terminate, 1) == -1) {
throw system_error("Failed to write to eventpipe");
}
}
/** /**
* Build controller instance * Build controller instance
*/ */
@ -70,23 +59,6 @@ controller::controller(connection& conn, signal_emitter& emitter, const logger&
m_swallow_limit = m_conf.deprecated("settings", "eventqueue-swallow", "throttle-output", m_swallow_limit); m_swallow_limit = m_conf.deprecated("settings", "eventqueue-swallow", "throttle-output", m_swallow_limit);
m_swallow_update = m_conf.deprecated("settings", "eventqueue-swallow-time", "throttle-output-for", m_swallow_update); m_swallow_update = m_conf.deprecated("settings", "eventqueue-swallow-time", "throttle-output-for", m_swallow_update);
if (pipe(g_eventpipe.data()) == 0) {
m_queuefd[PIPE_READ] = make_unique<file_descriptor>(g_eventpipe[PIPE_READ]);
m_queuefd[PIPE_WRITE] = make_unique<file_descriptor>(g_eventpipe[PIPE_WRITE]);
} else {
throw system_error("Failed to create event channel pipes");
}
m_log.trace("controller: Install signal handler");
struct sigaction act {};
memset(&act, 0, sizeof(act));
act.sa_handler = &interrupt_handler;
sigaction(SIGINT, &act, nullptr);
sigaction(SIGQUIT, &act, nullptr);
sigaction(SIGTERM, &act, nullptr);
sigaction(SIGUSR1, &act, nullptr);
sigaction(SIGALRM, &act, nullptr);
m_log.trace("controller: Setup user-defined modules"); m_log.trace("controller: Setup user-defined modules");
size_t created_modules{0}; size_t created_modules{0};
created_modules += setup_modules(alignment::LEFT); created_modules += setup_modules(alignment::LEFT);
@ -102,17 +74,6 @@ controller::controller(connection& conn, signal_emitter& emitter, const logger&
* Deconstruct controller * Deconstruct controller
*/ */
controller::~controller() { controller::~controller() {
m_log.trace("controller: Uninstall sighandler");
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGALRM, SIG_DFL);
if (g_reload) {
// Cause SIGUSR1 to be ignored until registered in the new polybar process
signal(SIGUSR1, SIG_IGN);
}
m_log.trace("controller: Detach signal receiver"); m_log.trace("controller: Detach signal receiver");
m_sig.detach(this); m_sig.detach(this);
@ -211,112 +172,101 @@ bool controller::enqueue(string&& input_data) {
return false; return false;
} }
void controller::conn_cb(int status, int events) {
// TODO handle negative status
if (m_connection.connection_has_error()) {
g_terminate = 1;
g_reload = 0;
uv_stop(uv_default_loop());
return;
}
shared_ptr<xcb_generic_event_t> evt{};
while ((evt = shared_ptr<xcb_generic_event_t>(xcb_poll_for_event(m_connection), free)) != nullptr) {
try {
m_connection.dispatch_event(evt);
} catch (xpp::connection_error& err) {
m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(err.code()));
} catch (const exception& err) {
m_log.err("Error in X event loop: %s", err.what());
}
}
}
void controller::ipc_cb(int status, int events) {
// TODO handle negative status
m_ipc->receive_message();
}
static void conn_cb_wrapper(uv_poll_t* handle, int status, int events) {
static_cast<controller*>(handle->data)->conn_cb(status, events);
}
static void ipc_cb_wrapper(uv_poll_t* handle, int status, int events) {
static_cast<controller*>(handle->data)->ipc_cb(status, events);
}
static void signal_cb_wrapper(uv_signal_t* handle, int signum) {
g_terminate = 1;
g_reload = (signum == SIGUSR1);
uv_stop(handle->loop);
}
static void confwatch_cb_wrapper(uv_fs_event_t* handle, const char* fname, int events, int status) {
// TODO handle error
std::cout << fname << std::endl;
g_terminate = 1;
g_reload = 1;
uv_stop(handle->loop);
}
/** /**
* Read events from configured file descriptors * Read events from configured file descriptors
*/ */
void controller::read_events() { void controller::read_events() {
m_log.info("Entering event loop (thread-id=%lu)", this_thread::get_id()); m_log.info("Entering event loop (thread-id=%lu)", this_thread::get_id());
int fd_connection{-1}; auto loop = uv_default_loop();
int fd_confwatch{-1};
int fd_ipc{-1};
vector<int> fds; auto conn_handle = std::make_unique<uv_poll_t>();
fds.emplace_back(*m_queuefd[PIPE_READ]); uv_poll_init(loop, conn_handle.get(), m_connection.get_file_descriptor());
fds.emplace_back((fd_connection = m_connection.get_file_descriptor())); conn_handle->data = this;
uv_poll_start(conn_handle.get(), UV_READABLE, conn_cb_wrapper);
std::vector<unique_ptr<uv_signal_t>> handles;
for (auto s : {SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGALRM}) {
auto signal_handle = std::make_unique<uv_signal_t>();
uv_signal_init(loop, signal_handle.get());
signal_handle->data = this;
uv_signal_start(signal_handle.get(), signal_cb_wrapper, s);
handles.push_back(std::move(signal_handle));
}
auto conf_handle = std::unique_ptr<uv_fs_event_t>(nullptr);
if (m_confwatch) { if (m_confwatch) {
m_log.trace("controller: Attach config watch"); conf_handle = std::make_unique<uv_fs_event_t>();
m_confwatch->attach(IN_MODIFY | IN_IGNORED); uv_fs_event_init(loop, conf_handle.get());
fds.emplace_back((fd_confwatch = m_confwatch->get_file_descriptor())); conf_handle->data = this;
uv_fs_event_start(conf_handle.get(), confwatch_cb_wrapper, m_confwatch->path().c_str(), 0);
} }
auto ipc_handle = std::unique_ptr<uv_poll_t>(nullptr);
if (m_ipc) { if (m_ipc) {
fds.emplace_back((fd_ipc = m_ipc->get_file_descriptor())); ipc_handle = std::make_unique<uv_poll_t>();
uv_poll_init(loop, ipc_handle.get(), m_ipc->get_file_descriptor());
ipc_handle->data = this;
uv_poll_start(ipc_handle.get(), UV_READABLE, ipc_cb_wrapper);
} }
while (!g_terminate) { uv_run(loop, UV_RUN_DEFAULT);
fd_set readfds{}; uv_loop_close(loop);
FD_ZERO(&readfds);
int maxfd{0};
for (auto&& fd : fds) {
FD_SET(fd, &readfds);
maxfd = std::max(maxfd, fd);
}
// Wait until event is ready on one of the configured streams
int events = select(maxfd + 1, &readfds, nullptr, nullptr, nullptr);
// Check for errors
if (events == -1) {
/*
* The Interrupt errno is generated when polybar is stopped, so it
* shouldn't generate an error message
*/
if (errno != EINTR) {
m_log.err("select failed in event loop: %s", strerror(errno));
}
break;
}
if (g_terminate || m_connection.connection_has_error()) {
break;
}
// Process event on the internal fd
if (m_queuefd[PIPE_READ] && FD_ISSET(static_cast<int>(*m_queuefd[PIPE_READ]), &readfds)) {
char buffer[BUFSIZ];
if (read(static_cast<int>(*m_queuefd[PIPE_READ]), &buffer, BUFSIZ) == -1) {
m_log.err("Failed to read from eventpipe (err: %s)", strerror(errno));
}
}
// Process event on the config inotify watch fd
unique_ptr<inotify_event> confevent;
if (fd_confwatch > -1 && FD_ISSET(fd_confwatch, &readfds) && (confevent = m_confwatch->await_match())) {
if (confevent->mask & IN_IGNORED) {
// IN_IGNORED: file was deleted or filesystem was unmounted
//
// This happens in some configurations of vim when a file is saved,
// since it is not actually issuing calls to write() but rather
// moves a file into the original's place after moving the original
// file to a different location (and subsequently deleting it).
//
// We need to re-attach the watch to the new file in this case.
fds.erase(
std::remove_if(fds.begin(), fds.end(), [fd_confwatch](int fd) { return fd == fd_confwatch; }), fds.end());
m_confwatch = inotify_util::make_watch(m_confwatch->path());
m_confwatch->attach(IN_MODIFY | IN_IGNORED);
fds.emplace_back((fd_confwatch = m_confwatch->get_file_descriptor()));
}
m_log.info("Configuration file changed");
g_terminate = 1;
g_reload = 1;
}
// Process event on the xcb connection fd
if (fd_connection > -1 && FD_ISSET(fd_connection, &readfds)) {
shared_ptr<xcb_generic_event_t> evt{};
while ((evt = shared_ptr<xcb_generic_event_t>(xcb_poll_for_event(m_connection), free)) != nullptr) {
try {
m_connection.dispatch_event(evt);
} catch (xpp::connection_error& err) {
m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(err.code()));
} catch (const exception& err) {
m_log.err("Error in X event loop: %s", err.what());
}
}
}
// Process event on the ipc fd
if (fd_ipc > -1 && FD_ISSET(fd_ipc, &readfds)) {
m_ipc->receive_message();
fds.erase(std::remove_if(fds.begin(), fds.end(), [fd_ipc](int fd) { return fd == fd_ipc; }), fds.end());
fds.emplace_back((fd_ipc = m_ipc->get_file_descriptor()));
}
}
} }
/** /**

View File

@ -1,11 +1,11 @@
#include "modules/xwindow.hpp" #include "modules/xwindow.hpp"
#include "drawtypes/label.hpp" #include "drawtypes/label.hpp"
#include "modules/meta/base.inl"
#include "utils/factory.hpp" #include "utils/factory.hpp"
#include "x11/atoms.hpp" #include "x11/atoms.hpp"
#include "x11/connection.hpp" #include "x11/connection.hpp"
#include "modules/meta/base.inl"
POLYBAR_NS POLYBAR_NS
namespace modules { namespace modules {
@ -138,6 +138,6 @@ namespace modules {
} }
return false; return false;
} }
} } // namespace modules
POLYBAR_NS_END POLYBAR_NS_END