mirror of
https://github.com/Z3Prover/z3
synced 2026-01-21 09:34:43 +00:00
Make Ctrl-C handling thread-safe (#7603)
The Ctrl-C handling is not thread safe, there's a global variable g_obj
that is being accessed without any locking. The signal handlers are
per-process, not per-thread, so that different threads step over each
other's handlers. It is unpredictable in which thread the signal handler
runs, so the handler may race with the scoped_ctrl_c destructor.
Fix this by introducing the functions signal_lock and signal_unlock.
signal_lock blocks the SIGINT signal and then takes a mutex (so that the
signal handler can't be called while the mutex is held). signal_unlock
drops the mutex and restores the signal mask.
We protect all the global variables with signal_lock and signal_unlock.
Note that on Windows, the SIGINT handler is being run in a separate
thread (and there is no way how to block it), so we can use a simple
mutex to synchronize the signal handler with the other threads.
In class cancel_eh, the operator () may be called concurrently by the
timer code and the Ctrl-C code, but the operator () accesses class'
members without any locking. Fix this race condition by using the
functions signal_lock() and signal_unlock().
There is this possible call trace:
SIGINT signal
on_sigint
a->m_cancel_eh()
cancel_eh::operator()
m_obj.inc_cancel
reslimit::inc_cancel
lock_guard lock(*g_rlimit_mux);
Here we take a mutex from a signal - this is subject to deadlock (if the
signal interrupted another piece of code where the mutex is already
held).
To fix this race, we remove g_rlimit_mux and replace it with
signal_lock() and signal_unlock(). signal_lock and signal_unlock block
the signal before grabbing the mutex, so the signal can't interrupt a
piece of code where the mutex is held and the deadlock won't happen.
Signed-off-by: Mikulas Patocka <mikulas@twibright.com>
This commit is contained in:
parent
0b7a81b7c9
commit
bcd615f3c5
4 changed files with 149 additions and 41 deletions
|
|
@ -12,46 +12,133 @@ Abstract:
|
|||
Author:
|
||||
|
||||
Leonardo de Moura (leonardo) 2011-04-27.
|
||||
Mikulas Patocka 2025-04-05. (rewritten to be thread safe)
|
||||
|
||||
Revision History:
|
||||
|
||||
--*/
|
||||
#include<signal.h>
|
||||
#include <signal.h>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include "util/scoped_ctrl_c.h"
|
||||
|
||||
static scoped_ctrl_c * g_obj = nullptr;
|
||||
#ifdef _WINDOWS
|
||||
#define USE_SIGNAL
|
||||
#endif
|
||||
|
||||
static void on_ctrl_c(int) {
|
||||
if (g_obj->m_first) {
|
||||
g_obj->m_cancel_eh(CTRL_C_EH_CALLER);
|
||||
if (g_obj->m_once) {
|
||||
g_obj->m_first = false;
|
||||
signal(SIGINT, on_ctrl_c); // re-install the handler
|
||||
}
|
||||
static std::recursive_mutex context_lock;
|
||||
static std::vector<scoped_ctrl_c *> active_contexts;
|
||||
#ifdef USE_SIGNAL
|
||||
static void (*old_handler)(int);
|
||||
#else
|
||||
static sigset_t context_old_set;
|
||||
static struct sigaction old_sigaction;
|
||||
static unsigned signal_lock_depth = 0;
|
||||
#endif
|
||||
static bool signal_handled = false;
|
||||
|
||||
void signal_lock(void) {
|
||||
#ifdef USE_SIGNAL
|
||||
context_lock.lock();
|
||||
#else
|
||||
sigset_t set, old_set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, SIGINT);
|
||||
if (sigprocmask(SIG_BLOCK, &set, &old_set))
|
||||
abort();
|
||||
context_lock.lock();
|
||||
signal_lock_depth++;
|
||||
if (signal_lock_depth == 1)
|
||||
context_old_set = old_set;
|
||||
#endif
|
||||
}
|
||||
|
||||
void signal_unlock(void) {
|
||||
#ifdef USE_SIGNAL
|
||||
context_lock.unlock();
|
||||
#else
|
||||
bool restore;
|
||||
sigset_t old_set = context_old_set;
|
||||
signal_lock_depth--;
|
||||
restore = !signal_lock_depth;
|
||||
context_lock.unlock();
|
||||
if (restore) {
|
||||
if (sigprocmask(SIG_SETMASK, &old_set, NULL))
|
||||
abort();
|
||||
}
|
||||
else {
|
||||
signal(SIGINT, g_obj->m_old_handler);
|
||||
raise(SIGINT);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void test_and_unhandle(void) {
|
||||
if (!signal_handled)
|
||||
return;
|
||||
for (auto a : active_contexts) {
|
||||
if (a->m_first)
|
||||
return;
|
||||
}
|
||||
#ifdef USE_SIGNAL
|
||||
signal(SIGINT, old_handler);
|
||||
#else
|
||||
if (sigaction(SIGINT, &old_sigaction, NULL))
|
||||
abort();
|
||||
#endif
|
||||
signal_handled = false;
|
||||
}
|
||||
|
||||
static void on_sigint(int) {
|
||||
signal_lock();
|
||||
#ifdef USE_SIGNAL
|
||||
if (signal_handled)
|
||||
signal(SIGINT, on_sigint);
|
||||
#endif
|
||||
for (auto a : active_contexts) {
|
||||
if (a->m_first)
|
||||
a->m_cancel_eh(CTRL_C_EH_CALLER);
|
||||
if (a->m_once)
|
||||
a->m_first = false;
|
||||
}
|
||||
test_and_unhandle();
|
||||
signal_unlock();
|
||||
}
|
||||
|
||||
scoped_ctrl_c::scoped_ctrl_c(event_handler & eh, bool once, bool enabled):
|
||||
m_cancel_eh(eh),
|
||||
m_cancel_eh(eh),
|
||||
m_first(true),
|
||||
m_once(once),
|
||||
m_enabled(enabled),
|
||||
m_old_scoped_ctrl_c(g_obj) {
|
||||
m_enabled(enabled) {
|
||||
if (m_enabled) {
|
||||
g_obj = this;
|
||||
m_old_handler = signal(SIGINT, on_ctrl_c);
|
||||
signal_lock();
|
||||
active_contexts.push_back(this);
|
||||
if (!signal_handled) {
|
||||
#ifdef USE_SIGNAL
|
||||
old_handler = signal(SIGINT, on_sigint);
|
||||
#else
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(struct sigaction));
|
||||
sa.sa_handler = on_sigint;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART;
|
||||
if (sigaction(SIGINT, &sa, &old_sigaction))
|
||||
abort();
|
||||
#endif
|
||||
signal_handled = true;
|
||||
}
|
||||
signal_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
scoped_ctrl_c::~scoped_ctrl_c() {
|
||||
scoped_ctrl_c::~scoped_ctrl_c() {
|
||||
if (m_enabled) {
|
||||
g_obj = m_old_scoped_ctrl_c;
|
||||
if (m_old_handler != SIG_ERR) {
|
||||
signal(SIGINT, m_old_handler);
|
||||
signal_lock();
|
||||
for (auto it = active_contexts.begin(); it != active_contexts.end(); it++) {
|
||||
if (*it == this) {
|
||||
active_contexts.erase(it);
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
abort();
|
||||
found:
|
||||
test_and_unhandle();
|
||||
signal_unlock();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue