3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-26 21:16:02 +00:00
z3/src/util/scoped_timer.cpp
mikulas-patocka 6ecc7a2dd4
Fix a race condition in scoped_timer::finalize (#7618)
scoped_timer::finalize is called from fork. However, it may race with
other threads creating or freeing timer threads.

This patch removes the loop in scoped_timer::finalize (because it is not
needed and it may spin) and also removes two unlocked assignments.

The idle thread is added to "available_workers" in
scoped_timer::~scoped_timer destructor.

If we call the "finalize" method as a part of total memory cleanup, all
the scoped_timers' destructors were already executed and all the worker
threads are already on "available_workers" vector. So, we don't need to
loop; the first loop iteration will clean all the threads.

If the "finalize" method is called from single-threaded program's fork(),
then all the scoped timers' destructors are already called and the case
is analogous to the previous case.

If the "finalize" method is called from multi-threaded program's fork(),
then it breaks down - the "num_workers" variable is the total amount of
workers (both sleeping and busy), and we loop until we terminated
"num_workers" threads - that means that if the number of sleeping workers
is less than "num_workers", the function just spins.

Then, there is unlocked assignment to "num_workers = 0" and
"available_workers.clear()" that can race with other threads doing z3
work and corrupt memory. available_workers.clear() is not needed, because
it was already cleared by std::swap(available_workers, cleanup_workers)
(and that was correctly locked).

Signed-off-by: Mikulas Patocka <mikulas@twibright.com>
2025-04-11 08:08:27 +01:00

152 lines
3.2 KiB
C++

/*++
Copyright (c) 2011 Microsoft Corporation
Module Name:
scoped_timer.cpp
Abstract:
<abstract>
Author:
Leonardo de Moura (leonardo) 2011-04-26.
Revision History:
Nuno Lopes (nlopes) 2019-02-04 - use C++11 goodies
--*/
#include "util/scoped_timer.h"
#include "util/mutex.h"
#include "util/util.h"
#include "util/cancel_eh.h"
#include "util/rlimit.h"
#include <atomic>
#include <chrono>
#include <climits>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
#ifndef _WINDOWS
#include <pthread.h>
#endif
enum scoped_timer_work_state { IDLE = 0, WORKING = 1, EXITING = 2 };
struct scoped_timer_state {
std::thread m_thread;
std::timed_mutex m_mutex;
event_handler * eh;
unsigned ms;
std::atomic<scoped_timer_work_state> work;
std::condition_variable_any cv;
};
static std::vector<scoped_timer_state*> available_workers;
static std::mutex workers;
static void thread_func(scoped_timer_state *s) {
workers.lock();
while (true) {
s->cv.wait(workers, [=]{ return s->work != IDLE; });
workers.unlock();
if (s->work == EXITING)
return;
auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(s->ms);
while (!s->m_mutex.try_lock_until(end)) {
if (std::chrono::steady_clock::now() >= end) {
s->eh->operator()(TIMEOUT_EH_CALLER);
goto next;
}
}
s->m_mutex.unlock();
next:
s->work = IDLE;
workers.lock();
}
}
scoped_timer::scoped_timer(unsigned ms, event_handler * eh) {
if (ms == 0 || ms == UINT_MAX)
return;
#ifdef POLLING_TIMER
auto* r = dynamic_cast<cancel_eh<reslimit>*>(eh);
if (r) {
r->t().set_timeout(ms);
r->set_auto_cancel();
return;
}
#endif
workers.lock();
if (available_workers.empty()) {
// start new thead
workers.unlock();
s = new scoped_timer_state;
init_state(ms, eh);
s->m_thread = std::thread(thread_func, s);
}
else {
// re-use existing thread
s = available_workers.back();
available_workers.pop_back();
init_state(ms, eh);
workers.unlock();
s->cv.notify_one();
}
}
scoped_timer::~scoped_timer() {
if (!s)
return;
s->m_mutex.unlock();
while (s->work == WORKING)
std::this_thread::yield();
workers.lock();
available_workers.push_back(s);
workers.unlock();
}
void scoped_timer::initialize() {
#ifndef _WINDOWS
static bool pthread_atfork_set = false;
if (!pthread_atfork_set) {
pthread_atfork(finalize, nullptr, nullptr);
pthread_atfork_set = true;
}
#endif
}
void scoped_timer::finalize() {
workers.lock();
for (auto w : available_workers) {
w->work = EXITING;
w->cv.notify_one();
}
decltype(available_workers) cleanup_workers;
std::swap(available_workers, cleanup_workers);
workers.unlock();
for (auto w : cleanup_workers) {
w->m_thread.join();
delete w;
}
}
void scoped_timer::init_state(unsigned ms, event_handler * eh) {
s->ms = ms;
s->eh = eh;
s->m_mutex.lock();
s->work = WORKING;
}