fix(sway/ipc): reconnect on disconnect instead of breaking + CPU-spinning
When sway's event-subscription send buffer overflows during an event flood, sway closes the client connection. The sway IPC event worker (SleeperThread running handleEvent -> recv) then threw on every iteration and the SleeperThread immediately re-invoked it, leaving the sway modules broken while busy-looping on a dead socket and pegging a CPU. Mirror the niri backend's reconnect loop: on a read/EOF/parse error from the event socket, close the old connection, back off for a couple of seconds (so we don't busy-spin), re-open the socket and replay the same subscriptions, then resume. A running_ flag set at the start of teardown makes the worker bail out cleanly instead of reconnecting to a socket that is being closed on purpose. The IPC message protocol and event parsing are unchanged. Fixes #3166.
This commit is contained in:
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
#include <sigc++/sigc++.h>
|
#include <sigc++/sigc++.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "ipc.hpp"
|
#include "ipc.hpp"
|
||||||
#include "util/SafeSignal.hpp"
|
#include "util/SafeSignal.hpp"
|
||||||
@@ -43,6 +45,14 @@ class Ipc {
|
|||||||
struct ipc_response send(int fd, uint32_t type, const std::string& payload = "");
|
struct ipc_response send(int fd, uint32_t type, const std::string& payload = "");
|
||||||
struct ipc_response recv(int fd);
|
struct ipc_response recv(int fd);
|
||||||
|
|
||||||
|
// Re-establish the event socket and re-subscribe after sway drops us, backing
|
||||||
|
// off between attempts so we don't busy-loop while sway is unavailable.
|
||||||
|
void reconnectEvent();
|
||||||
|
|
||||||
|
std::string socketPath_;
|
||||||
|
std::vector<std::string> subscribed_events_;
|
||||||
|
std::atomic<bool> running_{true};
|
||||||
|
|
||||||
util::ScopedFd fd_;
|
util::ScopedFd fd_;
|
||||||
util::ScopedFd fd_event_;
|
util::ScopedFd fd_event_;
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
|
|||||||
@@ -8,12 +8,14 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
#include <chrono>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "modules/sway/ipc/ipc.hpp"
|
#include "modules/sway/ipc/ipc.hpp"
|
||||||
@@ -41,12 +43,15 @@ void sendAll(int fd, const char* data, size_t size, const char* what) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Ipc::Ipc() {
|
Ipc::Ipc() {
|
||||||
const std::string socketPath = getSocketPath();
|
socketPath_ = getSocketPath();
|
||||||
fd_ = util::ScopedFd(open(socketPath));
|
fd_ = util::ScopedFd(open(socketPath_));
|
||||||
fd_event_ = util::ScopedFd(open(socketPath));
|
fd_event_ = util::ScopedFd(open(socketPath_));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ipc::~Ipc() {
|
Ipc::~Ipc() {
|
||||||
|
// Signal the worker before stopping it so an in-flight recv/reconnect bails
|
||||||
|
// out instead of trying to reconnect to a socket we're tearing down.
|
||||||
|
running_ = false;
|
||||||
thread_.stop();
|
thread_.stop();
|
||||||
|
|
||||||
if (fd_ > 0) {
|
if (fd_ > 0) {
|
||||||
@@ -191,11 +196,48 @@ void Ipc::subscribe(const std::string& payload) {
|
|||||||
if (res.payload != "{\"success\": true}") {
|
if (res.payload != "{\"success\": true}") {
|
||||||
throw std::runtime_error("Unable to subscribe ipc event");
|
throw std::runtime_error("Unable to subscribe ipc event");
|
||||||
}
|
}
|
||||||
|
// Remember the subscription so we can replay it if we have to reconnect.
|
||||||
|
subscribed_events_.push_back(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Ipc::reconnectEvent() {
|
||||||
|
// Sway closed our event connection (typically because its send buffer filled
|
||||||
|
// up during an event flood). Re-establish the socket and re-subscribe to the
|
||||||
|
// same events, backing off between attempts so we don't busy-loop and peg a
|
||||||
|
// CPU while sway is unavailable or keeps dropping us.
|
||||||
|
while (running_) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
|
if (!running_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
fd_event_.reset(open(socketPath_));
|
||||||
|
for (const auto& payload : subscribed_events_) {
|
||||||
|
const auto res = Ipc::send(fd_event_, IPC_SUBSCRIBE, payload);
|
||||||
|
if (res.payload != "{\"success\": true}") {
|
||||||
|
throw std::runtime_error("Unable to re-subscribe ipc event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spdlog::info("Reconnected to sway IPC event socket");
|
||||||
|
return;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::warn("Failed to reconnect to sway IPC ({}), retrying", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ipc::handleEvent() {
|
void Ipc::handleEvent() {
|
||||||
const auto res = Ipc::recv(fd_event_);
|
try {
|
||||||
signal_event.emit(res);
|
const auto res = Ipc::recv(fd_event_);
|
||||||
|
signal_event.emit(res);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
if (!running_) {
|
||||||
|
// The Ipc is being torn down; the socket was closed on purpose.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
spdlog::warn("Lost sway IPC event connection ({}), reconnecting", e.what());
|
||||||
|
reconnectEvent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace waybar::modules::sway
|
} // namespace waybar::modules::sway
|
||||||
|
|||||||
Reference in New Issue
Block a user