mirror of
https://github.com/Z3Prover/z3
synced 2025-08-04 18:30:24 +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
|
@ -18,28 +18,44 @@ Revision History:
|
|||
--*/
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include "util/event_handler.h"
|
||||
#include "util/scoped_ctrl_c.h"
|
||||
|
||||
/**
|
||||
\brief Generic event handler for invoking cancel method.
|
||||
*/
|
||||
template<typename T>
|
||||
class cancel_eh : public event_handler {
|
||||
bool m_canceled = false;
|
||||
std::atomic<bool> m_canceled = false;
|
||||
bool m_auto_cancel = false;
|
||||
T & m_obj;
|
||||
public:
|
||||
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(); }
|
||||
void operator()(event_handler_caller_t caller_id) override {
|
||||
signal_lock();
|
||||
if (!m_canceled) {
|
||||
m_caller_id = caller_id;
|
||||
m_canceled = true;
|
||||
m_obj.inc_cancel();
|
||||
}
|
||||
signal_unlock();
|
||||
}
|
||||
bool canceled() {
|
||||
bool ret;
|
||||
if (!m_canceled)
|
||||
return false;
|
||||
signal_lock();
|
||||
ret = m_canceled;
|
||||
signal_unlock();
|
||||
return ret;
|
||||
}
|
||||
void reset() {
|
||||
signal_lock();
|
||||
m_canceled = false;
|
||||
signal_unlock();
|
||||
}
|
||||
bool canceled() const { return m_canceled; }
|
||||
void reset() { m_canceled = false; }
|
||||
T& t() { return m_obj; }
|
||||
void set_auto_cancel() { m_auto_cancel = true; }
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue