From: Wolfgang Bangerth Date: Wed, 27 May 2020 20:32:53 +0000 (-0600) Subject: Ensure propagation of exceptions from tasks. X-Git-Tag: v9.3.0-rc1~1531^2~1 X-Git-Url: https://gitweb.dealii.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eea0f5a09493dc0f9fb097f94a07eeeaea78b06b;p=dealii.git Ensure propagation of exceptions from tasks. --- diff --git a/include/deal.II/base/thread_management.h b/include/deal.II/base/thread_management.h index 73bc007643..bb7d2a59ff 100644 --- a/include/deal.II/base/thread_management.h +++ b/include/deal.II/base/thread_management.h @@ -1004,6 +1004,26 @@ namespace Threads * may block until the task has completed running, all successive attempts * to join will return immediately). * + * If the operation that was executed on the task with which this + * object was initialized throws an exception instead of returning + * regularly, then calling the current join() function will first + * wait for that task to finish, and then in turn throw the + * exception that the task operation had thrown originally. This + * allows for the propagation of exceptions from tasks executed on + * a separate thread to the calling thread. + * + * (This behavior differs from that of + * [`std::future`](https://en.cppreference.com/w/cpp/thread/future), + * where the `std::future::wait()` function only waits for + * completion of the operation, whereas the exception is + * propagated only once one calls `std::future::get()`. However, + * this is awkward when putting `void` functions onto separate + * tasks because these do not actually return anything; + * consequently, it is more natural to call `std::task::wait()` + * for such tasks than the `std::task::get()` function since the + * latter does not, actually, return anything that could be + * gotten.) + * * @pre You can't call this function if you have used the default * constructor of this class and have not assigned a task object to it. In * other words, the function joinable() must return true. @@ -1075,6 +1095,12 @@ namespace Threads * to be moved, and in order to be moved, the current function needs to * return a writable (non-@p const) reference. * + * This function internally calls the join() member function. As a + * consequence, and as explained there, if the packaged task + * throws an exception that is then re-thrown by the join() + * function and consequently also the current function if you have + * not previously called join(). + * * @pre You can't call this function if you have used the default * constructor of this class and have not assigned a task object to it. In * other words, the function joinable() must return true. diff --git a/tests/base/task_01_exception.cc b/tests/base/task_01_exception.cc new file mode 100644 index 0000000000..3659075951 --- /dev/null +++ b/tests/base/task_01_exception.cc @@ -0,0 +1,52 @@ +// --------------------------------------------------------------------- +// +// Copyright (C) 2009 - 2018 by the deal.II authors +// +// This file is part of the deal.II library. +// +// The deal.II library is free software; you can use it, redistribute +// it, and/or modify it under the terms of the GNU Lesser General +// Public License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// The full text of the license can be found in the file LICENSE.md at +// the top level directory of deal.II. +// +// --------------------------------------------------------------------- + + +// Like the _01 test, but check that if the function called on the +// task throws an exception, that Task::wait() continues to work but +// Task::return_value() throws the exception. + +#include + +#include "../tests.h" + + +int +test() +{ + std::this_thread::sleep_for(std::chrono::seconds(3)); + + throw 42; +} + + +int +main() +{ + initlog(); + + Threads::Task t = Threads::new_task(test); + + // Here, an exception should be triggered: + try + { + t.join(); + } + catch (int i) + { + Assert(i == 42, ExcInternalError()); + deallog << "OK" << std::endl; + } +} diff --git a/tests/base/task_01_exception.with_threads=on.output b/tests/base/task_01_exception.with_threads=on.output new file mode 100644 index 0000000000..0fd8fc12f0 --- /dev/null +++ b/tests/base/task_01_exception.with_threads=on.output @@ -0,0 +1,2 @@ + +DEAL::OK diff --git a/tests/base/task_01_exception_02.cc b/tests/base/task_01_exception_02.cc new file mode 100644 index 0000000000..895c9d1e3d --- /dev/null +++ b/tests/base/task_01_exception_02.cc @@ -0,0 +1,52 @@ +// --------------------------------------------------------------------- +// +// Copyright (C) 2009 - 2018 by the deal.II authors +// +// This file is part of the deal.II library. +// +// The deal.II library is free software; you can use it, redistribute +// it, and/or modify it under the terms of the GNU Lesser General +// Public License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// The full text of the license can be found in the file LICENSE.md at +// the top level directory of deal.II. +// +// --------------------------------------------------------------------- + + +// Like the _01 test, but check that if the function called on the +// task throws an exception, that Task::wait() continues to work but +// Task::return_value() throws the exception. + +#include + +#include "../tests.h" + + +int +test() +{ + std::this_thread::sleep_for(std::chrono::seconds(3)); + + throw 42; +} + + +int +main() +{ + initlog(); + + Threads::Task t = Threads::new_task(test); + + // Here, an exception should be triggered: + try + { + t.return_value(); + } + catch (int i) + { + Assert(i == 42, ExcInternalError()); + deallog << "OK" << std::endl; + } +} diff --git a/tests/base/task_01_exception_02.with_threads=on.output b/tests/base/task_01_exception_02.with_threads=on.output new file mode 100644 index 0000000000..0fd8fc12f0 --- /dev/null +++ b/tests/base/task_01_exception_02.with_threads=on.output @@ -0,0 +1,2 @@ + +DEAL::OK