-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes swallowed exceptions in computation thread
Exceptions are now transferred from the computation thread to the main one using std::future mechanisms. Only the first encountered exception is thrown in the main thread. Signed-off-by: Sylvain Leclerc <[email protected]>
- Loading branch information
Showing
10 changed files
with
227 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
|
||
|
||
add_library(concurrency) | ||
add_library(Antares::concurrency ALIAS concurrency) | ||
|
||
target_sources(concurrency PRIVATE concurrency.cpp) | ||
target_include_directories(concurrency PUBLIC include) | ||
|
||
target_link_libraries(concurrency yuni-static-core) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// | ||
// Created by leclercsyl on 05/10/23. | ||
// | ||
#include <memory> | ||
#include "yuni/job/job.h" | ||
#include "antares/concurrency/concurrency.h" | ||
|
||
namespace Antares::Concurrency | ||
{ | ||
|
||
class JobImpl : public Yuni::Job::IJob { | ||
public: | ||
JobImpl(const Task& task) : task_(task) {} | ||
|
||
TaskFuture getFuture() { | ||
return task_.get_future(); | ||
} | ||
|
||
protected: | ||
void onExecute() override { | ||
task_(); | ||
} | ||
|
||
private: | ||
std::packaged_task<void()> task_; | ||
}; | ||
|
||
std::unique_ptr<Yuni::Job::IJob> MakeJob(const Task& task) { | ||
return std::make_unique<JobImpl>(task); | ||
} | ||
|
||
std::unique_ptr<JobImpl> MakePackagedJob(const Task& task) { | ||
return std::make_unique<JobImpl>(task); | ||
} | ||
|
||
std::future<void> AddTask(Yuni::Job::QueueService& threadPool, const Task& task) { | ||
auto job = MakePackagedJob(task); | ||
auto future = job->getFuture(); | ||
threadPool.add(Yuni::Job::IJob::Ptr(job.release())); | ||
return future; | ||
} | ||
|
||
void FutureSet::add(TaskFuture&& f) { | ||
futures_.push_back(std::move(f)); | ||
} | ||
|
||
void FutureSet::join() { | ||
for (auto& f: futures_) { | ||
f.get(); | ||
} | ||
} | ||
|
||
} |
49 changes: 49 additions & 0 deletions
49
src/libs/antares/concurrency/include/antares/concurrency/concurrency.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// | ||
// Created by leclercsyl on 05/10/23. | ||
// | ||
|
||
#ifndef ANTARES_CONCURRENCY_H | ||
#define ANTARES_CONCURRENCY_H | ||
|
||
#include <future> | ||
#include "yuni/job/queue/service.h" | ||
|
||
namespace Antares::Concurrency | ||
{ | ||
|
||
typedef std::function<void()> Task; | ||
typedef std::future<void> TaskFuture; | ||
|
||
/*! | ||
* \brief Queues the provided function and returns the corresponding std::future. | ||
*/ | ||
TaskFuture AddTask(Yuni::Job::QueueService& threadPool, const Task& task); | ||
|
||
/*! | ||
* \brief Utility class to gather futures to wait for. | ||
*/ | ||
class FutureSet | ||
{ | ||
public: | ||
/*! | ||
* \brief Adds one future to be monitored by this set. | ||
* | ||
* Note: the provided future will be left in "moved from" state. | ||
*/ | ||
void add(TaskFuture&& f); | ||
|
||
/*! | ||
* \brief Waits for completion of all added futures. | ||
* | ||
* If one of the future ends on exception, re-throws the first encountered exception. | ||
*/ | ||
void join(); | ||
|
||
private: | ||
std::vector<TaskFuture> futures_; | ||
}; | ||
|
||
} | ||
|
||
|
||
#endif //ANTARES_CONCURRENCY_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
add_subdirectory(concurrency) | ||
add_subdirectory(study) | ||
|
||
set(src_libs_antares "${CMAKE_SOURCE_DIR}/libs/antares") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
|
||
add_executable(test-concurrency ${SRC_LINK_PROPERTIES}) | ||
|
||
target_sources(test-concurrency PRIVATE test_concurrency.cpp) | ||
|
||
target_link_libraries(test-concurrency | ||
PRIVATE | ||
Boost::unit_test_framework | ||
Antares::concurrency | ||
) | ||
|
||
add_test(NAME concurrency COMMAND test-concurrency) |
76 changes: 76 additions & 0 deletions
76
src/tests/src/libs/antares/concurrency/test_concurrency.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// | ||
// Created by leclercsyl on 06/10/23. | ||
// | ||
#define BOOST_TEST_MODULE test-concurrency tests | ||
#define BOOST_TEST_DYN_LINK | ||
#include <boost/test/unit_test.hpp> | ||
#include <boost/test/data/test_case.hpp> | ||
|
||
#include "antares/concurrency/concurrency.h" | ||
|
||
using namespace Yuni::Job; | ||
using namespace Antares::Concurrency; | ||
|
||
std::unique_ptr<QueueService> createThreadPool(int size) | ||
{ | ||
auto threadPool = std::make_unique<QueueService>(); | ||
threadPool->maximumThreadCount(size); | ||
threadPool->start(); | ||
return threadPool; | ||
} | ||
|
||
BOOST_AUTO_TEST_CASE(test_no_error) | ||
{ | ||
auto threadPool = createThreadPool(1); | ||
int counter = 0; | ||
Task incrementCounter = [&counter]() { | ||
counter++; | ||
}; | ||
TaskFuture future = AddTask(*threadPool, incrementCounter); | ||
future.get(); | ||
BOOST_CHECK(counter == 1); | ||
} | ||
|
||
|
||
template <class Exc> | ||
Task failingTask() { | ||
return []() { | ||
throw Exc(); | ||
}; | ||
} | ||
|
||
class TestException {}; | ||
|
||
BOOST_AUTO_TEST_CASE(test_throw) | ||
{ | ||
auto threadPool = createThreadPool(1); | ||
TaskFuture future = AddTask(*threadPool, failingTask<TestException>()); | ||
BOOST_CHECK_THROW(future.get(), TestException); | ||
} | ||
|
||
BOOST_AUTO_TEST_CASE(test_future_set) | ||
{ | ||
auto threadPool = createThreadPool(4); | ||
int counter = 0; | ||
Task incrementCounter = [&counter]() { | ||
counter++; | ||
}; | ||
FutureSet futures; | ||
for (int i = 0; i < 10; i++) { | ||
futures.add(AddTask(*threadPool, incrementCounter)); | ||
} | ||
futures.join(); | ||
BOOST_CHECK(counter == 10); | ||
} | ||
|
||
class TestException1 {}; | ||
class TestException2 {}; | ||
|
||
BOOST_AUTO_TEST_CASE(test_future_set_rethrows_first) | ||
{ | ||
auto threadPool = createThreadPool(2); | ||
FutureSet futures; | ||
futures.add(AddTask(*threadPool, failingTask<TestException1>())); | ||
futures.add(AddTask(*threadPool, failingTask<TestException2>())); | ||
BOOST_CHECK_THROW(futures.join(), TestException1); | ||
} |