Skip to content

Commit 9ca04ee

Browse files
authored
Merge pull request #4048 from LiterallyVoid/literallyvoid/fix-4047-deadlock
Fix signal safety deadlock
2 parents eea9561 + 517eb76 commit 9ca04ee

File tree

1 file changed

+102
-68
lines changed

1 file changed

+102
-68
lines changed

src/main.cpp

Lines changed: 102 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <fcntl.h>
12
#include <spdlog/spdlog.h>
23
#include <sys/types.h>
34
#include <sys/wait.h>
@@ -7,99 +8,132 @@
78
#include <mutex>
89

910
#include "client.hpp"
11+
#include "util/SafeSignal.hpp"
1012

1113
std::mutex reap_mtx;
1214
std::list<pid_t> reap;
13-
volatile bool reload;
1415

15-
void* signalThread(void* args) {
16-
int err;
17-
int signum;
18-
sigset_t mask;
19-
sigemptyset(&mask);
20-
sigaddset(&mask, SIGCHLD);
16+
static int signal_pipe_write_fd;
17+
18+
// Write a single signal to `signal_pipe_write_fd`.
19+
// This function is set as a signal handler, so it must be async-signal-safe.
20+
static void writeSignalToPipe(int signum) {
21+
ssize_t amt = write(signal_pipe_write_fd, &signum, sizeof(int));
22+
23+
// There's not much we can safely do inside of a signal handler.
24+
// Let's just ignore any errors.
25+
(void)amt;
26+
}
27+
28+
// This initializes `signal_pipe_write_fd`, and sets up signal handlers.
29+
//
30+
// This function will run forever, emitting every `SIGUSR1`, `SIGUSR2`,
31+
// `SIGINT`, `SIGCHLD`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received
32+
// to `signal_handler`.
33+
static void catchSignals(waybar::SafeSignal<int>& signal_handler) {
34+
int fd[2];
35+
pipe(fd);
36+
37+
int signal_pipe_read_fd = fd[0];
38+
signal_pipe_write_fd = fd[1];
39+
40+
// This pipe should be able to buffer ~thousands of signals. If it fills up,
41+
// we'll drop signals instead of blocking.
42+
43+
// We can't allow the write end to block because we'll be writing to it in a
44+
// signal handler, which could interrupt the loop that's reading from it and
45+
// deadlock.
46+
47+
fcntl(signal_pipe_write_fd, F_SETFL, O_NONBLOCK);
48+
49+
std::signal(SIGUSR1, writeSignalToPipe);
50+
std::signal(SIGUSR2, writeSignalToPipe);
51+
std::signal(SIGINT, writeSignalToPipe);
52+
std::signal(SIGCHLD, writeSignalToPipe);
53+
54+
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
55+
std::signal(sig, writeSignalToPipe);
56+
}
2157

2258
while (true) {
23-
err = sigwait(&mask, &signum);
24-
if (err != 0) {
25-
spdlog::error("sigwait failed: {}", strerror(errno));
26-
continue;
59+
int signum;
60+
ssize_t amt = read(signal_pipe_read_fd, &signum, sizeof(int));
61+
if (amt < 0) {
62+
spdlog::error("read from signal pipe failed with error {}, closing thread", strerror(errno));
63+
break;
2764
}
2865

29-
switch (signum) {
30-
case SIGCHLD:
31-
spdlog::debug("Received SIGCHLD in signalThread");
32-
if (!reap.empty()) {
33-
reap_mtx.lock();
34-
for (auto it = reap.begin(); it != reap.end(); ++it) {
35-
if (waitpid(*it, nullptr, WNOHANG) == *it) {
36-
spdlog::debug("Reaped child with PID: {}", *it);
37-
it = reap.erase(it);
38-
}
39-
}
40-
reap_mtx.unlock();
41-
}
42-
break;
43-
default:
44-
spdlog::debug("Received signal with number {}, but not handling", signum);
45-
break;
66+
if (amt != sizeof(int)) {
67+
continue;
4668
}
69+
70+
signal_handler.emit(signum);
4771
}
4872
}
4973

50-
void startSignalThread() {
51-
int err;
52-
sigset_t mask;
53-
sigemptyset(&mask);
54-
sigaddset(&mask, SIGCHLD);
55-
56-
// Block SIGCHLD so it can be handled by the signal thread
57-
// Any threads created by this one (the main thread) should not
58-
// modify their signal mask to unblock SIGCHLD
59-
err = pthread_sigmask(SIG_BLOCK, &mask, nullptr);
60-
if (err != 0) {
61-
spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err));
62-
exit(1);
63-
}
74+
// Must be called on the main thread.
75+
//
76+
// If this signal should restart or close the bar, this function will write
77+
// `true` or `false`, respectively, into `reload`.
78+
static void handleSignalMainThread(int signum, bool& reload) {
79+
if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) {
80+
for (auto& bar : waybar::Client::inst()->bars) {
81+
bar->handleSignal(signum);
82+
}
6483

65-
pthread_t thread_id;
66-
err = pthread_create(&thread_id, nullptr, signalThread, nullptr);
67-
if (err != 0) {
68-
spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err));
69-
exit(1);
84+
return;
7085
}
71-
}
7286

73-
int main(int argc, char* argv[]) {
74-
try {
75-
auto* client = waybar::Client::inst();
76-
77-
std::signal(SIGUSR1, [](int /*signal*/) {
87+
switch (signum) {
88+
case SIGUSR1:
89+
spdlog::debug("Visibility toggled");
7890
for (auto& bar : waybar::Client::inst()->bars) {
7991
bar->toggle();
8092
}
81-
});
82-
83-
std::signal(SIGUSR2, [](int /*signal*/) {
93+
break;
94+
case SIGUSR2:
8495
spdlog::info("Reloading...");
8596
reload = true;
8697
waybar::Client::inst()->reset();
87-
});
88-
89-
std::signal(SIGINT, [](int /*signal*/) {
98+
break;
99+
case SIGINT:
90100
spdlog::info("Quitting.");
91101
reload = false;
92102
waybar::Client::inst()->reset();
93-
});
94-
95-
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
96-
std::signal(sig, [](int sig) {
97-
for (auto& bar : waybar::Client::inst()->bars) {
98-
bar->handleSignal(sig);
103+
break;
104+
case SIGCHLD:
105+
spdlog::debug("Received SIGCHLD in signalThread");
106+
if (!reap.empty()) {
107+
reap_mtx.lock();
108+
for (auto it = reap.begin(); it != reap.end(); ++it) {
109+
if (waitpid(*it, nullptr, WNOHANG) == *it) {
110+
spdlog::debug("Reaped child with PID: {}", *it);
111+
it = reap.erase(it);
112+
}
99113
}
100-
});
101-
}
102-
startSignalThread();
114+
reap_mtx.unlock();
115+
}
116+
break;
117+
default:
118+
spdlog::debug("Received signal with number {}, but not handling", signum);
119+
break;
120+
}
121+
}
122+
123+
int main(int argc, char* argv[]) {
124+
try {
125+
auto* client = waybar::Client::inst();
126+
127+
bool reload;
128+
129+
waybar::SafeSignal<int> posix_signal_received;
130+
posix_signal_received.connect([&](int signum) { handleSignalMainThread(signum, reload); });
131+
132+
std::thread signal_thread([&]() { catchSignals(posix_signal_received); });
133+
134+
// Every `std::thread` must be joined or detached.
135+
// This thread should run forever, so detach it.
136+
signal_thread.detach();
103137

104138
auto ret = 0;
105139
do {

0 commit comments

Comments
 (0)