Commit d0edf4c6 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook GitHub Bot

fix observed double-deletes of futures interrupt handlers

Summary:
The state machine for the interrupt has a has-handler state which holds the handler pointer and a terminal state which holds nothing. When a handler has been stored and an exception is raised, the state machine exchanges the handler and transitions the state from has-handler to terminal, and then invokes the handler and decrements its refcount, possibly deleting it. A concurrent continuation of a future can load the interrupt and, if it is a handler, increment its refcount.

There is a possible race: the handler is loaded in both threads, its refcount decremented in the first thread and incremeneted in the second thread, and then the first thread observes an historical refcount of zero and deletes the handler. Fix this race by holding onto the handler until the core is destroyed.

Reviewed By: davidtgoldblatt

Differential Revision: D30017199

fbshipit-source-id: b6c474c630719ef8fd1cb88fa982045a523590dd
parent 9781d415
...@@ -337,15 +337,14 @@ void CoreBase::raise(exception_wrapper e) { ...@@ -337,15 +337,14 @@ void CoreBase::raise(exception_wrapper e) {
FOLLY_FALLTHROUGH; FOLLY_FALLTHROUGH;
} }
case InterruptHasHandler: { // invoke the stored handler case InterruptHasHandler: { // invoke the stored handler
auto pointer = interrupt & ~InterruptMask;
auto exchanged = interrupt_.compare_exchange_strong( auto exchanged = interrupt_.compare_exchange_strong(
interrupt, InterruptTerminal, std::memory_order_relaxed); interrupt, pointer | InterruptTerminal, std::memory_order_relaxed);
if (!exchanged) { // ignore all calls after the first if (!exchanged) { // ignore all calls after the first
return; return;
} }
auto handler = auto handler = reinterpret_cast<InterruptHandler*>(pointer);
reinterpret_cast<InterruptHandler*>(interrupt & ~InterruptHasHandler);
handler->handle(e); handler->handle(e);
handler->release();
return; return;
} }
case InterruptHasObject: // ignore all calls after the first case InterruptHasObject: // ignore all calls after the first
...@@ -360,10 +359,21 @@ void CoreBase::initCopyInterruptHandlerFrom(const CoreBase& other) { ...@@ -360,10 +359,21 @@ void CoreBase::initCopyInterruptHandlerFrom(const CoreBase& other) {
auto interrupt = other.interrupt_.load(std::memory_order_acquire); auto interrupt = other.interrupt_.load(std::memory_order_acquire);
switch (interrupt & InterruptMask) { switch (interrupt & InterruptMask) {
case InterruptHasHandler: { // copy the handler case InterruptHasHandler: { // copy the handler
auto handler = auto pointer = interrupt & ~InterruptMask;
reinterpret_cast<InterruptHandler*>(interrupt & ~InterruptHasHandler); auto handler = reinterpret_cast<InterruptHandler*>(pointer);
handler->acquire(); handler->acquire();
interrupt_.store(interrupt, std::memory_order_release); interrupt_.store(
pointer | InterruptHasHandler, std::memory_order_release);
break;
}
case InterruptTerminal: { // copy the handler, if any
auto pointer = interrupt & ~InterruptMask;
auto handler = reinterpret_cast<InterruptHandler*>(pointer);
if (handler) {
handler->acquire();
interrupt_.store(
pointer | InterruptHasHandler, std::memory_order_release);
}
break; break;
} }
} }
...@@ -401,19 +411,25 @@ CoreBase::CoreBase(State state, unsigned char attached) ...@@ -401,19 +411,25 @@ CoreBase::CoreBase(State state, unsigned char attached)
CoreBase::~CoreBase() { CoreBase::~CoreBase() {
auto interrupt = interrupt_.load(std::memory_order_acquire); auto interrupt = interrupt_.load(std::memory_order_acquire);
auto pointer = interrupt & ~InterruptMask;
switch (interrupt & InterruptMask) { switch (interrupt & InterruptMask) {
case InterruptHasHandler: { case InterruptHasHandler: {
auto handler = auto handler = reinterpret_cast<InterruptHandler*>(pointer);
reinterpret_cast<InterruptHandler*>(interrupt & ~InterruptHasHandler);
handler->release(); handler->release();
break; break;
} }
case InterruptHasObject: { case InterruptHasObject: {
auto object = auto object = reinterpret_cast<exception_wrapper*>(pointer);
reinterpret_cast<exception_wrapper*>(interrupt & ~InterruptHasObject);
delete object; delete object;
break; break;
} }
case InterruptTerminal: {
auto handler = reinterpret_cast<InterruptHandler*>(pointer);
if (handler) {
handler->release();
}
break;
}
} }
} }
......
...@@ -464,8 +464,8 @@ class CoreBase { ...@@ -464,8 +464,8 @@ class CoreBase {
if (!exchanged) { if (!exchanged) {
terminate_with<std::logic_error>("set-interrupt-handler race"); terminate_with<std::logic_error>("set-interrupt-handler race");
} }
auto object = reinterpret_cast<exception_wrapper*>( auto pointer = interrupt & ~InterruptMask;
interrupt & ~InterruptHasObject); auto object = reinterpret_cast<exception_wrapper*>(pointer);
fn(as_const(*object)); fn(as_const(*object));
delete object; delete object;
return; return;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment