libutil: keep InterruptCallbacks state in shared_ptr

destruction of static objects is not sequenced before termination of
detached threads. this means that a detached thread can hold on to a
reference to a static object and access that object after main() has
returned and the static object destructor has run. ReceiveInterrupts
is one such case, but moving the shared state to the heap solves it.

Change-Id: Id597365ce9386000d171a03323169aafc542aa2c
This commit is contained in:
eldritch horrors
2025-01-20 20:00:22 +00:00
parent c82cc16754
commit 7720e6c43e
+25 -9
View File
@@ -5,6 +5,7 @@
#include "lix/libutil/thread-name.hh"
#include <map>
#include <memory>
#include <thread>
namespace nix {
@@ -46,7 +47,7 @@ struct InterruptCallbacks {
std::map<Token, std::function<void()>> callbacks;
};
static Sync<InterruptCallbacks> _interruptCallbacks;
static Sync<std::shared_ptr<Sync<InterruptCallbacks>>> _interruptCallbacks;
static void signalHandlerThread(sigset_t set)
{
@@ -68,12 +69,13 @@ void triggerInterrupt()
{
_isInterrupted = true;
{
auto callbacks = *_interruptCallbacks.lock();
if (callbacks) {
InterruptCallbacks::Token i = 0;
while (true) {
std::function<void()> callback;
{
auto interruptCallbacks(_interruptCallbacks.lock());
auto interruptCallbacks(callbacks->lock());
auto lb = interruptCallbacks->callbacks.lower_bound(i);
if (lb == interruptCallbacks->callbacks.end())
break;
@@ -167,24 +169,38 @@ void restoreSignals()
/* RAII helper to automatically deregister a callback. */
struct InterruptCallbackImpl : InterruptCallback
{
std::shared_ptr<Sync<InterruptCallbacks>> parent;
InterruptCallbacks::Token token;
InterruptCallbackImpl(
std::shared_ptr<Sync<InterruptCallbacks>> parent, InterruptCallbacks::Token token
)
: parent(parent)
, token(token)
{
}
~InterruptCallbackImpl() override
{
auto interruptCallbacks(_interruptCallbacks.lock());
auto interruptCallbacks(parent->lock());
interruptCallbacks->callbacks.erase(token);
}
};
std::unique_ptr<InterruptCallback> createInterruptCallback(std::function<void()> callback)
{
auto interruptCallbacks(_interruptCallbacks.lock());
auto callbacks = *_interruptCallbacks.lock();
if (!callbacks) {
auto lock = _interruptCallbacks.lock();
if (!*lock) {
*lock = std::make_shared<Sync<InterruptCallbacks>>();
}
callbacks = *lock;
}
auto interruptCallbacks(callbacks->lock());
auto token = interruptCallbacks->nextToken++;
interruptCallbacks->callbacks.emplace(token, callback);
auto res = std::make_unique<InterruptCallbackImpl>();
res->token = token;
return std::unique_ptr<InterruptCallback>(res.release());
return std::make_unique<InterruptCallbackImpl>(callbacks, token);
}
};