3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-07-18 18:36:41 +00:00

Make Ctrl-C handling thread-safe (#7603)

Signed-off-by: Mikulas Patocka <mikulas@twibright.com>
This commit is contained in:
Mikulas Patocka 2025-04-03 19:12:57 +02:00
parent 8d81a2dcaf
commit 95c55ae76b
3 changed files with 74 additions and 26 deletions

View file

@ -18,6 +18,7 @@ Revision History:
--*/ --*/
#pragma once #pragma once
#include <atomic>
#include "util/event_handler.h" #include "util/event_handler.h"
/** /**
@ -25,16 +26,15 @@ Revision History:
*/ */
template<typename T> template<typename T>
class cancel_eh : public event_handler { class cancel_eh : public event_handler {
bool m_canceled = false; std::atomic<bool> m_canceled = false;
bool m_auto_cancel = false; bool m_auto_cancel = false;
T & m_obj; T & m_obj;
public: public:
cancel_eh(T & o): m_obj(o) {} cancel_eh(T & o): m_obj(o) {}
~cancel_eh() override { if (m_canceled) m_obj.dec_cancel(); if (m_auto_cancel) m_obj.auto_cancel(); } ~cancel_eh() override { if (m_canceled) m_obj.dec_cancel(); if (m_auto_cancel) m_obj.auto_cancel(); }
void operator()(event_handler_caller_t caller_id) override { void operator()(event_handler_caller_t caller_id) override {
if (!m_canceled) { if (!m_canceled.exchange(true)) {
m_caller_id = caller_id; m_caller_id = caller_id;
m_canceled = true;
m_obj.inc_cancel(); m_obj.inc_cancel();
} }
} }

View file

@ -12,46 +12,96 @@ Abstract:
Author: Author:
Leonardo de Moura (leonardo) 2011-04-27. Leonardo de Moura (leonardo) 2011-04-27.
Mikulas Patocka 2025-04-03. (rewritten to be thread safe)
Revision History: Revision History:
--*/ --*/
#include<signal.h> #include <signal.h>
#include <cstring>
#include "util/scoped_ctrl_c.h" #include "util/scoped_ctrl_c.h"
static scoped_ctrl_c * g_obj = nullptr; static std::mutex context_lock;
static std::vector<scoped_ctrl_c *> active_contexts;
static sigset_t context_old_set;
static struct sigaction old_sigaction;
static bool signal_handled = false;
static void on_ctrl_c(int) { static void signal_lock(void) {
if (g_obj->m_first) { sigset_t set, old_set;
g_obj->m_cancel_eh(CTRL_C_EH_CALLER); sigemptyset(&set);
if (g_obj->m_once) { sigaddset(&set, SIGINT);
g_obj->m_first = false; if (sigprocmask(SIG_BLOCK, &set, &old_set))
signal(SIGINT, on_ctrl_c); // re-install the handler abort();
} context_lock.lock();
context_old_set = old_set;
}
static void signal_unlock(void) {
sigset_t old_set = context_old_set;
context_lock.unlock();
if (sigprocmask(SIG_SETMASK, &old_set, NULL))
abort();
}
static void test_and_unhandle(void) {
if (!signal_handled)
return;
for (auto a : active_contexts) {
if (a->m_first)
return;
} }
else { if (sigaction(SIGINT, &old_sigaction, NULL))
signal(SIGINT, g_obj->m_old_handler); abort();
raise(SIGINT); signal_handled = false;
}
static void on_sigint(int) {
signal_lock();
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): 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_first(true),
m_once(once), m_once(once),
m_enabled(enabled), m_enabled(enabled) {
m_old_scoped_ctrl_c(g_obj) {
if (m_enabled) { if (m_enabled) {
g_obj = this; signal_lock();
m_old_handler = signal(SIGINT, on_ctrl_c); active_contexts.push_back(this);
if (!signal_handled) {
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();
signal_handled = true;
}
signal_unlock();
} }
} }
scoped_ctrl_c::~scoped_ctrl_c() { scoped_ctrl_c::~scoped_ctrl_c() {
if (m_enabled) { if (m_enabled) {
g_obj = m_old_scoped_ctrl_c; signal_lock();
if (m_old_handler != SIG_ERR) { for (auto it = active_contexts.begin(); it != active_contexts.end(); it++) {
signal(SIGINT, m_old_handler); if (*it == this) {
active_contexts.erase(it);
goto found;
}
} }
abort();
found:
test_and_unhandle();
signal_unlock();
} }
} }

View file

@ -26,8 +26,6 @@ struct scoped_ctrl_c {
bool m_first; bool m_first;
bool m_once; bool m_once;
bool m_enabled; bool m_enabled;
void (STD_CALL *m_old_handler)(int);
scoped_ctrl_c * m_old_scoped_ctrl_c;
public: public:
// If once == true, then the cancel_eh is invoked only at the first Ctrl-C. // If once == true, then the cancel_eh is invoked only at the first Ctrl-C.
// The next time, the old signal handler will take over. // The next time, the old signal handler will take over.