ThreadPool: Improve exception handling

In particular, process() won't return as long as there are active
items. This prevents work item lambdas from referring to stack frames
that no longer exist.
This commit is contained in:
Eelco Dolstra 2017-09-08 14:40:27 +02:00
parent a2740c9ca2
commit 8f6b347abd
No known key found for this signature in database
GPG key ID: 8170B4726D7198DE
2 changed files with 53 additions and 24 deletions

View file

@ -46,11 +46,17 @@ void ThreadPool::enqueue(const work_t & t)
void ThreadPool::process() void ThreadPool::process()
{ {
/* Loop until there are no active work items *and* there either
are no queued items or there is an exception. The
post-condition is that no new items will become active. */
while (true) { while (true) {
auto state(state_.lock()); auto state(state_.lock());
if (state->exception) if (!state->active) {
std::rethrow_exception(state->exception); if (state->exception)
if (state->left.empty() && !state->pending) break; std::rethrow_exception(state->exception);
if (state->left.empty())
break;
}
state.wait(done); state.wait(done);
} }
} }
@ -58,41 +64,64 @@ void ThreadPool::process()
void ThreadPool::workerEntry() void ThreadPool::workerEntry()
{ {
bool didWork = false; bool didWork = false;
std::exception_ptr exc;
while (true) { while (true) {
work_t w; work_t w;
{ {
auto state(state_.lock()); auto state(state_.lock());
if (didWork) {
assert(state->active);
state->active--;
if (exc) {
if (!state->exception) {
state->exception = exc;
// Tell the other workers to quit.
state->quit = true;
work.notify_all();
} else {
/* Print the exception, since we can't
propagate it. */
try {
std::rethrow_exception(exc);
} catch (std::exception & e) {
if (!dynamic_cast<Interrupted*>(&e) &&
!dynamic_cast<ThreadPoolShutDown*>(&e))
ignoreException();
} catch (...) {
}
}
}
}
/* Wait until a work item is available or another thread
had an exception or we're asked to quit. */
while (true) { while (true) {
if (state->quit || state->exception) return; if (state->quit) {
if (didWork) { if (!state->active)
assert(state->pending); done.notify_one();
state->pending--; return;
didWork = false;
} }
if (!state->left.empty()) break; if (!state->left.empty()) break;
if (!state->pending) if (!state->active) {
done.notify_all(); done.notify_one();
return;
}
state.wait(work); state.wait(work);
} }
w = state->left.front();
w = std::move(state->left.front());
state->left.pop(); state->left.pop();
state->pending++; state->active++;
} }
try { try {
w(); w();
} catch (std::exception & e) { } catch (...) {
auto state(state_.lock()); exc = std::current_exception();
if (state->exception) {
if (!dynamic_cast<Interrupted*>(&e) &&
!dynamic_cast<ThreadPoolShutDown*>(&e))
printError(format("error: %s") % e.what());
} else {
state->exception = std::current_exception();
work.notify_all();
done.notify_all();
}
} }
didWork = true; didWork = true;

View file

@ -44,7 +44,7 @@ private:
struct State struct State
{ {
std::queue<work_t> left; std::queue<work_t> left;
size_t pending = 0; size_t active = 0;
std::exception_ptr exception; std::exception_ptr exception;
std::vector<std::thread> workers; std::vector<std::thread> workers;
bool quit = false; bool quit = false;