refactor(eventloop): Use concurrent queue for events

Events are now enqueued using a thread safe concurrent queue
which makes the previous eventloop locking redundant.
This commit is contained in:
Michael Carlberg 2016-10-29 06:48:51 +02:00
parent 92900e78d6
commit 1075144b00
15 changed files with 5263 additions and 384 deletions

View File

@ -48,20 +48,12 @@ def DirectoryOfThisScript():
flags.append('-I'+ DirectoryOfThisScript() +'/src') flags.append('-I'+ DirectoryOfThisScript() +'/src')
flags.append('-I'+ DirectoryOfThisScript() +'/include') flags.append('-I'+ DirectoryOfThisScript() +'/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/gsl') flags.append('-I'+ DirectoryOfThisScript() +'/lib/boost/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/cpp_freetype/include') flags.append('-I'+ DirectoryOfThisScript() +'/lib/concurrentqueue/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/i3ipcpp/include') flags.append('-I'+ DirectoryOfThisScript() +'/lib/i3ipcpp/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/xpp/include') flags.append('-I'+ DirectoryOfThisScript() +'/lib/xpp/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/lemonbar/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/fastdelegate/include')
flags.append('-I'+ DirectoryOfThisScript() +'/lib/boost/include')
flags.append('-I'+ DirectoryOfThisScript() +'/tests') flags.append('-I'+ DirectoryOfThisScript() +'/tests')
flags.append('-I/usr/include/freetype2') flags.append('-I/usr/include/freetype2')
flags.append('-I/usr/include/pango-1.0')
flags.append('-I/usr/include/cairomm-1.0')
flags.append('-I/usr/include/pangomm-1.4')
flags.append('-I/usr/include/glibmm-2.4')
flags.append('-I/usr/lib/cairomm-1.0/include')
flags.append('-I/usr/include') flags.append('-I/usr/include')
def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):

View File

@ -44,6 +44,7 @@ LEMONBUDDY_NS
namespace di = boost::di; namespace di = boost::di;
namespace chrono = std::chrono; namespace chrono = std::chrono;
namespace this_thread = std::this_thread; namespace this_thread = std::this_thread;
namespace placeholders = std::placeholders;
using namespace std::chrono_literals; using namespace std::chrono_literals;
@ -53,6 +54,7 @@ using std::size_t;
using std::move; using std::move;
using std::bind; using std::bind;
using std::forward; using std::forward;
using std::pair;
using std::function; using std::function;
using std::shared_ptr; using std::shared_ptr;
using std::unique_ptr; using std::unique_ptr;
@ -128,4 +130,15 @@ auto read_env = [](const char* var, string&& fallback = "") {
return value != nullptr ? value : fallback; return value != nullptr ? value : fallback;
}; };
template <class T>
auto time_execution(const T& expr) noexcept {
auto start = std::chrono::high_resolution_clock::now();
expr();
auto finish = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(finish - start).count();
}
template <typename... Args>
using callback = function<void(Args...)>;
LEMONBUDDY_NS_END LEMONBUDDY_NS_END

View File

@ -434,21 +434,21 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
// Connect signal handlers {{{ // Connect signal handlers {{{
// clang-format off // clang-format off
g_signals::parser::alignment_change = bind(&bar::on_alignment_change, this, std::placeholders::_1); g_signals::parser::alignment_change = bind(&bar::on_alignment_change, this, placeholders::_1);
g_signals::parser::attribute_set = bind(&bar::on_attribute_set, this, std::placeholders::_1); g_signals::parser::attribute_set = bind(&bar::on_attribute_set, this, placeholders::_1);
g_signals::parser::attribute_unset = bind(&bar::on_attribute_unset, this, std::placeholders::_1); g_signals::parser::attribute_unset = bind(&bar::on_attribute_unset, this, placeholders::_1);
g_signals::parser::attribute_toggle = bind(&bar::on_attribute_toggle, this, std::placeholders::_1); g_signals::parser::attribute_toggle = bind(&bar::on_attribute_toggle, this, placeholders::_1);
g_signals::parser::action_block_open = bind(&bar::on_action_block_open, this, std::placeholders::_1, std::placeholders::_2); g_signals::parser::action_block_open = bind(&bar::on_action_block_open, this, placeholders::_1, placeholders::_2);
g_signals::parser::action_block_close = bind(&bar::on_action_block_close, this, std::placeholders::_1); g_signals::parser::action_block_close = bind(&bar::on_action_block_close, this, placeholders::_1);
g_signals::parser::color_change = bind(&bar::on_color_change, this, std::placeholders::_1, std::placeholders::_2); g_signals::parser::color_change = bind(&bar::on_color_change, this, placeholders::_1, placeholders::_2);
g_signals::parser::font_change = bind(&bar::on_font_change, this, std::placeholders::_1); g_signals::parser::font_change = bind(&bar::on_font_change, this, placeholders::_1);
g_signals::parser::pixel_offset = bind(&bar::on_pixel_offset, this, std::placeholders::_1); g_signals::parser::pixel_offset = bind(&bar::on_pixel_offset, this, placeholders::_1);
g_signals::parser::ascii_text_write = bind(&bar::draw_character, this, std::placeholders::_1); g_signals::parser::ascii_text_write = bind(&bar::draw_character, this, placeholders::_1);
g_signals::parser::unicode_text_write = bind(&bar::draw_character, this, std::placeholders::_1); g_signals::parser::unicode_text_write = bind(&bar::draw_character, this, placeholders::_1);
// clang-format on // clang-format on
if (m_tray.align != alignment::NONE) if (m_tray.align != alignment::NONE)
g_signals::tray::report_slotcount = bind(&bar::on_tray_report, this, std::placeholders::_1); g_signals::tray::report_slotcount = bind(&bar::on_tray_report, this, placeholders::_1);
// }}} // }}}

View File

@ -5,6 +5,7 @@
#include "common.hpp" #include "common.hpp"
#include "components/bar.hpp" #include "components/bar.hpp"
#include "components/config.hpp" #include "components/config.hpp"
#include "components/eventloop.hpp"
#include "components/logger.hpp" #include "components/logger.hpp"
#include "components/signals.hpp" #include "components/signals.hpp"
#include "components/x11/connection.hpp" #include "components/x11/connection.hpp"
@ -12,11 +13,10 @@
#include "components/x11/tray.hpp" #include "components/x11/tray.hpp"
#include "components/x11/types.hpp" #include "components/x11/types.hpp"
#include "config.hpp" #include "config.hpp"
#include "utils/command.hpp"
#include "utils/inotify.hpp" #include "utils/inotify.hpp"
#include "utils/process.hpp" #include "utils/process.hpp"
#include "utils/socket.hpp" #include "utils/socket.hpp"
#include "utils/throttle.hpp" #include "utils/string.hpp"
#include "modules/backlight.hpp" #include "modules/backlight.hpp"
#include "modules/battery.hpp" #include "modules/battery.hpp"
@ -46,7 +46,6 @@
LEMONBUDDY_NS LEMONBUDDY_NS
using namespace modules; using namespace modules;
using module_t = unique_ptr<module_interface>;
class controller { class controller {
public: public:
@ -54,10 +53,12 @@ class controller {
* Construct controller * Construct controller
*/ */
explicit controller(connection& conn, const logger& logger, const config& config, explicit controller(connection& conn, const logger& logger, const config& config,
unique_ptr<bar> bar, unique_ptr<traymanager> tray, inotify_watch_t& confwatch) unique_ptr<eventloop> eventloop, unique_ptr<bar> bar, unique_ptr<traymanager> tray,
inotify_watch_t& confwatch)
: m_connection(conn) : m_connection(conn)
, m_log(logger) , m_log(logger)
, m_conf(config) , m_conf(config)
, m_eventloop(forward<decltype(eventloop)>(eventloop))
, m_bar(forward<decltype(bar)>(bar)) , m_bar(forward<decltype(bar)>(bar))
, m_traymanager(forward<decltype(tray)>(tray)) , m_traymanager(forward<decltype(tray)>(tray))
, m_confwatch(confwatch) {} , m_confwatch(confwatch) {}
@ -67,34 +68,29 @@ class controller {
* threads and spawned processes * threads and spawned processes
*/ */
~controller() noexcept { ~controller() noexcept {
if (!m_mutex.try_lock_for(5s)) { g_signals::bar::action_click = nullptr;
m_log.warn("Failed to acquire lock for 5s... Forcing shutdown using SIGKILL");
raise(SIGKILL);
}
std::lock_guard<std::timed_mutex> guard(m_mutex, std::adopt_lock);
m_log.info("Stopping modules");
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
module->stop();
}
}
if (m_command) { if (m_command) {
m_log.info("Terminating running shell command"); m_log.info("Terminating running shell command");
m_command->terminate(); m_command->terminate();
} }
if (m_traymanager) if (m_eventloop) {
m_traymanager.reset(); m_log.info("Deconstructing eventloop");
m_eventloop->set_update_cb(nullptr);
m_eventloop->set_input_db(nullptr);
m_eventloop.reset();
}
if (m_bar) { if (m_bar) {
m_log.trace("controller: Deconstruct bar instance"); m_log.info("Deconstructing bar");
g_signals::bar::action_click = nullptr;
m_bar.reset(); m_bar.reset();
} }
if (m_traymanager) {
m_traymanager.reset();
}
m_log.info("Interrupting X event loop"); m_log.info("Interrupting X event loop");
m_connection.send_dummy_event(m_connection.root()); m_connection.send_dummy_event(m_connection.root());
@ -124,8 +120,8 @@ class controller {
/** /**
* Setup X environment * Setup X environment
*/ */
auto bootstrap(bool to_stdout = false, bool dump_wmname = false) { auto bootstrap(bool writeback = false, bool dump_wmname = false) {
m_stdout = to_stdout; m_writeback = writeback;
m_log.trace("controller: Initialize X atom cache"); m_log.trace("controller: Initialize X atom cache");
m_connection.preload_atoms(); m_connection.preload_atoms();
@ -133,13 +129,14 @@ class controller {
m_log.trace("controller: Query X extension data"); m_log.trace("controller: Query X extension data");
m_connection.query_extensions(); m_connection.query_extensions();
// Disabled X extensions {{{
// const auto& damage_ext = m_connection.extension<xpp::damage::extension>(); // const auto& damage_ext = m_connection.extension<xpp::damage::extension>();
// m_log.trace("controller: Found 'Damage' (first_event: %i, first_error: %i)", // m_log.trace("controller: Found 'Damage' (first_event: %i, first_error: %i)",
// damage_ext->first_event, damage_ext->first_error); // damage_ext->first_event, damage_ext->first_error);
// const auto& render_ext = m_connection.extension<xpp::render::extension>(); // const auto& render_ext = m_connection.extension<xpp::render::extension>();
// m_log.trace("controller: Found 'Render' (first_event: %i, first_error: %i)", // m_log.trace("controller: Found 'Render' (first_event: %i, first_error: %i)",
// render_ext->first_event, render_ext->first_error); // render_ext->first_event, render_ext->first_error);
// }}}
const auto& randr_ext = m_connection.extension<xpp::randr::extension>(); const auto& randr_ext = m_connection.extension<xpp::randr::extension>();
m_log.trace("controller: Found 'RandR' (first_event: %i, first_error: %i)", m_log.trace("controller: Found 'RandR' (first_event: %i, first_error: %i)",
@ -148,7 +145,6 @@ class controller {
// Listen for events on the root window to be able to // Listen for events on the root window to be able to
// break the blocking wait call when cleaning up // break the blocking wait call when cleaning up
m_log.trace("controller: Listen for events on the root window"); m_log.trace("controller: Listen for events on the root window");
try { try {
const uint32_t value_list[1]{XCB_EVENT_MASK_STRUCTURE_NOTIFY}; const uint32_t value_list[1]{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
m_connection.change_window_attributes_checked( m_connection.change_window_attributes_checked(
@ -157,21 +153,26 @@ class controller {
throw application_error("Failed to change root window event mask: " + string{err.what()}); throw application_error("Failed to change root window event mask: " + string{err.what()});
} }
g_signals::bar::action_click = bind(&controller::on_mouse_event, this, placeholders::_1);
m_log.trace("controller: Attach eventloop callbacks");
m_eventloop->set_update_cb(bind(&controller::on_update, this));
m_eventloop->set_input_db(bind(&controller::on_unrecognized_action, this, placeholders::_1));
try { try {
m_log.trace("controller: Setup bar renderer"); m_log.trace("controller: Setup bar");
m_bar->bootstrap(m_stdout || dump_wmname); m_bar->bootstrap(m_writeback || dump_wmname);
if (dump_wmname) {
std::cout << m_bar->settings().wmname << std::endl;
return;
} else if (!to_stdout) {
g_signals::bar::action_click = bind(&controller::on_module_click, this, std::placeholders::_1);
}
} catch (const std::exception& err) { } catch (const std::exception& err) {
throw application_error("Failed to setup bar renderer: " + string{err.what()}); throw application_error("Failed to setup bar renderer: " + string{err.what()});
} }
if (dump_wmname) {
std::cout << m_bar->settings().wmname << std::endl;
return;
}
try { try {
if (m_stdout) { if (m_writeback) {
m_log.trace("controller: Disabling tray (reason: stdout mode)"); m_log.trace("controller: Disabling tray (reason: stdout mode)");
m_traymanager.reset(); m_traymanager.reset();
} else if (m_bar->tray().align == alignment::NONE) { } else if (m_bar->tray().align == alignment::NONE) {
@ -187,14 +188,8 @@ class controller {
m_traymanager.reset(); m_traymanager.reset();
} }
m_log.trace("main: Setup bar modules"); m_log.trace("controller: Setup user-defined modules");
bootstrap_modules(); bootstrap_modules();
// Allow <throttle_limit> ticks within <throttle_ms> timeframe
const auto throttle_limit = m_conf.get<unsigned int>("settings", "throttle-limit", 3);
const auto throttle_ms = chrono::duration<double, std::milli>(
m_conf.get<unsigned int>("settings", "throttle-ms", 60));
m_throttler = throttle_util::make_throttler(throttle_limit, throttle_ms);
} }
/** /**
@ -209,73 +204,36 @@ class controller {
install_sigmask(); install_sigmask();
install_confwatch(); install_confwatch();
m_threads.emplace_back([this] { // Wait for term signal in separate thread
m_connection.flush(); m_threads.emplace_back(thread(&controller::wait_for_signal, this));
m_log.trace("controller: Start modules"); // Activate traymanager in separate thread
for (auto&& block : m_modules) { if (!m_writeback && m_traymanager) {
for (auto&& module : block.second) { m_threads.emplace_back(thread(&controller::activate_tray, this));
try { }
module->start();
} catch (const application_error& err) {
m_log.err("Failed to start '%s' (reason: %s)", module->name(), err.what());
}
// Offset the initial broadcasts by 25ms to // Listen for X events in separate thread
// avoid the updates from being ignored by the throttler if (!m_writeback) {
this_thread::sleep_for(25ms); m_threads.emplace_back(thread(&controller::wait_for_xevent, this));
} }
}
if (m_stdout) { // Start event loop
m_log.trace("controller: Ignoring tray manager (reason: stdout mode)"); if (m_eventloop) {
m_log.trace("controller: Ignoring X event loop (reason: stdout mode)"); auto throttle_ms = m_conf.get<double>("settings", "throttle-ms", 10);
return; auto throttle_limit = m_conf.get<int>("settings", "throttle-limit", 5);
} m_eventloop->run(chrono::duration<double, std::milli>(throttle_ms), throttle_limit);
}
if (m_traymanager) { // Wake up signal thread
try { if (m_waiting) {
m_log.trace("controller: Activate tray manager"); kill(getpid(), SIGTERM);
m_traymanager->activate(); }
} catch (const std::exception& err) {
m_log.err(err.what());
m_log.err("Failed to activate tray manager, disabling...");
m_traymanager.reset();
}
}
m_connection.flush();
m_log.trace("controller: Listen for X events");
while (m_running) {
auto evt = m_connection.wait_for_event();
if (evt != nullptr && m_running)
m_connection.dispatch_event(evt);
}
});
wait();
m_running = false; m_running = false;
return !m_reload; return !m_reload;
} }
/**
* Block execution until a defined signal is raised
*/
void wait() {
m_log.trace("controller: Wait for signal");
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);
m_reload = (caught_signal == SIGUSR1);
}
protected: protected:
/** /**
* Set signal mask for the current and future threads * Set signal mask for the current and future threads
@ -294,6 +252,12 @@ class controller {
if (pthread_sigmask(SIG_BLOCK, &m_waitmask, nullptr) == -1) if (pthread_sigmask(SIG_BLOCK, &m_waitmask, nullptr) == -1)
throw system_error(); throw system_error();
sigemptyset(&m_ignmask);
sigaddset(&m_ignmask, SIGPIPE);
if (pthread_sigmask(SIG_BLOCK, &m_ignmask, nullptr) == -1)
throw system_error();
} }
/** /**
@ -334,21 +298,65 @@ class controller {
}); });
} }
void 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;
}
void wait_for_xevent() {
m_log.trace("controller: Listen for X events");
m_connection.flush();
while (true) {
shared_ptr<xcb_generic_event_t> evt;
if ((evt = m_connection.wait_for_event()))
m_connection.dispatch_event(evt);
if (!m_running)
break;
}
}
void activate_tray() {
m_log.trace("controller: Activate tray manager");
try {
m_traymanager->activate();
} catch (const std::exception& err) {
m_log.err(err.what());
m_log.err("Failed to activate tray manager, disabling...");
m_traymanager.reset();
}
}
/** /**
* Create and initialize bar modules * Create and initialize bar modules
*/ */
void bootstrap_modules() { void bootstrap_modules() {
m_modules.emplace(alignment::LEFT, vector<module_t>{}); const bar_settings bar{m_bar->settings()};
m_modules.emplace(alignment::CENTER, vector<module_t>{}); string bs{m_conf.bar_section()};
m_modules.emplace(alignment::RIGHT, vector<module_t>{});
size_t module_count = 0; size_t module_count = 0;
for (auto& block : m_modules) { for (int i = 0; i < 3; i++) {
string bs{m_conf.bar_section()}; alignment align = static_cast<alignment>(i + 1);
string confkey; string confkey;
switch (block.first) { switch (align) {
case alignment::LEFT: case alignment::LEFT:
confkey = "modules-left"; confkey = "modules-left";
break; break;
@ -363,49 +371,56 @@ class controller {
} }
for (auto& module_name : string_util::split(m_conf.get<string>(bs, confkey, ""), ' ')) { for (auto& module_name : string_util::split(m_conf.get<string>(bs, confkey, ""), ' ')) {
auto type = m_conf.get<string>("module/" + module_name, "type"); try {
auto bar = m_bar->settings(); auto type = m_conf.get<string>("module/" + module_name, "type");
auto& modules = block.second; module_t module;
if (type == "internal/counter") if (type == "internal/counter")
modules.emplace_back(new counter_module(bar, m_log, m_conf, module_name)); module.reset(new counter_module(bar, m_log, m_conf, module_name));
else if (type == "internal/backlight") else if (type == "internal/backlight")
modules.emplace_back(new backlight_module(bar, m_log, m_conf, module_name)); module.reset(new backlight_module(bar, m_log, m_conf, module_name));
else if (type == "internal/xbacklight") else if (type == "internal/xbacklight")
modules.emplace_back(new xbacklight_module(bar, m_log, m_conf, module_name)); module.reset(new xbacklight_module(bar, m_log, m_conf, module_name));
else if (type == "internal/battery") else if (type == "internal/battery")
modules.emplace_back(new battery_module(bar, m_log, m_conf, module_name)); module.reset(new battery_module(bar, m_log, m_conf, module_name));
else if (type == "internal/bspwm") else if (type == "internal/bspwm")
modules.emplace_back(new bspwm_module(bar, m_log, m_conf, module_name)); module.reset(new bspwm_module(bar, m_log, m_conf, module_name));
else if (type == "internal/cpu") else if (type == "internal/cpu")
modules.emplace_back(new cpu_module(bar, m_log, m_conf, module_name)); module.reset(new cpu_module(bar, m_log, m_conf, module_name));
else if (type == "internal/date") else if (type == "internal/date")
modules.emplace_back(new date_module(bar, m_log, m_conf, module_name)); module.reset(new date_module(bar, m_log, m_conf, module_name));
else if (type == "internal/memory") else if (type == "internal/memory")
modules.emplace_back(new memory_module(bar, m_log, m_conf, module_name)); module.reset(new memory_module(bar, m_log, m_conf, module_name));
else if (type == "internal/i3") else if (type == "internal/i3")
modules.emplace_back(new i3_module(bar, m_log, m_conf, module_name)); module.reset(new i3_module(bar, m_log, m_conf, module_name));
else if (type == "internal/mpd") else if (type == "internal/mpd")
modules.emplace_back(new mpd_module(bar, m_log, m_conf, module_name)); module.reset(new mpd_module(bar, m_log, m_conf, module_name));
else if (type == "internal/volume") else if (type == "internal/volume")
modules.emplace_back(new volume_module(bar, m_log, m_conf, module_name)); module.reset(new volume_module(bar, m_log, m_conf, module_name));
else if (type == "internal/network") else if (type == "internal/network")
modules.emplace_back(new network_module(bar, m_log, m_conf, module_name)); module.reset(new network_module(bar, m_log, m_conf, module_name));
else if (type == "custom/text") else if (type == "custom/text")
modules.emplace_back(new text_module(bar, m_log, m_conf, module_name)); module.reset(new text_module(bar, m_log, m_conf, module_name));
else if (type == "custom/script") else if (type == "custom/script")
modules.emplace_back(new script_module(bar, m_log, m_conf, module_name)); module.reset(new script_module(bar, m_log, m_conf, module_name));
else if (type == "custom/menu") else if (type == "custom/menu")
modules.emplace_back(new menu_module(bar, m_log, m_conf, module_name)); module.reset(new menu_module(bar, m_log, m_conf, module_name));
else else
throw application_error("Unknown module: " + module_name); throw application_error("Unknown module: " + module_name);
auto& module = modules.back(); module->set_update_cb(bind(&eventloop::enqueue, m_eventloop.get(),
eventloop::entry_t{static_cast<int>(event_type::UPDATE)}));
module->set_stop_cb(bind(&eventloop::enqueue, m_eventloop.get(),
eventloop::entry_t{static_cast<int>(event_type::CHECK)}));
module->set_writer(bind(&controller::on_module_update, this, std::placeholders::_1)); module->setup();
module->set_terminator(bind(&controller::on_module_stop, this, std::placeholders::_1));
module_count++; m_eventloop->add_module(align, move(module));
module_count++;
} catch (const module_error& err) {
continue;
}
} }
} }
@ -413,115 +428,21 @@ class controller {
throw application_error("No modules created"); throw application_error("No modules created");
} }
void on_module_update(string /* module_name */) { /**
if (!m_mutex.try_lock_for(50ms)) { * Callback for clicked bar actions
this_thread::yield(); */
return; void on_mouse_event(string input) {
eventloop::entry_t evt{static_cast<int>(event_type::INPUT)};
if (input.length() > sizeof(evt.data)) {
m_log.warn("Ignoring input event (size)");
} else {
snprintf(evt.data, sizeof(evt.data), "%s", input.c_str());
m_eventloop->enqueue(evt);
} }
std::lock_guard<std::timed_mutex> guard(m_mutex, std::adopt_lock);
if (!m_running)
return;
if (!m_throttler->passthrough(m_throttle_strategy)) {
m_log.trace("controller: Update event throttled");
return;
}
string contents{""};
string separator{m_bar->settings().separator};
string padding_left(m_bar->settings().padding_left, ' ');
string padding_right(m_bar->settings().padding_right, ' ');
auto margin_left = m_bar->settings().module_margin_left;
auto margin_right = m_bar->settings().module_margin_right;
for (auto&& block : m_modules) {
string block_contents;
for (auto&& module : block.second) {
auto module_contents = module->contents();
if (module_contents.empty())
continue;
if (!block_contents.empty() && !separator.empty())
block_contents += separator;
if (!(block.first == alignment::LEFT && module == block.second.front()))
block_contents += string(margin_left, ' ');
block_contents += module->contents();
if (!(block.first == alignment::RIGHT && module == block.second.back()))
block_contents += string(margin_right, ' ');
}
if (block_contents.empty())
continue;
switch (block.first) {
case alignment::LEFT:
contents += "%{l}";
contents += padding_left;
break;
case alignment::CENTER:
contents += "%{c}";
break;
case alignment::RIGHT:
contents += "%{r}";
block_contents += padding_right;
break;
case alignment::NONE:
break;
}
block_contents = string_util::replace_all(block_contents, "B-}%{B#", "B#");
block_contents = string_util::replace_all(block_contents, "F-}%{F#", "F#");
block_contents = string_util::replace_all(block_contents, "T-}%{T", "T");
contents += string_util::replace_all(block_contents, "}%{", " ");
}
if (m_stdout)
std::cout << contents << std::endl;
else
m_bar->parse(contents);
} }
void on_module_stop(string /* module_name */) { void on_unrecognized_action(string input) {
if (!m_running)
return;
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
if (module->running())
return;
}
}
m_log.warn("No running modules, raising SIGTERM");
kill(getpid(), SIGTERM);
}
void on_module_click(string input) {
if (!m_clickmtx.try_lock()) {
this_thread::yield();
return;
}
std::lock_guard<std::mutex> guard(m_clickmtx, std::adopt_lock);
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
if (!module->receive_events())
continue;
if (module->handle_event(input))
return;
}
}
m_clickmtx.unlock();
try { try {
if (m_command) { if (m_command) {
m_log.warn("Terminating previous shell command"); m_log.warn("Terminating previous shell command");
@ -538,31 +459,96 @@ class controller {
} }
} }
void on_update() {
string contents{""};
string separator{m_bar->settings().separator};
string padding_left(m_bar->settings().padding_left, ' ');
string padding_right(m_bar->settings().padding_right, ' ');
auto margin_left = m_bar->settings().module_margin_left;
auto margin_right = m_bar->settings().module_margin_right;
for (const auto& block : m_eventloop->modules()) {
string block_contents;
bool is_left = false;
bool is_center = false;
bool is_right = false;
if (block.first == alignment::LEFT)
is_left = true;
else if (block.first == alignment::CENTER)
is_center = true;
else if (block.first == alignment::RIGHT)
is_right = true;
for (const auto& module : block.second) {
auto module_contents = module->contents();
if (module_contents.empty())
continue;
if (!block_contents.empty() && !separator.empty())
block_contents += separator;
if (!(is_left && module == block.second.front()))
block_contents += string(margin_left, ' ');
block_contents += module->contents();
if (!(is_right && module == block.second.back()))
block_contents += string(margin_right, ' ');
}
if (block_contents.empty())
continue;
if (is_left) {
contents += "%{l}";
contents += padding_left;
} else if (is_center) {
contents += "%{c}";
} else if (is_right) {
contents += "%{r}";
block_contents += padding_right;
}
block_contents = string_util::replace_all(block_contents, "B-}%{B#", "B#");
block_contents = string_util::replace_all(block_contents, "F-}%{F#", "F#");
block_contents = string_util::replace_all(block_contents, "T-}%{T", "T");
contents += string_util::replace_all(block_contents, "}%{", " ");
}
if (m_writeback) {
std::cout << contents << std::endl;
} else {
m_bar->parse(contents);
}
}
private: private:
connection& m_connection; connection& m_connection;
registry m_registry{m_connection}; registry m_registry{m_connection};
const logger& m_log; const logger& m_log;
const config& m_conf; const config& m_conf;
unique_ptr<eventloop> m_eventloop;
unique_ptr<bar> m_bar; unique_ptr<bar> m_bar;
unique_ptr<traymanager> m_traymanager; unique_ptr<traymanager> m_traymanager;
std::timed_mutex m_mutex;
std::mutex m_clickmtx;
stateflag m_stdout{false};
stateflag m_running{false}; stateflag m_running{false};
stateflag m_reload{false}; stateflag m_reload{false};
stateflag m_waiting{false};
sigset_t m_waitmask; sigset_t m_waitmask;
sigset_t m_ignmask;
inotify_watch_t& m_confwatch; inotify_watch_t& m_confwatch;
vector<thread> m_threads; vector<thread> m_threads;
map<alignment, vector<module_t>> m_modules;
command_util::command_t m_command; command_util::command_t m_command;
unique_ptr<throttle_util::event_throttler> m_throttler; bool m_writeback = false;
throttle_util::strategy::try_once_or_leave_yolo m_throttle_strategy;
}; };
namespace { namespace {
@ -577,6 +563,7 @@ namespace {
configure_connection(), configure_connection(),
configure_logger(), configure_logger(),
configure_config(), configure_config(),
configure_eventloop(),
configure_bar(), configure_bar(),
configure_traymanager()); configure_traymanager());
// clang-format on // clang-format on

View File

@ -0,0 +1,287 @@
#pragma once
#include <moodycamel/blockingconcurrentqueue.h>
#include "common.hpp"
#include "components/bar.hpp"
#include "components/logger.hpp"
#include "modules/meta.hpp"
#include "utils/command.hpp"
#include "utils/string.hpp"
LEMONBUDDY_NS
using module_t = unique_ptr<modules::module_interface>;
using modulemap_t = map<alignment, vector<module_t>>;
enum class event_type { NONE = 0, UPDATE, CHECK, INPUT, QUIT };
struct event {
int type;
char data[256]{'\0'};
};
class eventloop {
public:
/**
* Queue type
*/
using entry_t = event;
using queue_t = moodycamel::BlockingConcurrentQueue<entry_t>;
/**
* Construct eventloop
*/
explicit eventloop(const logger& logger) : m_log(logger) {}
/**
* Deconstruct eventloop
*/
~eventloop() noexcept {
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
auto module_name = module->name();
auto cleanup_ms = time_execution([&module] {
module->stop();
module.reset();
});
m_log.trace("eventloop: Deconstruction of %s took %lu ms.", module_name, cleanup_ms);
}
}
}
/**
* Set callback handler for UPDATE events
*/
void set_update_cb(callback<>&& cb) {
m_update_cb = forward<decltype(cb)>(cb);
}
/**
* Set callback handler for raw INPUT events
*/
void set_input_db(callback<string>&& cb) {
m_unrecognized_input_cb = forward<decltype(cb)>(cb);
}
/**
* Get reference to module map
*/
modulemap_t& modules() {
return m_modules;
}
/**
* Add module to alignment block
*/
void add_module(const alignment pos, module_t&& module) {
modulemap_t::iterator it = m_modules.lower_bound(pos);
if (it != m_modules.end() && !(m_modules.key_comp()(pos, it->first))) {
it->second.emplace_back(forward<module_t>(module));
} else {
vector<module_t> vec;
vec.emplace_back(forward<module_t>(module));
m_modules.insert(it, modulemap_t::value_type(pos, move(vec)));
}
}
/**
* Enqueue event
*/
bool enqueue(const entry_t& i) {
bool enqueued;
if ((enqueued = m_queue.enqueue(i)) == false) {
m_log.warn("Failed to queue event (%d)", i.type);
}
return enqueued;
}
/**
* Start module threads and wait for events on the queue
*
* @param timeframe Time to wait for subsequent events
* @param limit Maximum amount of subsequent events to swallow within timeframe
*/
template <typename Rep, typename Period>
void run(chrono::duration<Rep, Period> timeframe, int limit) {
m_log.info("Starting event loop");
m_running = true;
// Start module threads
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
try {
m_log.info("Starting %s", module->name());
module->start();
} catch (const application_error& err) {
m_log.err("Failed to start '%s' (reason: %s)", module->name(), err.what());
}
}
}
m_log.trace("eventloop: Enter loop");
while (m_running) {
entry_t evt, next{static_cast<int>(event_type::NONE)};
m_queue.wait_dequeue(evt);
if (!m_running) {
break;
}
if (match_event(evt, event_type::UPDATE)) {
int swallowed = 0;
while (swallowed++ < limit && m_queue.wait_dequeue_timed(next, timeframe)) {
if (match_event(next, event_type::QUIT)) {
evt = next;
break;
} else if (compare_events(evt, next)) {
m_log.trace("eventloop: Swallowing event within timeframe");
evt = next;
} else {
break;
}
}
}
forward_event(evt);
if (match_event(next, event_type::NONE))
continue;
if (compare_events(evt, next))
continue;
forward_event(next);
}
m_log.trace("eventloop: Loop ended");
}
/**
* Stop main loop by enqueuing a QUIT event
*/
void stop() {
m_log.info("Stopping event loop");
m_running = false;
enqueue({static_cast<int>(event_type::QUIT)});
}
protected:
/**
* Test if event matches given type
*/
bool match_event(entry_t evt, event_type type) {
return static_cast<int>(type) == evt.type;
}
/**
* Compare given events
*/
bool compare_events(entry_t evt, entry_t evt2) {
return evt.type == evt2.type;
}
/**
* Forward event to handler based on type
*/
void forward_event(entry_t evt) {
if (evt.type == static_cast<int>(event_type::UPDATE)) {
on_update();
} else if (evt.type == static_cast<int>(event_type::INPUT)) {
on_input(string{evt.data});
} else if (evt.type == static_cast<int>(event_type::CHECK)) {
on_check();
} else if (evt.type == static_cast<int>(event_type::QUIT)) {
on_quit();
} else {
m_log.warn("Unknown event type for enqueued event (%d)", evt.type);
}
}
/**
* Handler for enqueued UPDATE events
*/
void on_update() {
m_log.trace("eventloop: Received UPDATE event");
if (m_update_cb) {
m_update_cb();
} else {
m_log.warn("No callback to handle update");
}
}
/**
* Handler for enqueued INPUT events
*/
void on_input(string input) {
m_log.trace("eventloop: Received INPUT event");
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
if (!module->receive_events())
continue;
if (module->handle_event(input)) {
return;
}
}
}
if (m_unrecognized_input_cb) {
m_unrecognized_input_cb(input);
} else {
m_log.warn("No callback to handle unrecognized input");
}
}
/**
* Handler for enqueued CHECK events
*/
void on_check() {
if (!m_running) {
return;
}
for (auto&& block : m_modules) {
for (auto&& module : block.second) {
if (module->running())
return;
}
}
m_log.warn("No running modules...");
stop();
}
/**
* Handler for enqueued QUIT events
*/
void on_quit() {
m_log.trace("eventloop: Received QUIT event");
m_running = false;
}
private:
const logger& m_log;
queue_t m_queue;
modulemap_t m_modules;
stateflag m_running;
callback<> m_update_cb;
callback<string> m_unrecognized_input_cb;
};
namespace {
/**
* Configure injection module
*/
template <typename T = unique_ptr<eventloop>>
di::injector<T> configure_eventloop() {
return di::make_injector(configure_logger());
}
}
LEMONBUDDY_NS_END

View File

@ -178,7 +178,7 @@ namespace modules {
int i = 0; int i = 0;
const int poll_seconds = m_conf.get<float>(name(), "poll-interval", 3.0f) / dur.count(); const int poll_seconds = m_conf.get<float>(name(), "poll-interval", 3.0f) / dur.count();
while (enabled()) { while (running()) {
// TODO(jaagr): Keep track of when the values were last read to determine // TODO(jaagr): Keep track of when the values were last read to determine
// if we need to trigger the event manually or not. // if we need to trigger the event manually or not.
if (poll_seconds > 0 && (++i % poll_seconds) == 0) { if (poll_seconds > 0 && (++i % poll_seconds) == 0) {

View File

@ -101,8 +101,10 @@ namespace modules {
} }
void stop() { void stop() {
if (m_subscriber) if (m_subscriber) {
m_log.info("%s: Disconnecting from socket", name());
m_subscriber->disconnect(); m_subscriber->disconnect();
}
event_module::stop(); event_module::stop();
} }

View File

@ -101,14 +101,14 @@ namespace modules {
for (auto&& tag : string_util::split(format->value, ' ')) { for (auto&& tag : string_util::split(format->value, ' ')) {
if (tag[0] != '<' || tag[tag.length() - 1] != '>') if (tag[0] != '<' || tag[tag.length() - 1] != '>')
continue; continue;
if (std::find(format->tags.begin(), format->tags.end(), tag) != format->tags.end()) if (find(format->tags.begin(), format->tags.end(), tag) != format->tags.end())
continue; continue;
if (std::find(whitelist.begin(), whitelist.end(), tag) != whitelist.end()) if (find(whitelist.begin(), whitelist.end(), tag) != whitelist.end())
continue; continue;
throw undefined_format_tag("[" + m_modname + "] Undefined \"" + name + "\" tag: " + tag); throw undefined_format_tag("[" + m_modname + "] Undefined \"" + name + "\" tag: " + tag);
} }
m_formats.insert(make_pair(name, std::move(format))); m_formats.insert(make_pair(name, move(format)));
} }
shared_ptr<module_format> get(string format_name) { shared_ptr<module_format> get(string format_name) {
@ -158,8 +158,8 @@ namespace modules {
virtual bool handle_event(string cmd) = 0; virtual bool handle_event(string cmd) = 0;
virtual bool receive_events() const = 0; virtual bool receive_events() const = 0;
virtual void set_writer(std::function<void(string)>&& fn) = 0; virtual void set_update_cb(callback<>&& cb) = 0;
virtual void set_terminator(std::function<void(string)>&& fn) = 0; virtual void set_stop_cb(callback<>&& cb) = 0;
}; };
// }}} // }}}
@ -177,32 +177,23 @@ namespace modules {
, m_formatter(make_unique<module_formatter>(m_conf, m_name)) {} , m_formatter(make_unique<module_formatter>(m_conf, m_name)) {}
~module() { ~module() {
CAST_MOD(Impl)->stop(); m_log.trace("%s: Deconstructing", name());
m_updatelock.unlock(); assert(!running());
std::lock_guard<threading_util::spin_lock> lck(m_updatelock); for (auto&& thread_ : m_threads) {
{ if (thread_.joinable()) {
if (m_broadcast_thread.joinable()) thread_.join();
m_broadcast_thread.join();
for (auto&& thread_ : m_threads) {
if (thread_.joinable())
thread_.join();
} }
m_threads.clear();
} }
m_log.trace("%s: Done cleaning up", name());
} }
void set_writer(std::function<void(string)>&& fn) { void set_update_cb(callback<>&& cb) {
m_writer = forward<decltype(fn)>(fn); m_update_callback = forward<decltype(cb)>(cb);
} }
void set_terminator(std::function<void(string)>&& fn) { void set_stop_cb(callback<>&& cb) {
m_terminator = forward<decltype(fn)>(fn); m_stop_callback = forward<decltype(cb)>(cb);
} }
string name() const { string name() const {
@ -210,36 +201,42 @@ namespace modules {
} }
bool running() const { bool running() const {
return CONST_MOD(Impl).enabled(); return m_enabled.load(std::memory_order_relaxed);
} }
void setup() { void setup() {
m_log.trace("%s: Setup", name()); m_log.trace("%s: Setup", m_name);
try { try {
CAST_MOD(Impl)->setup(); CAST_MOD(Impl)->setup();
} catch (const module_error& err) {
m_log.err("%s: Setup failed", name());
CAST_MOD(Impl)->halt(err.what());
} catch (const std::exception& err) { } catch (const std::exception& err) {
m_log.err("%s: Setup failed", name()); m_log.err("%s: Setup failed", m_name);
CAST_MOD(Impl)->halt(err.what()); halt(err.what());
} }
} }
void stop() { void stop() {
if (!enabled()) if (!running()) {
return; return;
std::unique_lock<threading_util::spin_lock> lck(m_updatelock);
{
enable(false);
CAST_MOD(Impl)->teardown();
m_log.trace("%s: Stop", name());
} }
if (m_terminator) m_log.info("%s: Stopping", name());
m_terminator(name()); m_enabled.store(false, std::memory_order_relaxed);
wakeup();
std::lock_guard<threading_util::spin_lock> guard(m_lock);
{
CAST_MOD(Impl)->teardown();
if (m_mainthread.joinable()) {
m_mainthread.join();
}
}
if (m_stop_callback) {
m_stop_callback();
}
} }
void halt(string error_message) { void halt(string error_message) {
@ -248,9 +245,7 @@ namespace modules {
stop(); stop();
} }
void teardown() { void teardown() {}
CAST_MOD(Impl)->wakeup();
}
string contents() { string contents() {
return m_cache; return m_cache;
@ -265,22 +260,15 @@ namespace modules {
} }
protected: protected:
bool enabled() const {
return m_enabled;
}
void enable(bool state) {
m_enabled = state;
}
void broadcast() { void broadcast() {
if (!enabled()) if (!running()) {
return; return;
}
m_cache = CAST_MOD(Impl)->get_output(); m_cache = CAST_MOD(Impl)->get_output();
if (m_writer) if (m_update_callback)
m_writer(name()); m_update_callback();
else else
m_log.warn("%s: No handler, ignoring broadcast...", name()); m_log.warn("%s: No handler, ignoring broadcast...", name());
} }
@ -302,7 +290,7 @@ namespace modules {
} }
string get_output() { string get_output() {
if (!enabled()) { if (!running()) {
m_log.trace("%s: Module is disabled", name()); m_log.trace("%s: Module is disabled", name());
return ""; return "";
} }
@ -334,11 +322,10 @@ namespace modules {
} }
protected: protected:
function<void(string)> m_writer; callback<> m_update_callback;
function<void(string)> m_terminator; callback<> m_stop_callback;
threading_util::spin_lock m_updatelock; threading_util::spin_lock m_lock;
// std::timed_mutex m_mutex;
const bar_settings m_bar; const bar_settings m_bar;
const logger& m_log; const logger& m_log;
@ -351,11 +338,11 @@ namespace modules {
unique_ptr<builder> m_builder; unique_ptr<builder> m_builder;
unique_ptr<module_formatter> m_formatter; unique_ptr<module_formatter> m_formatter;
vector<thread> m_threads; vector<thread> m_threads;
thread m_mainthread;
private: private:
stateflag m_enabled{false}; stateflag m_enabled{true};
string m_cache; string m_cache;
thread m_broadcast_thread;
}; };
// }}} // }}}
@ -368,8 +355,6 @@ namespace modules {
using module<Impl>::module; using module<Impl>::module;
void start() { void start() {
CAST_MOD(Impl)->enable(true);
CAST_MOD(Impl)->setup();
CAST_MOD(Impl)->broadcast(); CAST_MOD(Impl)->broadcast();
} }
@ -389,8 +374,7 @@ namespace modules {
using module<Impl>::module; using module<Impl>::module;
void start() { void start() {
CAST_MOD(Impl)->enable(true); CAST_MOD(Impl)->m_mainthread = thread(&timer_module::runner, this);
CAST_MOD(Impl)->m_threads.emplace_back(thread(&timer_module::runner, this));
} }
protected: protected:
@ -398,11 +382,9 @@ namespace modules {
void runner() { void runner() {
try { try {
CAST_MOD(Impl)->setup(); while (CONST_MOD(Impl).running()) {
std::lock_guard<threading_util::spin_lock> guard(this->m_lock);
while (CONST_MOD(Impl).enabled()) {
{ {
std::lock_guard<threading_util::spin_lock> lck(this->m_updatelock);
if (CAST_MOD(Impl)->update()) if (CAST_MOD(Impl)->update())
CAST_MOD(Impl)->broadcast(); CAST_MOD(Impl)->broadcast();
} }
@ -425,34 +407,36 @@ namespace modules {
using module<Impl>::module; using module<Impl>::module;
void start() { void start() {
CAST_MOD(Impl)->enable(true); CAST_MOD(Impl)->m_mainthread = thread(&event_module::runner, this);
CAST_MOD(Impl)->m_threads.emplace_back(thread(&event_module::runner, this));
} }
protected: protected:
void runner() { void runner() {
try { try {
CAST_MOD(Impl)->setup(); // Send initial broadcast to warmup cache
if (CONST_MOD(Impl).running()) {
CAST_MOD(Impl)->update();
CAST_MOD(Impl)->broadcast();
}
// warmup while (CONST_MOD(Impl).running()) {
CAST_MOD(Impl)->update();
CAST_MOD(Impl)->broadcast();
while (CONST_MOD(Impl).enabled()) {
CAST_MOD(Impl)->idle(); CAST_MOD(Impl)->idle();
if (!CONST_MOD(Impl).enabled()) if (!CONST_MOD(Impl).running())
break; break;
std::lock_guard<threading_util::spin_lock> lck(this->m_updatelock); std::lock_guard<threading_util::spin_lock> guard(this->m_lock);
{ {
if (!CAST_MOD(Impl)->has_event()) if (!CAST_MOD(Impl)->has_event())
continue; continue;
else if (!CAST_MOD(Impl)->update()) if (!CONST_MOD(Impl).running())
break;
if (!CAST_MOD(Impl)->update())
continue; continue;
else
CAST_MOD(Impl)->broadcast();
} }
if (CONST_MOD(Impl).running())
CAST_MOD(Impl)->broadcast();
} }
} catch (const module_error& err) { } catch (const module_error& err) {
CAST_MOD(Impl)->halt(err.what()); CAST_MOD(Impl)->halt(err.what());
@ -471,18 +455,19 @@ namespace modules {
using module<Impl>::module; using module<Impl>::module;
void start() { void start() {
CAST_MOD(Impl)->enable(true); CAST_MOD(Impl)->m_mainthread = thread(&inotify_module::runner, this);
CAST_MOD(Impl)->m_threads.emplace_back(thread(&inotify_module::runner, this));
} }
protected: protected:
void runner() { void runner() {
try { try {
CAST_MOD(Impl)->setup(); // Send initial broadcast to warmup cache
CAST_MOD(Impl)->on_event(nullptr); // warmup if (CONST_MOD(Impl).running()) {
CAST_MOD(Impl)->broadcast(); CAST_MOD(Impl)->on_event(nullptr);
CAST_MOD(Impl)->broadcast();
}
while (CAST_MOD(Impl)->enabled()) { while (CONST_MOD(Impl).running()) {
CAST_MOD(Impl)->poll_events(); CAST_MOD(Impl)->poll_events();
} }
} catch (const module_error& err) { } catch (const module_error& err) {
@ -517,22 +502,28 @@ namespace modules {
return; return;
} }
while (CONST_MOD(Impl).enabled()) { while (CONST_MOD(Impl).running()) {
for (auto&& w : watches) { std::unique_lock<threading_util::spin_lock> guard(this->m_lock);
this->m_log.trace_x("%s: Poll inotify watch %s", CONST_MOD(Impl).name(), w->path()); {
std::lock_guard<threading_util::spin_lock> lck(this->m_updatelock); for (auto&& w : watches) {
this->m_log.trace_x("%s: Poll inotify watch %s", CONST_MOD(Impl).name(), w->path());
if (w->poll(1000 / watches.size())) { if (w->poll(1000 / watches.size())) {
auto event = w->get_event(); auto event = w->get_event();
w->remove(); w->remove();
if (CAST_MOD(Impl)->on_event(event.get())) if (CAST_MOD(Impl)->on_event(event.get()))
CAST_MOD(Impl)->broadcast(); CAST_MOD(Impl)->broadcast();
return; return;
}
if (!CONST_MOD(Impl).running())
break;
} }
} }
guard.unlock();
CAST_MOD(Impl)->idle(); CAST_MOD(Impl)->idle();
} }
} }

View File

@ -83,7 +83,6 @@ namespace modules {
} }
void teardown() { void teardown() {
wakeup();
m_mpd.reset(); m_mpd.reset();
} }

View File

@ -165,7 +165,7 @@ namespace modules {
const chrono::milliseconds framerate{m_animation_packetloss->framerate()}; const chrono::milliseconds framerate{m_animation_packetloss->framerate()};
const auto dur = chrono::duration<double>(framerate); const auto dur = chrono::duration<double>(framerate);
while (enabled()) { while (running()) {
if (m_connected && m_packetloss) if (m_connected && m_packetloss)
broadcast(); broadcast();
sleep(dur); sleep(dur);

View File

@ -56,9 +56,6 @@ namespace modules {
if (!m_tail) if (!m_tail)
return true; return true;
if (!enabled())
return false;
try { try {
if (!m_command || !m_command->is_running()) { if (!m_command || !m_command->is_running()) {
auto exec = string_util::replace_all(m_exec, "%counter%", to_string(++m_counter)); auto exec = string_util::replace_all(m_exec, "%counter%", to_string(++m_counter));

View File

@ -171,10 +171,15 @@ namespace string_util {
return static_cast<const stringstream&>(os).str(); return static_cast<const stringstream&>(os).str();
} }
/**
* Hash type
*/
using hash_type = unsigned long;
/** /**
* Compute string hash * Compute string hash
*/ */
inline auto hash(string src) { inline hash_type hash(string src) {
return std::hash<string>()(src); return std::hash<string>()(src);
} }
} }

View File

@ -0,0 +1,981 @@
// Provides an efficient blocking version of moodycamel::ConcurrentQueue.
// ©2015-2016 Cameron Desrochers. Distributed under the terms of the simplified
// BSD license, available at the top of concurrentqueue.h.
// Uses Jeff Preshing's semaphore implementation (under the terms of its
// separate zlib license, embedded below).
#pragma once
#include "concurrentqueue.h"
#include <type_traits>
#include <cerrno>
#include <memory>
#include <chrono>
#include <ctime>
#if defined(_WIN32)
// Avoid including windows.h in a header; we only need a handful of
// items, so we'll redeclare them here (this is relatively safe since
// the API generally has to remain stable between Windows versions).
// I know this is an ugly hack but it still beats polluting the global
// namespace with thousands of generic names or adding a .cpp for nothing.
extern "C" {
struct _SECURITY_ATTRIBUTES;
__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName);
__declspec(dllimport) int __stdcall CloseHandle(void* hObject);
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);
__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);
}
#elif defined(__MACH__)
#include <mach/mach.h>
#elif defined(__unix__)
#include <semaphore.h>
#endif
namespace moodycamel
{
namespace details
{
// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's
// portable + lightweight semaphore implementations, originally from
// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
// LICENSE:
// Copyright (c) 2015 Jeff Preshing
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgement in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
namespace mpmc_sema
{
#if defined(_WIN32)
class Semaphore
{
private:
void* m_hSema;
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
const long maxLong = 0x7fffffff;
m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
}
~Semaphore()
{
CloseHandle(m_hSema);
}
void wait()
{
const unsigned long infinite = 0xffffffff;
WaitForSingleObject(m_hSema, infinite);
}
bool try_wait()
{
const unsigned long RC_WAIT_TIMEOUT = 0x00000102;
return WaitForSingleObject(m_hSema, 0) != RC_WAIT_TIMEOUT;
}
bool timed_wait(std::uint64_t usecs)
{
const unsigned long RC_WAIT_TIMEOUT = 0x00000102;
return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) != RC_WAIT_TIMEOUT;
}
void signal(int count = 1)
{
ReleaseSemaphore(m_hSema, count, nullptr);
}
};
#elif defined(__MACH__)
//---------------------------------------------------------
// Semaphore (Apple iOS and OSX)
// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
//---------------------------------------------------------
class Semaphore
{
private:
semaphore_t m_sema;
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
}
~Semaphore()
{
semaphore_destroy(mach_task_self(), m_sema);
}
void wait()
{
semaphore_wait(m_sema);
}
bool try_wait()
{
return timed_wait(0);
}
bool timed_wait(std::uint64_t timeout_usecs)
{
mach_timespec_t ts;
ts.tv_sec = timeout_usecs / 1000000;
ts.tv_nsec = (timeout_usecs % 1000000) * 1000;
// added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
kern_return_t rc = semaphore_timedwait(m_sema, ts);
return rc != KERN_OPERATION_TIMED_OUT;
}
void signal()
{
semaphore_signal(m_sema);
}
void signal(int count)
{
while (count-- > 0)
{
semaphore_signal(m_sema);
}
}
};
#elif defined(__unix__)
//---------------------------------------------------------
// Semaphore (POSIX, Linux)
//---------------------------------------------------------
class Semaphore
{
private:
sem_t m_sema;
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
sem_init(&m_sema, 0, initialCount);
}
~Semaphore()
{
sem_destroy(&m_sema);
}
void wait()
{
// http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
int rc;
do {
rc = sem_wait(&m_sema);
} while (rc == -1 && errno == EINTR);
}
bool try_wait()
{
int rc;
do {
rc = sem_trywait(&m_sema);
} while (rc == -1 && errno == EINTR);
return !(rc == -1 && errno == EAGAIN);
}
bool timed_wait(std::uint64_t usecs)
{
struct timespec ts;
const int usecs_in_1_sec = 1000000;
const int nsecs_in_1_sec = 1000000000;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += usecs / usecs_in_1_sec;
ts.tv_nsec += (usecs % usecs_in_1_sec) * 1000;
// sem_timedwait bombs if you have more than 1e9 in tv_nsec
// so we have to clean things up before passing it in
if (ts.tv_nsec > nsecs_in_1_sec) {
ts.tv_nsec -= nsecs_in_1_sec;
++ts.tv_sec;
}
int rc;
do {
rc = sem_timedwait(&m_sema, &ts);
} while (rc == -1 && errno == EINTR);
return !(rc == -1 && errno == ETIMEDOUT);
}
void signal()
{
sem_post(&m_sema);
}
void signal(int count)
{
while (count-- > 0)
{
sem_post(&m_sema);
}
}
};
#else
#error Unsupported platform! (No semaphore wrapper available)
#endif
//---------------------------------------------------------
// LightweightSemaphore
//---------------------------------------------------------
class LightweightSemaphore
{
public:
typedef std::make_signed<std::size_t>::type ssize_t;
private:
std::atomic<ssize_t> m_count;
Semaphore m_sema;
bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1)
{
ssize_t oldCount;
// Is there a better way to set the initial spin count?
// If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC,
// as threads start hitting the kernel semaphore.
int spin = 10000;
while (--spin >= 0)
{
oldCount = m_count.load(std::memory_order_relaxed);
if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
return true;
std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop.
}
oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
if (oldCount > 0)
return true;
if (timeout_usecs < 0)
{
m_sema.wait();
return true;
}
if (m_sema.timed_wait((std::uint64_t)timeout_usecs))
return true;
// At this point, we've timed out waiting for the semaphore, but the
// count is still decremented indicating we may still be waiting on
// it. So we have to re-adjust the count, but only if the semaphore
// wasn't signaled enough times for us too since then. If it was, we
// need to release the semaphore too.
while (true)
{
oldCount = m_count.load(std::memory_order_acquire);
if (oldCount >= 0 && m_sema.try_wait())
return true;
if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed))
return false;
}
}
ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1)
{
assert(max > 0);
ssize_t oldCount;
int spin = 10000;
while (--spin >= 0)
{
oldCount = m_count.load(std::memory_order_relaxed);
if (oldCount > 0)
{
ssize_t newCount = oldCount > max ? oldCount - max : 0;
if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
return oldCount - newCount;
}
std::atomic_signal_fence(std::memory_order_acquire);
}
oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
if (oldCount <= 0)
{
if (timeout_usecs < 0)
m_sema.wait();
else if (!m_sema.timed_wait((std::uint64_t)timeout_usecs))
{
while (true)
{
oldCount = m_count.load(std::memory_order_acquire);
if (oldCount >= 0 && m_sema.try_wait())
break;
if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed))
return 0;
}
}
}
if (max > 1)
return 1 + tryWaitMany(max - 1);
return 1;
}
public:
LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount)
{
assert(initialCount >= 0);
}
bool tryWait()
{
ssize_t oldCount = m_count.load(std::memory_order_relaxed);
while (oldCount > 0)
{
if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
return true;
}
return false;
}
void wait()
{
if (!tryWait())
waitWithPartialSpinning();
}
bool wait(std::int64_t timeout_usecs)
{
return tryWait() || waitWithPartialSpinning(timeout_usecs);
}
// Acquires between 0 and (greedily) max, inclusive
ssize_t tryWaitMany(ssize_t max)
{
assert(max >= 0);
ssize_t oldCount = m_count.load(std::memory_order_relaxed);
while (oldCount > 0)
{
ssize_t newCount = oldCount > max ? oldCount - max : 0;
if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
return oldCount - newCount;
}
return 0;
}
// Acquires at least one, and (greedily) at most max
ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs)
{
assert(max >= 0);
ssize_t result = tryWaitMany(max);
if (result == 0 && max > 0)
result = waitManyWithPartialSpinning(max, timeout_usecs);
return result;
}
ssize_t waitMany(ssize_t max)
{
ssize_t result = waitMany(max, -1);
assert(result > 0);
return result;
}
void signal(ssize_t count = 1)
{
assert(count >= 0);
ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release);
ssize_t toRelease = -oldCount < count ? -oldCount : count;
if (toRelease > 0)
{
m_sema.signal((int)toRelease);
}
}
ssize_t availableApprox() const
{
ssize_t count = m_count.load(std::memory_order_relaxed);
return count > 0 ? count : 0;
}
};
} // end namespace mpmc_sema
} // end namespace details
// This is a blocking version of the queue. It has an almost identical interface to
// the normal non-blocking version, with the addition of various wait_dequeue() methods
// and the removal of producer-specific dequeue methods.
template<typename T, typename Traits = ConcurrentQueueDefaultTraits>
class BlockingConcurrentQueue
{
private:
typedef ::moodycamel::ConcurrentQueue<T, Traits> ConcurrentQueue;
typedef details::mpmc_sema::LightweightSemaphore LightweightSemaphore;
public:
typedef typename ConcurrentQueue::producer_token_t producer_token_t;
typedef typename ConcurrentQueue::consumer_token_t consumer_token_t;
typedef typename ConcurrentQueue::index_t index_t;
typedef typename ConcurrentQueue::size_t size_t;
typedef typename std::make_signed<size_t>::type ssize_t;
static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE;
static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD;
static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE;
static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE;
static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE;
static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE;
static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE;
public:
// Creates a queue with at least `capacity` element slots; note that the
// actual number of elements that can be inserted without additional memory
// allocation depends on the number of producers and the block size (e.g. if
// the block size is equal to `capacity`, only a single block will be allocated
// up-front, which means only a single producer will be able to enqueue elements
// without an extra allocation -- blocks aren't shared between producers).
// This method is not thread safe -- it is up to the user to ensure that the
// queue is fully constructed before it starts being used by other threads (this
// includes making the memory effects of construction visible, possibly with a
// memory barrier).
explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE)
: inner(capacity), sema(create<LightweightSemaphore>(), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
{
assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
if (!sema) {
MOODYCAMEL_THROW(std::bad_alloc());
}
}
BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers)
: inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create<LightweightSemaphore>(), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
{
assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
if (!sema) {
MOODYCAMEL_THROW(std::bad_alloc());
}
}
// Disable copying and copy assignment
BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
// Moving is supported, but note that it is *not* a thread-safe operation.
// Nobody can use the queue while it's being moved, and the memory effects
// of that move must be propagated to other threads before they can use it.
// Note: When a queue is moved, its tokens are still valid but can only be
// used with the destination queue (i.e. semantically they are moved along
// with the queue itself).
BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
: inner(std::move(other.inner)), sema(std::move(other.sema))
{ }
inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
{
return swap_internal(other);
}
// Swaps this queue's state with the other's. Not thread-safe.
// Swapping two queues does not invalidate their tokens, however
// the tokens that were created for one queue must be used with
// only the swapped queue (i.e. the tokens are tied to the
// queue's movable state, not the object itself).
inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT
{
swap_internal(other);
}
private:
BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other)
{
if (this == &other) {
return *this;
}
inner.swap(other.inner);
sema.swap(other.sema);
return *this;
}
public:
// Enqueues a single item (by copying it).
// Allocates memory if required. Only fails if memory allocation fails (or implicit
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(T const& item)
{
if (details::likely(inner.enqueue(item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible).
// Allocates memory if required. Only fails if memory allocation fails (or implicit
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(T&& item)
{
if (details::likely(inner.enqueue(std::move(item)))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by copying it) using an explicit producer token.
// Allocates memory if required. Only fails if memory allocation fails (or
// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(producer_token_t const& token, T const& item)
{
if (details::likely(inner.enqueue(token, item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible) using an explicit producer token.
// Allocates memory if required. Only fails if memory allocation fails (or
// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(producer_token_t const& token, T&& item)
{
if (details::likely(inner.enqueue(token, std::move(item)))) {
sema->signal();
return true;
}
return false;
}
// Enqueues several items.
// Allocates memory if required. Only fails if memory allocation fails (or
// implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
// is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Note: Use std::make_move_iterator if the elements should be moved instead of copied.
// Thread-safe.
template<typename It>
inline bool enqueue_bulk(It itemFirst, size_t count)
{
if (details::likely(inner.enqueue_bulk(std::forward<It>(itemFirst), count))) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Enqueues several items using an explicit producer token.
// Allocates memory if required. Only fails if memory allocation fails
// (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Note: Use std::make_move_iterator if the elements should be moved
// instead of copied.
// Thread-safe.
template<typename It>
inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
{
if (details::likely(inner.enqueue_bulk(token, std::forward<It>(itemFirst), count))) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Enqueues a single item (by copying it).
// Does not allocate memory. Fails if not enough room to enqueue (or implicit
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
// is 0).
// Thread-safe.
inline bool try_enqueue(T const& item)
{
if (inner.try_enqueue(item)) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible).
// Does not allocate memory (except for one-time implicit producer).
// Fails if not enough room to enqueue (or implicit production is
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
// Thread-safe.
inline bool try_enqueue(T&& item)
{
if (inner.try_enqueue(std::move(item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by copying it) using an explicit producer token.
// Does not allocate memory. Fails if not enough room to enqueue.
// Thread-safe.
inline bool try_enqueue(producer_token_t const& token, T const& item)
{
if (inner.try_enqueue(token, item)) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible) using an explicit producer token.
// Does not allocate memory. Fails if not enough room to enqueue.
// Thread-safe.
inline bool try_enqueue(producer_token_t const& token, T&& item)
{
if (inner.try_enqueue(token, std::move(item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues several items.
// Does not allocate memory (except for one-time implicit producer).
// Fails if not enough room to enqueue (or implicit production is
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
// Note: Use std::make_move_iterator if the elements should be moved
// instead of copied.
// Thread-safe.
template<typename It>
inline bool try_enqueue_bulk(It itemFirst, size_t count)
{
if (inner.try_enqueue_bulk(std::forward<It>(itemFirst), count)) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Enqueues several items using an explicit producer token.
// Does not allocate memory. Fails if not enough room to enqueue.
// Note: Use std::make_move_iterator if the elements should be moved
// instead of copied.
// Thread-safe.
template<typename It>
inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
{
if (inner.try_enqueue_bulk(token, std::forward<It>(itemFirst), count)) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Attempts to dequeue from the queue.
// Returns false if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename U>
inline bool try_dequeue(U& item)
{
if (sema->tryWait()) {
while (!inner.try_dequeue(item)) {
continue;
}
return true;
}
return false;
}
// Attempts to dequeue from the queue using an explicit consumer token.
// Returns false if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename U>
inline bool try_dequeue(consumer_token_t& token, U& item)
{
if (sema->tryWait()) {
while (!inner.try_dequeue(token, item)) {
continue;
}
return true;
}
return false;
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued.
// Returns 0 if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename It>
inline size_t try_dequeue_bulk(It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued.
// Returns 0 if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename It>
inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
}
return count;
}
// Blocks the current thread until there's something to dequeue, then
// dequeues it.
// Never allocates. Thread-safe.
template<typename U>
inline void wait_dequeue(U& item)
{
sema->wait();
while (!inner.try_dequeue(item)) {
continue;
}
}
// Blocks the current thread until either there's something to dequeue
// or the timeout (specified in microseconds) expires. Returns false
// without setting `item` if the timeout expires, otherwise assigns
// to `item` and returns true.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue.
// Never allocates. Thread-safe.
template<typename U>
inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs)
{
if (!sema->wait(timeout_usecs)) {
return false;
}
while (!inner.try_dequeue(item)) {
continue;
}
return true;
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Never allocates. Thread-safe.
template<typename U, typename Rep, typename Period>
inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Blocks the current thread until there's something to dequeue, then
// dequeues it using an explicit consumer token.
// Never allocates. Thread-safe.
template<typename U>
inline void wait_dequeue(consumer_token_t& token, U& item)
{
sema->wait();
while (!inner.try_dequeue(token, item)) {
continue;
}
}
// Blocks the current thread until either there's something to dequeue
// or the timeout (specified in microseconds) expires. Returns false
// without setting `item` if the timeout expires, otherwise assigns
// to `item` and returns true.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue.
// Never allocates. Thread-safe.
template<typename U>
inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs)
{
if (!sema->wait(timeout_usecs)) {
return false;
}
while (!inner.try_dequeue(token, item)) {
continue;
}
return true;
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Never allocates. Thread-safe.
template<typename U, typename Rep, typename Period>
inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_timed(token, item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued, which will
// always be at least one (this method blocks until the queue
// is non-empty) and at most max.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk(It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue_bulk.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Never allocates. Thread-safe.
template<typename It, typename Rep, typename Period>
inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_bulk_timed<It&>(itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued, which will
// always be at least one (this method blocks until the queue
// is non-empty) and at most max.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue_bulk.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Never allocates. Thread-safe.
template<typename It, typename Rep, typename Period>
inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_bulk_timed<It&>(token, itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Returns an estimate of the total number of elements currently in the queue. This
// estimate is only accurate if the queue has completely stabilized before it is called
// (i.e. all enqueue and dequeue operations have completed and their memory effects are
// visible on the calling thread, and no further operations start while this method is
// being called).
// Thread-safe.
inline size_t size_approx() const
{
return (size_t)sema->availableApprox();
}
// Returns true if the underlying atomic variables used by
// the queue are lock-free (they should be on most platforms).
// Thread-safe.
static bool is_lock_free()
{
return ConcurrentQueue::is_lock_free();
}
private:
template<typename U>
static inline U* create()
{
auto p = (Traits::malloc)(sizeof(U));
return p != nullptr ? new (p) U : nullptr;
}
template<typename U, typename A1>
static inline U* create(A1&& a1)
{
auto p = (Traits::malloc)(sizeof(U));
return p != nullptr ? new (p) U(std::forward<A1>(a1)) : nullptr;
}
template<typename U>
static inline void destroy(U* p)
{
if (p != nullptr) {
p->~U();
}
(Traits::free)(p);
}
private:
ConcurrentQueue inner;
std::unique_ptr<LightweightSemaphore, void (*)(LightweightSemaphore*)> sema;
};
template<typename T, typename Traits>
inline void swap(BlockingConcurrentQueue<T, Traits>& a, BlockingConcurrentQueue<T, Traits>& b) MOODYCAMEL_NOEXCEPT
{
a.swap(b);
}
} // end namespace moodycamel

File diff suppressed because it is too large Load Diff

View File

@ -62,6 +62,7 @@ target_include_directories(${LIBRARY_NAME}_static PUBLIC ${BOOST_INCLUDE_DIR})
target_include_directories(${LIBRARY_NAME}_static PUBLIC ${FONTCONFIG_INCLUDE_DIRS}) target_include_directories(${LIBRARY_NAME}_static PUBLIC ${FONTCONFIG_INCLUDE_DIRS})
target_include_directories(${LIBRARY_NAME}_static PUBLIC ${PROJECT_SOURCE_DIR}/include) target_include_directories(${LIBRARY_NAME}_static PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_include_directories(${LIBRARY_NAME}_static PUBLIC ${PROJECT_SOURCE_DIR}/lib/boost/include) target_include_directories(${LIBRARY_NAME}_static PUBLIC ${PROJECT_SOURCE_DIR}/lib/boost/include)
target_include_directories(${LIBRARY_NAME}_static PUBLIC ${PROJECT_SOURCE_DIR}/lib/concurrentqueue/include)
target_compile_definitions(${BINARY_NAME} PUBLIC target_compile_definitions(${BINARY_NAME} PUBLIC
${X11_XCB_DEFINITIONS} ${X11_XCB_DEFINITIONS}
@ -116,6 +117,7 @@ set(APP_LIBRARIES ${LIBRARY_NAME}_static ${XPP_LIBRARY} PARENT_SCOPE)
set(APP_INCLUDE_DIRS set(APP_INCLUDE_DIRS
${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/lib/boost/include ${PROJECT_SOURCE_DIR}/lib/boost/include
${PROJECT_SOURCE_DIR}/lib/concurrentqueue/include
${XPP_INCLUDE_DIRS} ${XPP_INCLUDE_DIRS}
PARENT_SCOPE) PARENT_SCOPE)