From 785bb5e0cb32a7e3b6e3aa23041d5164d29e70a7 Mon Sep 17 00:00:00 2001 From: Wolfgang Bangerth Date: Wed, 1 Dec 2021 21:31:09 -0700 Subject: [PATCH] Explicitly wait for tasks to be done. --- include/deal.II/base/thread_management.h | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/include/deal.II/base/thread_management.h b/include/deal.II/base/thread_management.h index f034fc7b3d..b56047f9fa 100644 --- a/include/deal.II/base/thread_management.h +++ b/include/deal.II/base/thread_management.h @@ -1203,6 +1203,47 @@ namespace Threads , task_has_finished(false) {} + + /** + * Destructor. Wait for the results to be ready. This ensures that the + * last Task object holding a shared pointer to the current TaskData + * object blocks until the task has actually finished -- in essence, + * this makes sure that one cannot just abandon a task completely + * by letting all Task objects that point to it go out of scope. + */ + ~TaskData() + { + // Explicitly wait for the results to be ready. This class stores + // a std::future object, and we could just let the compiler generate + // the destructor which would then call the destructor of std::future + // which *may* block until the future is ready. As explained in + // https://en.cppreference.com/w/cpp/thread/future/~future + // this is only a *may*, not a *must*. (The standard does not + // appear to say anything about it at all.) As a consequence, + // let's be explicit about waiting. + // + // One of the corner cases we have to worry about is that if a task + // ends by throwing an exception, then wait() will re-throw that + // exception on the thread that calls it, the first time around + // someone calls wait() (or the return_value() function of the + // surrounding class). So if we get to this constructor and an exception + // is thrown by wait(), then that means that the last Task object + // referring to a task is going out of scope with nobody having + // ever checked the return value of the task itself. In that case, + // one could argue that they would also not have cared about whether + // an exception is thrown, and that we should simply ignore the + // exception. This is what we do here. It is also the simplest solution, + // because we don't know what one should do with the exception to begin + // with: destructors aren't allowed to throw exceptions, so we can't + // just rethrow it here if one had been triggered. + try + { + wait(); + } + catch (...) + {} + } + /** * Wait for the std::future object to be ready, i.e., for the * time when the std::promise receives its value. If this has -- 2.39.5