diff options
author | Eelco Dolstra <edolstra@gmail.com> | 2017-09-08 14:40:27 +0200 |
---|---|---|
committer | Eelco Dolstra <edolstra@gmail.com> | 2017-09-08 14:42:09 +0200 |
commit | 8f6b347abd93706e36cf67fc4c53a46c7ad1ad49 (patch) | |
tree | e633fe5a3e5526c50c539480f108564efe22fbb5 /src | |
parent | a2740c9ca23e748c3ab8ea61a135594a94c86aae (diff) |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/libutil/thread-pool.cc | 75 | ||||
-rw-r--r-- | src/libutil/thread-pool.hh | 2 |
2 files changed, 53 insertions, 24 deletions
diff --git a/src/libutil/thread-pool.cc b/src/libutil/thread-pool.cc index 0a3a40724..f43dbe0c3 100644 --- a/src/libutil/thread-pool.cc +++ b/src/libutil/thread-pool.cc @@ -46,11 +46,17 @@ void ThreadPool::enqueue(const work_t & t) 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) { auto state(state_.lock()); - if (state->exception) - std::rethrow_exception(state->exception); - if (state->left.empty() && !state->pending) break; + if (!state->active) { + if (state->exception) + std::rethrow_exception(state->exception); + if (state->left.empty()) + break; + } state.wait(done); } } @@ -58,41 +64,64 @@ void ThreadPool::process() void ThreadPool::workerEntry() { bool didWork = false; + std::exception_ptr exc; while (true) { work_t w; { 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) { - if (state->quit || state->exception) return; - if (didWork) { - assert(state->pending); - state->pending--; - didWork = false; + if (state->quit) { + if (!state->active) + done.notify_one(); + return; } if (!state->left.empty()) break; - if (!state->pending) - done.notify_all(); + if (!state->active) { + done.notify_one(); + return; + } state.wait(work); } - w = state->left.front(); + + w = std::move(state->left.front()); state->left.pop(); - state->pending++; + state->active++; } try { w(); - } catch (std::exception & e) { - auto state(state_.lock()); - 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(); - } + } catch (...) { + exc = std::current_exception(); } didWork = true; diff --git a/src/libutil/thread-pool.hh b/src/libutil/thread-pool.hh index 361a9d33a..835dfb4b8 100644 --- a/src/libutil/thread-pool.hh +++ b/src/libutil/thread-pool.hh @@ -44,7 +44,7 @@ private: struct State { std::queue<work_t> left; - size_t pending = 0; + size_t active = 0; std::exception_ptr exception; std::vector<std::thread> workers; bool quit = false; |