Commit 0ee7dfe1 authored by Philip Pronin's avatar Philip Pronin Committed by Facebook GitHub Bot

protect executor callback destruction

Summary:
Callback destruction can involve user logic.  In practice, we
found quite a bunch of patterns like:

```
executor->add([x = someGuardObjectThatTriggersWorkInDtor] {
  ...
});
```

So ensure that dtor is

* Executed under `RequestContextScopeGuard`,
* Protected from leaking unhandled exceptions.

Reviewed By: ot, luciang

Differential Revision: D26744394

fbshipit-source-id: 5fcca0287a98de919c3649a9613e6d54cc1a1ba3
parent 204ae3a6
...@@ -74,6 +74,23 @@ ThreadPoolExecutor::Task::Task( ...@@ -74,6 +74,23 @@ ThreadPoolExecutor::Task::Task(
enqueueTime_ = std::chrono::steady_clock::now(); enqueueTime_ = std::chrono::steady_clock::now();
} }
namespace {
template <class F>
void nothrow(const char* name, F&& f) {
try {
f();
} catch (const std::exception& e) {
LOG(ERROR) << "ThreadPoolExecutor: " << name << " threw unhandled "
<< typeid(e).name() << " exception: " << e.what();
} catch (...) {
LOG(ERROR) << "ThreadPoolExecutor: " << name
<< " threw unhandled non-exception object";
}
}
} // namespace
void ThreadPoolExecutor::runTask(const ThreadPtr& thread, Task&& task) { void ThreadPoolExecutor::runTask(const ThreadPtr& thread, Task&& task) {
thread->idle.store(false, std::memory_order_relaxed); thread->idle.store(false, std::memory_order_relaxed);
auto startTime = std::chrono::steady_clock::now(); auto startTime = std::chrono::steady_clock::now();
...@@ -84,30 +101,29 @@ void ThreadPoolExecutor::runTask(const ThreadPtr& thread, Task&& task) { ...@@ -84,30 +101,29 @@ void ThreadPoolExecutor::runTask(const ThreadPtr& thread, Task&& task) {
} }
stats.waitTime = startTime - task.enqueueTime_; stats.waitTime = startTime - task.enqueueTime_;
if (task.expiration_ > std::chrono::milliseconds(0) && {
stats.waitTime >= task.expiration_) {
stats.expired = true;
if (task.expireCallback_ != nullptr) {
task.expireCallback_();
}
} else {
folly::RequestContextScopeGuard rctx(task.context_); folly::RequestContextScopeGuard rctx(task.context_);
try { if (task.expiration_ > std::chrono::milliseconds(0) &&
task.func_(); stats.waitTime >= task.expiration_) {
} catch (const std::exception& e) { stats.expired = true;
LOG(ERROR) << "ThreadPoolExecutor: func threw unhandled " if (task.expireCallback_ != nullptr) {
<< typeid(e).name() << " exception: " << e.what(); nothrow("expireCallback", [&] { task.expireCallback_(); });
} catch (...) { }
LOG(ERROR) << "ThreadPoolExecutor: func threw unhandled non-exception " } else {
"object"; nothrow("func", [&] { task.func_(); });
} }
// Callback destruction might involve user logic, protect it as well.
nothrow("~func", [&] { task.func_ = nullptr; });
nothrow("~expireCallback", [&] { task.expireCallback_ = nullptr; });
}
if (!stats.expired) {
stats.runTime = std::chrono::steady_clock::now() - startTime; stats.runTime = std::chrono::steady_clock::now() - startTime;
} }
// Times in this USDT use granularity of std::chrono::steady_clock::duration, // Times in this USDT use granularity of std::chrono::steady_clock::duration,
// which is platform dependent. On Facebook servers, the granularity is // which is platform dependent. On Facebook servers, the granularity is
// nanoseconds. We explicitly do not perform any unit conversions to avoid // nanoseconds. We explicitly do not perform any unit conversions to avoid
// unneccessary costs and leave it to consumers of this data to know what // unnecessary costs and leave it to consumers of this data to know what
// effective clock resolution is. // effective clock resolution is.
FOLLY_SDT( FOLLY_SDT(
folly, folly,
......
...@@ -590,19 +590,27 @@ class TestData : public folly::RequestData { ...@@ -590,19 +590,27 @@ class TestData : public folly::RequestData {
}; };
TEST(ThreadPoolExecutorTest, RequestContext) { TEST(ThreadPoolExecutorTest, RequestContext) {
CPUThreadPoolExecutor executor(1);
RequestContextScopeGuard rctx; // create new request context for this scope RequestContextScopeGuard rctx; // create new request context for this scope
EXPECT_EQ(nullptr, RequestContext::get()->getContextData("test")); EXPECT_EQ(nullptr, RequestContext::get()->getContextData("test"));
RequestContext::get()->setContextData("test", std::make_unique<TestData>(42)); RequestContext::get()->setContextData("test", std::make_unique<TestData>(42));
auto data = RequestContext::get()->getContextData("test"); auto data = RequestContext::get()->getContextData("test");
EXPECT_EQ(42, dynamic_cast<TestData*>(data)->data_); EXPECT_EQ(42, dynamic_cast<TestData*>(data)->data_);
executor.add([] { struct VerifyRequestContext {
auto data2 = RequestContext::get()->getContextData("test"); ~VerifyRequestContext() {
ASSERT_TRUE(data2 != nullptr); auto data2 = RequestContext::get()->getContextData("test");
EXPECT_EQ(42, dynamic_cast<TestData*>(data2)->data_); EXPECT_TRUE(data2 != nullptr);
}); if (data2 != nullptr) {
EXPECT_EQ(42, dynamic_cast<TestData*>(data2)->data_);
}
}
};
{
CPUThreadPoolExecutor executor(1);
executor.add([] { VerifyRequestContext(); });
executor.add([x = VerifyRequestContext()] {});
}
} }
std::atomic<int> g_sequence{}; std::atomic<int> g_sequence{};
......
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