fix(mango): reconnect IPC event thread on disconnect
The IPC event thread had no reconnect: on POLLHUP/POLLERR/POLLNVAL or read()==0/error it broke out of the loop and the thread exited permanently, freezing every mango module with stale content until Waybar was restarted. Wrap the connect + poll/read loop in a reconnect loop with a bounded 2s backoff, re-establishing the socket and resuming on disconnect, modeled on the niri backend. Add an atomic running_ flag so the worker exits cleanly on teardown; the destructor now sets it false before closing the socket so the worker breaks out and joins, and leaves the final close to the destructor to avoid a double close.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
// include/modules/mango/backend.hpp
|
// include/modules/mango/backend.hpp
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -52,6 +53,7 @@ class IPC {
|
|||||||
|
|
||||||
static Json::Value sendCommand(const std::string& cmd);
|
static Json::Value sendCommand(const std::string& cmd);
|
||||||
|
|
||||||
|
std::atomic<bool> running_ = true;
|
||||||
int sockfd_ = -1;
|
int sockfd_ = -1;
|
||||||
std::thread ipc_thread_;
|
std::thread ipc_thread_;
|
||||||
mutable std::mutex data_mutex_;
|
mutable std::mutex data_mutex_;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -100,71 +101,121 @@ void IPC::sendAsync(const Json::Value& request) {
|
|||||||
IPC::IPC() : sockfd_(-1), active_client_(Json::nullValue) { startIPC(); }
|
IPC::IPC() : sockfd_(-1), active_client_(Json::nullValue) { startIPC(); }
|
||||||
|
|
||||||
IPC::~IPC() {
|
IPC::~IPC() {
|
||||||
|
running_ = false;
|
||||||
if (sockfd_ != -1) close(sockfd_);
|
if (sockfd_ != -1) close(sockfd_);
|
||||||
if (ipc_thread_.joinable()) ipc_thread_.join();
|
if (ipc_thread_.joinable()) ipc_thread_.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IPC::startIPC() {
|
void IPC::startIPC() {
|
||||||
|
// Connect synchronously so a missing socket (this WM isn't the active
|
||||||
|
// compositor) throws here and lets the module constructor fail, instead of
|
||||||
|
// the module always attaching with a permanently empty widget.
|
||||||
sockfd_ = IPC::connectToSocket();
|
sockfd_ = IPC::connectToSocket();
|
||||||
|
|
||||||
ipc_thread_ = std::thread([this]() {
|
ipc_thread_ = std::thread([this]() {
|
||||||
spdlog::info("Mango IPC thread started");
|
spdlog::info("Mango IPC thread started");
|
||||||
|
|
||||||
struct pollfd pfd;
|
|
||||||
pfd.fd = sockfd_;
|
|
||||||
pfd.events = POLLIN;
|
|
||||||
|
|
||||||
const std::vector<std::string> subs = {"watch all-monitors"};
|
|
||||||
for (const auto& cmd : subs) {
|
|
||||||
if (write(sockfd_, cmd.c_str(), cmd.size()) != (ssize_t)cmd.size() ||
|
|
||||||
write(sockfd_, "\n", 1) != 1) {
|
|
||||||
spdlog::error("Failed to subscribe to {}", cmd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[4096];
|
char buf[4096];
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
while (true) {
|
bool have_initial_fd = true;
|
||||||
int ret = poll(&pfd, 1, 1000);
|
|
||||||
if (ret == 0) continue;
|
|
||||||
if (ret < 0) {
|
|
||||||
if (errno == EINTR) continue;
|
|
||||||
spdlog::error("IPC poll error: {}", strerror(errno));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
// Reconnect loop: if the event stream drops (POLLHUP/POLLERR, read()==0 or
|
||||||
spdlog::info("Mango IPC socket closed or invalid");
|
// an error) we back off briefly and re-establish the socket instead of
|
||||||
break;
|
// leaving every mango module frozen forever with stale content.
|
||||||
|
while (running_) {
|
||||||
|
if (!have_initial_fd) {
|
||||||
|
try {
|
||||||
|
sockfd_ = IPC::connectToSocket();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::error("Mango IPC: failed to reconnect: {}", e.what());
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
have_initial_fd = false;
|
||||||
|
|
||||||
if (pfd.revents & POLLIN) {
|
bool subscribed = true;
|
||||||
ssize_t n = read(sockfd_, buf, sizeof(buf));
|
const std::vector<std::string> subs = {"watch all-monitors"};
|
||||||
if (n == 0) {
|
for (const auto& cmd : subs) {
|
||||||
spdlog::info("Mango IPC connection closed");
|
if (write(sockfd_, cmd.c_str(), cmd.size()) != (ssize_t)cmd.size() ||
|
||||||
|
write(sockfd_, "\n", 1) != 1) {
|
||||||
|
spdlog::error("Failed to subscribe to {}", cmd);
|
||||||
|
subscribed = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (n < 0) {
|
}
|
||||||
|
if (!subscribed) {
|
||||||
|
if (sockfd_ != -1) {
|
||||||
|
close(sockfd_);
|
||||||
|
sockfd_ = -1;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pollfd pfd;
|
||||||
|
pfd.fd = sockfd_;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
bool connected = true;
|
||||||
|
while (running_ && connected) {
|
||||||
|
int ret = poll(&pfd, 1, 1000);
|
||||||
|
if (ret == 0) continue;
|
||||||
|
if (ret < 0) {
|
||||||
if (errno == EINTR) continue;
|
if (errno == EINTR) continue;
|
||||||
spdlog::error("IPC read error: {}", strerror(errno));
|
spdlog::error("IPC poll error: {}", strerror(errno));
|
||||||
|
connected = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
buffer.append(buf, n);
|
|
||||||
|
|
||||||
size_t pos;
|
if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||||
while ((pos = buffer.find('\n')) != std::string::npos) {
|
spdlog::info("Mango IPC socket closed or invalid");
|
||||||
std::string line = buffer.substr(0, pos);
|
connected = false;
|
||||||
buffer.erase(0, pos + 1);
|
break;
|
||||||
if (line.empty()) continue;
|
}
|
||||||
try {
|
|
||||||
parseIPC(line);
|
if (pfd.revents & POLLIN) {
|
||||||
} catch (const std::exception& e) {
|
ssize_t n = read(sockfd_, buf, sizeof(buf));
|
||||||
spdlog::warn("Failed to parse IPC line: {} - {}", line, e.what());
|
if (n == 0) {
|
||||||
|
spdlog::info("Mango IPC connection closed");
|
||||||
|
connected = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
spdlog::error("IPC read error: {}", strerror(errno));
|
||||||
|
connected = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buffer.append(buf, n);
|
||||||
|
|
||||||
|
size_t pos;
|
||||||
|
while ((pos = buffer.find('\n')) != std::string::npos) {
|
||||||
|
std::string line = buffer.substr(0, pos);
|
||||||
|
buffer.erase(0, pos + 1);
|
||||||
|
if (line.empty()) continue;
|
||||||
|
try {
|
||||||
|
parseIPC(line);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::warn("Failed to parse IPC line: {} - {}", line, e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On shutdown leave the socket for the destructor to close (avoids a
|
||||||
|
// double close); on a genuine disconnect close it before reconnecting.
|
||||||
|
if (!running_) break;
|
||||||
|
if (sockfd_ != -1) {
|
||||||
|
close(sockfd_);
|
||||||
|
sockfd_ = -1;
|
||||||
|
}
|
||||||
|
spdlog::warn("Mango IPC: event stream closed, reconnecting");
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spdlog::info("Mango IPC thread stopping");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user