fix(niri): reconnect IPC and stop per-event throttling to prevent freeze

The niri IPC worker slept 1ms per event and never reconnected. Under an
event burst the per-event cap back-pressures the socket, niri fills its
send buffer and drops the stream; read_line then returns false, the
detached thread exits and the module freezes permanently.

Remove the per-event sleep so events drain as fast as they arrive, and
wrap the socket setup and read loop in a reconnect loop that backs off
and re-establishes the stream on drop. A running_ flag lets the thread
exit cleanly on teardown.

Fixes #5117.
This commit is contained in:
Alex
2026-07-04 03:14:13 +02:00
parent 3831524ba8
commit 6672e924df
2 changed files with 58 additions and 32 deletions
+54 -32
View File
@@ -23,6 +23,8 @@ namespace waybar::modules::niri {
IPC::IPC() { startIPC(); }
IPC::~IPC() { running_ = false; }
int IPC::connectToSocket() {
const char* socket_path = getenv("NIRI_SOCKET");
@@ -55,40 +57,60 @@ int IPC::connectToSocket() {
void IPC::startIPC() {
// will start IPC and relay events to parseIPC
int socketfd = connectToSocket();
std::thread([this, socketfd]() {
std::thread([this]() {
spdlog::info("Niri IPC starting");
auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);
auto istream = Gio::DataInputStream::create(unix_istream);
auto ostream = Gio::DataOutputStream::create(unix_ostream);
if (!ostream->put_string("\"EventStream\"\n") || !ostream->flush()) {
spdlog::error("Niri IPC: failed to start event stream");
return;
}
std::string line;
if (!istream->read_line(line) || line != R"({"Ok":"Handled"})") {
spdlog::error("Niri IPC: failed to start event stream");
return;
}
while (istream->read_line(line)) {
spdlog::debug("Niri IPC: received {}", line);
// Reconnect loop: if the event stream drops we back off briefly and
// re-establish the socket instead of leaving the module frozen forever.
while (running_) {
int socketfd;
try {
parseIPC(line);
socketfd = connectToSocket();
} catch (std::exception& e) {
spdlog::warn("Failed to parse IPC message: {}, reason: {}", line, e.what());
} catch (...) {
throw;
spdlog::error("Niri IPC: failed to connect: {}", e.what());
std::this_thread::sleep_for(std::chrono::seconds(2));
continue;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
auto unix_istream = Gio::UnixInputStream::create(socketfd, true);
auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);
auto istream = Gio::DataInputStream::create(unix_istream);
auto ostream = Gio::DataOutputStream::create(unix_ostream);
if (!ostream->put_string("\"EventStream\"\n") || !ostream->flush()) {
spdlog::error("Niri IPC: failed to start event stream");
std::this_thread::sleep_for(std::chrono::seconds(2));
continue;
}
std::string line;
if (!istream->read_line(line) || line != R"({"Ok":"Handled"})") {
spdlog::error("Niri IPC: failed to start event stream");
std::this_thread::sleep_for(std::chrono::seconds(2));
continue;
}
// Drain events as fast as they arrive; throttling here back-pressures the
// socket, fills niri's send buffer and makes niri drop the stream.
while (running_ && istream->read_line(line)) {
spdlog::debug("Niri IPC: received {}", line);
try {
parseIPC(line);
} catch (std::exception& e) {
spdlog::warn("Failed to parse IPC message: {}, reason: {}", line, e.what());
} catch (...) {
throw;
}
}
if (!running_) break;
spdlog::warn("Niri IPC: event stream closed, reconnecting");
std::this_thread::sleep_for(std::chrono::seconds(2));
}
spdlog::info("Niri IPC stopping");
}).detach();
}
@@ -196,12 +218,12 @@ void IPC::parseIPC(const std::string& line) {
for (auto& win : windows_) {
win["is_focused"] = focused && win["id"].asUInt64() == id;
}
} else if (const auto &payload = ev["WindowLayoutsChanged"]) {
const auto &values = payload["changes"];
for (const auto &changed : values) {
} else if (const auto& payload = ev["WindowLayoutsChanged"]) {
const auto& values = payload["changes"];
for (const auto& changed : values) {
const auto id = changed[0].asUInt64();
const auto &change = changed[1];
for (auto &win : windows_) {
const auto& change = changed[1];
for (auto& win : windows_) {
if (win["id"].asUInt64() == id) {
win["layout"] = change;
break;