fix(mpd): regularly timeout the event listener to prevent timeout

The MPD server has a connection_timeout that defaults to 60. If no data
is transferred in this timespan, the connection is closed. If the
connection is closed while the event listener is listening for events,
it will block forever. By timing out the event listening and
re-connecting regularly, we prevent this issue. An option "timeout" has
been added for users that have a lower server connection_timeout than
default. Fixes #277
This commit is contained in:
Minijackson 2019-04-21 10:54:40 +02:00
parent 768bc29bc1
commit b50650f63f
No known key found for this signature in database
GPG Key ID: FEA888C9F5D64F62
2 changed files with 51 additions and 21 deletions

View File

@ -48,6 +48,8 @@ class MPD : public ALabel {
const char* server_; const char* server_;
const unsigned port_; const unsigned port_;
unsigned timeout_;
// We need a mutex here because we can trigger updates from multiple thread: // We need a mutex here because we can trigger updates from multiple thread:
// the event based updates, the periodic updates needed for the elapsed time, // the event based updates, the periodic updates needed for the elapsed time,
// and the click play/pause feature // and the click play/pause feature

View File

@ -7,17 +7,29 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "{album} - {artist} - {title}", 5), : ALabel(config, "{album} - {artist} - {title}", 5),
module_name_(id.empty() ? "mpd" : "mpd#" + id), module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr), server_(nullptr),
port_(config["port"].asUInt()), port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
connection_(nullptr, &mpd_connection_free), connection_(nullptr, &mpd_connection_free),
alternate_connection_(nullptr, &mpd_connection_free), alternate_connection_(nullptr, &mpd_connection_free),
status_(nullptr, &mpd_status_free), status_(nullptr, &mpd_status_free),
song_(nullptr, &mpd_song_free) { song_(nullptr, &mpd_song_free) {
if (!config_["port"].isNull() && !config_["port"].isUInt()) {
std::cerr << module_name_ << ": `port` configuration should be an unsigned int" << std::endl;
}
if (!config_["timeout"].isNull() && !config_["timeout"].isUInt()) {
std::cerr << module_name_ << ": `timeout` configuration should be an unsigned int" << std::endl;
}
label_.set_name("mpd"); label_.set_name("mpd");
if (!id.empty()) { if (!id.empty()) {
label_.get_style_context()->add_class(id); label_.get_style_context()->add_class(id);
} }
if (!config["server"].isNull()) { if (!config["server"].isNull()) {
if (!config_["server"].isString()) {
std::cerr << module_name_ << "`server` configuration should be a string" << std::endl;
}
server_ = config["server"].asCString(); server_ = config["server"].asCString();
} }
@ -50,18 +62,18 @@ auto waybar::modules::MPD::update() -> void {
std::thread waybar::modules::MPD::event_listener() { std::thread waybar::modules::MPD::event_listener() {
return std::thread([this] { return std::thread([this] {
while (true) { while (true) {
try {
if (connection_ == nullptr) { if (connection_ == nullptr) {
// Retry periodically if no connection // Retry periodically if no connection
try {
update(); update();
} catch (const std::exception& e) {
std::cerr << module_name_ + ": " + e.what() << std::endl;
}
std::this_thread::sleep_for(interval_); std::this_thread::sleep_for(interval_);
} else { } else {
waitForEvent(); waitForEvent();
dp.emit(); dp.emit();
} }
} catch (const std::exception& e) {
std::cerr << module_name_ + ": " + e.what() << std::endl;
}
} }
}); });
} }
@ -76,12 +88,12 @@ std::thread waybar::modules::MPD::periodic_updater() {
} }
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) { std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) {
std::string result = config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A"; std::string result =
config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A";
const char* tag = mpd_song_get_tag(song_.get(), type, idx); const char* tag = mpd_song_get_tag(song_.get(), type, idx);
// mpd_song_get_tag can return NULL, so make sure it's valid before setting // mpd_song_get_tag can return NULL, so make sure it's valid before setting
if (tag) if (tag) result = tag;
result = tag;
return result; return result;
} }
@ -89,8 +101,9 @@ std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) {
void waybar::modules::MPD::setLabel() { void waybar::modules::MPD::setLabel() {
if (connection_ == nullptr) { if (connection_ == nullptr) {
label_.get_style_context()->add_class("disconnected"); label_.get_style_context()->add_class("disconnected");
// In the case connection closed while MPD is stopped
label_.get_style_context()->remove_class("stopped"); label_.get_style_context()->remove_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
auto format = config_["format-disconnected"].isString() auto format = config_["format-disconnected"].isString()
? config_["format-disconnected"].asString() ? config_["format-disconnected"].asString()
@ -231,10 +244,11 @@ void waybar::modules::MPD::tryConnect() {
return; return;
} }
connection_ = unique_connection(mpd_connection_new(server_, port_, 5'000), &mpd_connection_free); connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
alternate_connection_ = alternate_connection_ =
unique_connection(mpd_connection_new(server_, port_, 5'000), &mpd_connection_free); unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
if (connection_ == nullptr || alternate_connection_ == nullptr) { if (connection_ == nullptr || alternate_connection_ == nullptr) {
std::cerr << module_name_ << ": Failed to connect to MPD" << std::endl; std::cerr << module_name_ << ": Failed to connect to MPD" << std::endl;
@ -250,19 +264,22 @@ void waybar::modules::MPD::tryConnect() {
connection_.reset(); connection_.reset();
alternate_connection_.reset(); alternate_connection_.reset();
} }
std::cerr << module_name_ << ": Connected to MPD" << std::endl;
} }
void waybar::modules::MPD::checkErrors(mpd_connection* conn) { void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
switch (mpd_connection_get_error(conn)) { switch (mpd_connection_get_error(conn)) {
case MPD_ERROR_SUCCESS: case MPD_ERROR_SUCCESS:
mpd_connection_clear_error(conn);
return; return;
case MPD_ERROR_TIMEOUT:
case MPD_ERROR_CLOSED: case MPD_ERROR_CLOSED:
std::cerr << module_name_ << ": Connection to MPD closed" << std::endl;
mpd_connection_clear_error(conn); mpd_connection_clear_error(conn);
connection_.reset(); connection_.reset();
alternate_connection_.reset(); alternate_connection_.reset();
state_ = MPD_STATE_UNKNOWN; state_ = MPD_STATE_UNKNOWN;
return; throw std::runtime_error("Connection to MPD closed");
default: default:
auto error_message = mpd_connection_get_error_message(conn); auto error_message = mpd_connection_get_error_message(conn);
mpd_connection_clear_error(conn); mpd_connection_clear_error(conn);
@ -285,8 +302,19 @@ void waybar::modules::MPD::waitForEvent() {
auto conn = alternate_connection_.get(); auto conn = alternate_connection_.get();
// Wait for a player (play/pause), option (random, shuffle, etc.), or playlist // Wait for a player (play/pause), option (random, shuffle, etc.), or playlist
// change // change
mpd_run_idle_mask(conn, if (!mpd_send_idle_mask(
static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_PLAYLIST)); conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_PLAYLIST))) {
checkErrors(conn);
return;
}
// alternate_idle_ = true;
// See issue #277:
// https://github.com/Alexays/Waybar/issues/277
mpd_recv_idle(conn, /* disable_timeout = */ false);
checkErrors(conn);
mpd_response_finish(conn);
checkErrors(conn); checkErrors(conn);
} }