3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-07-29 07:27:57 +00:00
z3/src/util/cancel_eh.h
Mikulas Patocka bcd615f3c5 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>
2025-04-09 18:35:28 +02:00

62 lines
1.3 KiB
C++

/*++
Copyright (c) 2011 Microsoft Corporation
Module Name:
cancel_eh.h
Abstract:
Template for implementing simple event handler that just invokes cancel method.
Author:
Leonardo de Moura (leonardo) 2011-04-27.
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 {
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();
}
T& t() { return m_obj; }
void set_auto_cancel() { m_auto_cancel = true; }
};