refactor: Signaling

This commit is contained in:
Michael Carlberg 2016-12-01 08:41:49 +01:00
parent 9f8dabfc8d
commit c6540a8950
11 changed files with 227 additions and 203 deletions

View File

@ -1,5 +1,7 @@
#pragma once
#include <csignal>
#include "common.hpp"
#include "components/config.hpp"
#include "components/eventloop.hpp"
@ -48,6 +50,8 @@ class controller {
void wait_for_signal();
void wait_for_xevent();
void wait_for_eventloop();
void wait_for_configwatch();
void bootstrap_modules();
@ -56,6 +60,22 @@ class controller {
void on_unrecognized_action(string input);
void on_update();
private:
enum class thread_role {
EVENT_QUEUE,
EVENT_QUEUE_X,
IPC_LISTENER,
CONF_LISTENER,
};
bool is_thread_joinable(thread_role&& role) {
if (m_threads.find(role) == m_threads.end()) {
return false;
} else {
return m_threads[role].joinable();
}
}
private:
connection& m_connection;
registry m_registry{m_connection};
@ -70,10 +90,9 @@ class controller {
stateflag m_waiting{false};
stateflag m_trayactivated{false};
sigset_t m_blockmask;
sigset_t m_waitmask;
sigset_t m_ignmask;
vector<thread> m_threads;
map<thread_role, thread> m_threads;
inotify_util::watch_t& m_confwatch;
command_util::command_t m_command;

View File

@ -25,6 +25,11 @@ struct event {
char data[256]{'\0'};
};
struct quit_event {
const uint8_t type{static_cast<uint8_t>(event_type::QUIT)};
bool reload{false};
};
class eventloop {
public:
/**
@ -35,8 +40,7 @@ class eventloop {
using duration_t = chrono::duration<double, std::milli>;
explicit eventloop(const logger& logger, const config& config);
~eventloop() noexcept;
~eventloop();
void start();
void wait();
@ -64,7 +68,7 @@ class eventloop {
void on_update();
void on_input(char* input);
void on_check();
void on_quit();
void on_quit(const quit_event& quit);
private:
const logger& m_log;
@ -80,11 +84,6 @@ class eventloop {
*/
modulemap_t m_modules;
/**
* @brief Lock used when accessing the module map
*/
std::mutex m_modulelock;
/**
* @brief Flag to indicate current run state
*/

View File

@ -34,7 +34,7 @@ class screen : public xpp::event::sink<evt::randr_screen_change_notify> {
const config& m_conf;
xcb_window_t m_root;
xcb_window_t m_proxy;
xcb_window_t m_proxy{XCB_NONE};
struct size m_size{0U, 0U};
bool m_sigraised{false};

View File

@ -2,6 +2,7 @@
#include "common.hpp"
#include "components/eventloop.hpp"
#include "utils/functional.hpp"
POLYBAR_NS
@ -17,13 +18,34 @@ enum class attribute : uint8_t;
/**
* @TODO: Allow multiple signal handlers
* @TODO: Encapsulate signals
*/
namespace g_signals {
/**
* Helper used to create no-op "callbacks"
*/
template <typename... T>
static callback<T...> noop = [](T...) {};
/**
* Signals used to communicate with the event loop
*/
namespace event {
extern callback<const eventloop::entry_t&> enqueue;
extern callback<const eventloop::entry_t&> enqueue_delayed;
}
/**
* Signals used to communicate with the bar window
*/
namespace bar {
extern callback<string> action_click;
extern callback<const bool> visibility_change;
}
/**
* Signals used to communicate with the input parser
*/
namespace parser {
extern callback<const uint32_t> background_change;
extern callback<const uint32_t> foreground_change;
@ -42,6 +64,9 @@ namespace g_signals {
extern callback<const char*, const size_t> string_write;
}
/**
* Signals used to communicate with the tray manager
*/
namespace tray {
extern callback<const uint16_t> report_slotcount;
}

View File

@ -25,7 +25,7 @@ namespace inotify_util {
~inotify_watch() noexcept;
void attach(int mask = IN_MODIFY);
void remove();
void remove(bool force = false);
bool poll(int wait_ms = 1000);
unique_ptr<event_t> get_event();
bool await_match();

View File

@ -69,7 +69,7 @@ void config::copy_inherited() {
m_logger.trace("config: Copying missing params (sub=\"%s\", base=\"%s\")", section.first, inherit);
// Iterate the the base and copy the parameters
// Iterate the base and copy the parameters
// that hasn't been defined for the sub-section
for (auto&& base_param : *base_section) {
if (!section.second.get_child_optional(base_param.first)) {

View File

@ -1,5 +1,4 @@
#include <chrono>
#include <csignal>
#include <mutex>
#include "components/bar.hpp"
@ -74,35 +73,42 @@ di::injector<unique_ptr<controller>> configure_controller(watch_t& confwatch) {
* threads and spawned processes
*/
controller::~controller() {
pthread_sigmask(SIG_UNBLOCK, &m_blockmask, nullptr);
sigset_t sig;
sigemptyset(&sig);
sigaddset(&sig, SIGALRM);
pthread_sigmask(SIG_BLOCK, &sig, nullptr);
if (m_command) {
m_log.info("Terminating running shell command");
m_command.reset();
}
if (m_eventloop) {
m_log.info("Deconstructing eventloop");
m_eventloop.reset();
}
if (m_ipc) {
m_log.info("Deconstructing ipc");
m_ipc.reset();
}
if (m_bar) {
m_log.info("Deconstructing bar");
m_bar.reset();
}
m_log.info("Interrupting X event loop");
m_connection.send_dummy_event(m_connection.root());
if (m_ipc) {
m_log.info("Deconstructing ipc");
m_ipc.reset();
}
if (!m_threads.empty()) {
m_log.info("Joining active threads");
for (auto&& thread : m_threads) {
if (thread.joinable()) {
thread.join();
}
if (m_eventloop) {
m_log.info("Deconstructing eventloop");
m_eventloop.reset();
}
if (!m_writeback) {
m_log.info("Interrupting X event loop");
m_connection.send_dummy_event(m_connection.root());
}
m_log.info("Joining active threads");
for (auto&& th : m_threads) {
if (th.second.joinable()) {
th.second.join();
}
}
@ -115,9 +121,16 @@ controller::~controller() {
}
/**
* Setup X environment
* Initialize components and setup X environment
*/
void controller::bootstrap(bool writeback, bool dump_wmname) {
// Add all signals to the block mask
sigfillset(&m_blockmask);
if (pthread_sigmask(SIG_BLOCK, &m_blockmask, nullptr) == -1) {
throw system_error("Failed to block signals");
}
m_writeback = writeback;
m_log.trace("controller: Initialize X atom cache");
@ -171,37 +184,57 @@ void controller::run() {
m_log.info("Starting application");
m_running = true;
install_sigmask();
install_confwatch();
if (m_confwatch && !m_writeback) {
m_threads[thread_role::CONF_LISTENER] = thread(&controller::wait_for_configwatch, this);
}
// Start ipc receiver if its enabled
if (m_conf.get<bool>(m_conf.bar_section(), "enable-ipc", false)) {
m_threads.emplace_back(thread(&ipc::receive_messages, m_ipc.get()));
m_threads[thread_role::IPC_LISTENER] = thread(&ipc::receive_messages, m_ipc.get());
}
// Listen for X events in separate thread
if (!m_writeback) {
m_threads.emplace_back(thread(&controller::wait_for_xevent, this));
m_threads[thread_role::EVENT_QUEUE_X] = thread(&controller::wait_for_xevent, this);
}
// Wait for term signal in separate thread
m_threads.emplace_back(thread(&controller::wait_for_signal, this));
// Start event loop
if (m_eventloop) {
m_eventloop->start();
m_eventloop->wait();
m_threads[thread_role::EVENT_QUEUE] = thread(&controller::wait_for_eventloop, this);
}
// Wake up signal thread
if (m_waiting) {
kill(getpid(), SIGTERM);
}
m_log.trace("controller: Wait for signal");
m_waiting = true;
uninstall_sigmask();
uninstall_confwatch();
sigemptyset(&m_waitmask);
sigaddset(&m_waitmask, SIGINT);
sigaddset(&m_waitmask, SIGQUIT);
sigaddset(&m_waitmask, SIGTERM);
sigaddset(&m_waitmask, SIGUSR1);
sigaddset(&m_waitmask, SIGALRM);
int caught_signal = 0;
sigwait(&m_waitmask, &caught_signal);
m_running = false;
m_waiting = false;
if (caught_signal == SIGUSR1) {
m_reload = true;
}
m_log.warn("Termination signal received, shutting down...");
m_log.trace("controller: Caught signal %d", caught_signal);
if (m_eventloop) {
m_log.trace("controller: Stopping event loop");
m_eventloop->stop();
}
if (!m_writeback && m_confwatch) {
m_log.trace("controller: Removing config watch");
m_confwatch->remove(true);
}
}
/**
@ -211,110 +244,26 @@ bool controller::completed() {
return !m_running && !m_reload;
}
/**
* Set signal mask for the current and future threads
*/
void controller::install_sigmask() {
m_log.trace("controller: Set pthread_sigmask to block term signals");
sigemptyset(&m_waitmask);
sigaddset(&m_waitmask, SIGINT);
sigaddset(&m_waitmask, SIGQUIT);
sigaddset(&m_waitmask, SIGTERM);
sigaddset(&m_waitmask, SIGUSR1);
if (pthread_sigmask(SIG_BLOCK, &m_waitmask, nullptr) == -1) {
throw system_error();
}
sigemptyset(&m_ignmask);
sigaddset(&m_ignmask, SIGPIPE);
if (pthread_sigmask(SIG_BLOCK, &m_ignmask, nullptr) == -1) {
throw system_error();
}
}
/**
* Uninstall sigmask to allow term signals
*/
void controller::uninstall_sigmask() {
m_log.trace("controller: Set pthread_sigmask to unblock term signals");
if (pthread_sigmask(SIG_UNBLOCK, &m_waitmask, nullptr) == -1) {
throw system_error();
}
}
/**
* Listen for changes to the config file
*/
void controller::install_confwatch() {
if (!m_running) {
return;
}
if (!m_confwatch) {
m_log.trace("controller: Config watch not set, skip...");
return;
}
m_threads.emplace_back([&] {
try {
if (!m_running) {
return;
}
m_log.trace("controller: Attach config watch");
m_confwatch->attach(IN_MODIFY);
m_log.trace("controller: Wait for config file inotify event");
if (m_confwatch->await_match() && m_running) {
m_log.info("Configuration file changed");
kill(getpid(), SIGUSR1);
}
} catch (const system_error& err) {
m_log.err(err.what());
m_log.trace("controller: Reset config watch");
m_confwatch.reset();
}
});
}
/**
* Remove the config inotify watch
*/
void controller::uninstall_confwatch() {
void controller::wait_for_configwatch() {
try {
if (m_confwatch) {
m_log.info("Removing config watch");
m_confwatch->remove();
m_log.trace("controller: Attach config watch");
m_confwatch->attach(IN_MODIFY);
m_log.trace("controller: Wait for config file inotify event");
if (m_confwatch->await_match() && m_running) {
m_log.info("Configuration file changed");
kill(getpid(), SIGUSR1);
}
} catch (const system_error& err) {
m_log.err(err.what());
m_log.trace("controller: Reset config watch");
m_confwatch.reset();
}
}
/**
* Wait for termination signal
*/
void controller::wait_for_signal() {
m_log.trace("controller: Wait for signal");
m_waiting = true;
int caught_signal = 0;
sigwait(&m_waitmask, &caught_signal);
m_log.warn("Termination signal received, shutting down...");
m_log.trace("controller: Caught signal %d", caught_signal);
if (m_eventloop) {
m_eventloop->stop();
}
m_reload = (caught_signal == SIGUSR1);
m_waiting = false;
}
/**
* Wait for X events and forward them to
* the event registry
@ -341,6 +290,21 @@ void controller::wait_for_xevent() {
}
}
/**
* Start event loop and wait for it to finish
*/
void controller::wait_for_eventloop() {
m_eventloop->start();
m_eventloop->wait();
this_thread::sleep_for(250ms);
if (m_running) {
m_log.trace("controller: eventloop ended, raising SIGALRM");
kill(getpid(), SIGALRM);
}
}
/**
* Create and initialize bar modules
*/

View File

@ -1,5 +1,8 @@
#include <csignal>
#include "components/eventloop.hpp"
#include "components/types.hpp"
#include "components/signals.hpp"
#include "utils/string.hpp"
#include "utils/time.hpp"
#include "x11/color.hpp"
@ -13,20 +16,27 @@ eventloop::eventloop(const logger& logger, const config& config) : m_log(logger)
m_delayed_time = duration_t{m_conf.get<double>("settings", "eventloop-delayed-time", 25)};
m_swallow_time = duration_t{m_conf.get<double>("settings", "eventloop-swallow-time", 10)};
m_swallow_limit = m_conf.get<size_t>("settings", "eventloop-swallow", 5U);
g_signals::event::enqueue = bind(&eventloop::enqueue, this, placeholders::_1);
g_signals::event::enqueue_delayed = bind(&eventloop::enqueue_delayed, this, placeholders::_1);
}
/**
* Deconstruct eventloop
*/
eventloop::~eventloop() noexcept {
eventloop::~eventloop() {
g_signals::event::enqueue = g_signals::noop<const eventloop::entry_t&>;
g_signals::event::enqueue_delayed = g_signals::noop<const eventloop::entry_t&>;
m_update_cb = nullptr;
m_unrecognized_input_cb = nullptr;
if (m_delayed_thread.joinable()) {
m_delayed_thread.join();
}
std::lock_guard<std::mutex> guard(m_modulelock, std::adopt_lock);
if (m_queue_thread.joinable()) {
m_queue_thread.join();
}
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
@ -129,8 +139,6 @@ void eventloop::set_input_db(callback<string>&& cb) {
* Add module to alignment block
*/
void eventloop::add_module(const alignment pos, module_t&& module) {
std::lock_guard<std::mutex> guard(m_modulelock, std::adopt_lock);
auto it = m_modules.lower_bound(pos);
if (it != m_modules.end() && !(m_modules.key_comp()(pos, it->first))) {
@ -164,8 +172,6 @@ size_t eventloop::module_count() const {
* Start module threads
*/
void eventloop::dispatch_modules() {
std::lock_guard<std::mutex> guard(m_modulelock);
for (const auto& block : m_modules) {
for (const auto& module : block.second) {
try {
@ -211,7 +217,7 @@ void eventloop::dispatch_queue_worker() {
forward_event(evt);
}
m_log.trace("eventloop: Reached end of queue worker thread");
m_log.info("Queue worker done");
}
/**
@ -233,7 +239,7 @@ void eventloop::dispatch_delayed_worker() {
m_delayed_entry.type = 0;
}
m_log.trace("eventloop: Reached end of delayed worker thread");
m_log.info("Delayed worker done");
}
/**
@ -267,7 +273,7 @@ void eventloop::forward_event(entry_t evt) {
} else if (evt.type == static_cast<uint8_t>(event_type::CHECK)) {
on_check();
} else if (evt.type == static_cast<uint8_t>(event_type::QUIT)) {
on_quit();
on_quit(reinterpret_cast<const quit_event&>(evt));
} else {
m_log.warn("Unknown event type for enqueued event (%d)", evt.type);
}
@ -292,12 +298,6 @@ void eventloop::on_update() {
void eventloop::on_input(char* input) {
m_log.trace("eventloop: Received INPUT event");
if (!m_modulelock.try_lock()) {
return;
}
std::lock_guard<std::mutex> guard(m_modulelock, std::adopt_lock);
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
if (!module->receive_events()) {
@ -320,34 +320,29 @@ void eventloop::on_input(char* input) {
* Handler for enqueued CHECK events
*/
void eventloop::on_check() {
if (!m_running) {
return;
}
if (!m_modulelock.try_lock()) {
return;
}
std::lock_guard<std::mutex> guard(m_modulelock, std::adopt_lock);
for (const auto& block : m_modules) {
for (const auto& module : block.second) {
if (module->running()) {
if (m_running && module->running()) {
return;
}
}
}
m_log.warn("No running modules...");
stop();
}
/**
* Handler for enqueued QUIT events
*/
void eventloop::on_quit() {
void eventloop::on_quit(const quit_event& quit) {
m_log.trace("eventloop: Received QUIT event");
m_running = false;
if (quit.reload) {
kill(getpid(), SIGUSR1);
}
}
POLYBAR_NS_END

View File

@ -5,6 +5,8 @@
#include "components/logger.hpp"
#include "components/screen.hpp"
#include "components/types.hpp"
#include "components/signals.hpp"
#include "components/eventloop.hpp"
#include "x11/connection.hpp"
#include "x11/randr.hpp"
#include "x11/winspec.hpp"
@ -27,6 +29,8 @@ screen::screen(connection& conn, const logger& logger, const config& conf)
, m_conf(conf)
, m_root(conn.root())
, m_size({conn.screen()->width_in_pixels, conn.screen()->height_in_pixels}) {
assert(g_signals::event::enqueue != nullptr);
// Check if the reloading has been disabled by the user
if (!m_conf.get<bool>("settings", "screenchange-reload", false)) {
return;
@ -65,7 +69,7 @@ screen::screen(connection& conn, const logger& logger, const config& conf)
screen::~screen() {
m_connection.detach_sink(this, 1);
if (m_proxy) {
if (m_proxy != XCB_NONE) {
m_connection.destroy_window(m_proxy);
}
}
@ -79,7 +83,9 @@ void screen::handle(const evt::randr_screen_change_notify& evt) {
if (!m_sigraised && evt->request_window == m_proxy) {
m_log.warn("randr_screen_change_notify (%ux%u)... reloading", evt->width, evt->height);
m_sigraised = true;
kill(getpid(), SIGUSR1);
quit_event quit{};
quit.reload = true;
g_signals::event::enqueue(reinterpret_cast<const eventloop::entry_t&>(quit));
}
}

View File

@ -3,34 +3,50 @@
POLYBAR_NS
/**
* Signals used to communicate with the bar window
*/
callback<const string> g_signals::bar::action_click{nullptr};
callback<const bool> g_signals::bar::visibility_change{nullptr};
namespace g_signals {
/**
* Signals used to communicate with the event loop
*/
namespace event {
callback<const eventloop::entry_t&> enqueue{noop<const eventloop::entry_t&>};
callback<const eventloop::entry_t&> enqueue_delayed{noop<const eventloop::entry_t&>};
}
/**
* Signals used to communicate with the input parser
*/
callback<const uint32_t> g_signals::parser::background_change{nullptr};
callback<const uint32_t> g_signals::parser::foreground_change{nullptr};
callback<const uint32_t> g_signals::parser::underline_change{nullptr};
callback<const uint32_t> g_signals::parser::overline_change{nullptr};
callback<const alignment> g_signals::parser::alignment_change{nullptr};
callback<const attribute> g_signals::parser::attribute_set{nullptr};
callback<const attribute> g_signals::parser::attribute_unset{nullptr};
callback<const attribute> g_signals::parser::attribute_toggle{nullptr};
callback<const int8_t> g_signals::parser::font_change{nullptr};
callback<const int16_t> g_signals::parser::pixel_offset{nullptr};
callback<const mousebtn, const string> g_signals::parser::action_block_open{nullptr};
callback<const mousebtn> g_signals::parser::action_block_close{nullptr};
callback<const uint16_t> g_signals::parser::ascii_text_write{nullptr};
callback<const uint16_t> g_signals::parser::unicode_text_write{nullptr};
callback<const char*, const size_t> g_signals::parser::string_write{nullptr};
/**
* Signals used to communicate with the bar window
*/
namespace bar {
callback<const string> action_click{noop<const string>};
callback<const bool> visibility_change{noop<const bool>};
}
/**
* Signals used to communicate with the tray manager
*/
callback<const uint16_t> g_signals::tray::report_slotcount{nullptr};
/**
* Signals used to communicate with the input parser
*/
namespace parser {
callback<const uint32_t> background_change{noop<const uint32_t>};
callback<const uint32_t> foreground_change{noop<const uint32_t>};
callback<const uint32_t> underline_change{noop<const uint32_t>};
callback<const uint32_t> overline_change{noop<const uint32_t>};
callback<const alignment> alignment_change{noop<const alignment>};
callback<const attribute> attribute_set{noop<const attribute>};
callback<const attribute> attribute_unset{noop<const attribute>};
callback<const attribute> attribute_toggle{noop<const attribute>};
callback<const int8_t> font_change{noop<const int8_t>};
callback<const int16_t> pixel_offset{noop<const int16_t>};
callback<const mousebtn, const string> action_block_open{noop<const mousebtn, const string>};
callback<const mousebtn> action_block_close{noop<const mousebtn>};
callback<const uint16_t> ascii_text_write{noop<const uint16_t>};
callback<const uint16_t> unicode_text_write{noop<const uint16_t>};
callback<const char*, const size_t> string_write{noop<const char*, const size_t>};
}
/**
* Signals used to communicate with the tray manager
*/
namespace tray {
callback<const uint16_t> report_slotcount{noop<const uint16_t>};
}
}
POLYBAR_NS_END

View File

@ -35,8 +35,8 @@ namespace inotify_util {
/**
* Remove inotify watch
*/
void inotify_watch::remove() {
if (inotify_rm_watch(m_fd, m_wd) == -1) {
void inotify_watch::remove(bool force) {
if (inotify_rm_watch(m_fd, m_wd) == -1 && !force) {
throw system_error("Failed to remove inotify watch");
}
m_wd = -1;