libutil: fix segfault in makeInterruptible callback

cancelling the promise returned by makeInterruptible could free the
fulfiller before the interrupt callback handle, and no order of the
attachments made a difference. we must resort to putting fulfillers
into shared_ptrs so we can capture them in interrupt callbacks now.
(alternatively we could add another kind of interrupt callback, but
the complexity of doing that outweighs the cost of one shared_ptr.)

fixes #895

Change-Id: I008b160482fd4d81a29d7e9e452dcda858b090b9
This commit is contained in:
eldritch horrors
2025-07-01 23:12:49 +02:00
parent ed3c202c20
commit bfabaa688f
+7 -4
View File
@@ -124,11 +124,14 @@ template<typename T>
kj::Promise<Result<T>> makeInterruptible(kj::Promise<Result<T>> p)
{
auto onInterrupt = kj::newPromiseAndCrossThreadFulfiller<Result<T>>();
auto interruptCallback = createInterruptCallback([fulfiller{onInterrupt.fulfiller.get()}] {
fulfiller->fulfill(result::failure(std::make_exception_ptr(makeInterrupted())));
// the fulfiller must be a shared_ptr<Own<...>> since functions must
// be copyable, and we don't have move_only_function on all stdlibs.
auto fulfiller =
std::make_shared<decltype(onInterrupt.fulfiller)>(std::move(onInterrupt.fulfiller));
auto interruptCallback = createInterruptCallback([fulfiller] {
(*fulfiller)->fulfill(result::failure(std::make_exception_ptr(makeInterrupted())));
});
return p.attach(std::move(onInterrupt.fulfiller), std::move(interruptCallback))
.exclusiveJoin(std::move(onInterrupt.promise));
return p.attach(std::move(interruptCallback)).exclusiveJoin(std::move(onInterrupt.promise));
}
void triggerInterrupt();