From 6672e924df67ed08160033c8dc836d79850ccc8e Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jul 2026 03:13:42 +0200 Subject: [PATCH] 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. --- include/modules/niri/backend.hpp | 4 ++ src/modules/niri/backend.cpp | 86 ++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/include/modules/niri/backend.hpp b/include/modules/niri/backend.hpp index 07be039a..acbbf52e 100644 --- a/include/modules/niri/backend.hpp +++ b/include/modules/niri/backend.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -18,6 +19,7 @@ class EventHandler { class IPC { public: IPC(); + ~IPC(); void registerForIPC(const std::string& ev, EventHandler* ev_handler); void unregisterForIPC(EventHandler* handler); @@ -45,6 +47,8 @@ class IPC { util::JsonParser parser_; std::mutex callbackMutex_; std::list> callbacks_; + + std::atomic running_{true}; }; inline std::unique_ptr gIPC; diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index 0900e092..245bf2de 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -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;