diff --git a/src/libs/antares/concurrency/include/antares/concurrency/concurrency.h b/src/libs/antares/concurrency/include/antares/concurrency/concurrency.h index 8813f3eeb1..fbca3dc7d0 100644 --- a/src/libs/antares/concurrency/include/antares/concurrency/concurrency.h +++ b/src/libs/antares/concurrency/include/antares/concurrency/concurrency.h @@ -46,6 +46,19 @@ using TaskFuture = std::future; const Task& task, Yuni::Job::Priority priority = Yuni::Job::priorityDefault); +/*! + * \brief Queues the provided function objects and returns the corresponding std::future. + * + * T must define operator (). + * + * This allows to handle exceptions occuring in the underlying task, + * as opposite to Yuni::Job::QueueService::add which swallows them. + */ +template +[[nodiscard]] TaskFuture AddTask(Yuni::Job::QueueService& threadPool, + const std::shared_ptr& task, + Yuni::Job::Priority priority = Yuni::Job::priorityDefault); + /*! * \brief Utility class to gather futures to wait for. */ @@ -82,6 +95,44 @@ class FutureSet std::vector futures_; }; + +namespace Detail { //implementation details + +/*! + * Utility class to wrap a callable object pointer + * into a copyable callable object. + * + * @tparam T the underlying callable type + */ +template +class CopyableCallable +{ +public: + explicit CopyableCallable(const std::shared_ptr& functionObject) : + functionObject_(functionObject) + { + } + + void operator()() + { + (*functionObject_)(); + } + +private: + std::shared_ptr functionObject_; +}; + +} + +template +TaskFuture AddTask(Yuni::Job::QueueService& threadPool, + const std::shared_ptr& task, + Yuni::Job::Priority priority) +{ + Task wrappedTask = Detail::CopyableCallable(task); + return AddTask(threadPool, wrappedTask, priority); +} + } diff --git a/src/solver/simulation/solver.hxx b/src/solver/simulation/solver.hxx index 74d087fa43..1f568015ec 100644 --- a/src/solver/simulation/solver.hxx +++ b/src/solver/simulation/solver.hxx @@ -88,6 +88,10 @@ public: hydroHotStart = (study.parameters.initialReservoirLevels.iniLevels == Data::irlHotStart); } + yearJob(const yearJob&) = delete; + yearJob& operator =(const yearJob&) = delete; + ~yearJob() = default; + private: ISimulation* simulation_; unsigned int y; @@ -233,6 +237,7 @@ public: } // End of onExecute() method }; + template inline ISimulation::ISimulation(Data::Study& study, const ::Settings& settings, @@ -983,7 +988,7 @@ void ISimulation::loopThroughYears(uint firstYear, // have to be rerun (meaning : they must be run once). if(!set_it->yearFailed[y]) // continue; - Concurrency::Task task = yearJob(this, + auto task = std::make_shared>(this, y, set_it->yearFailed, set_it->isFirstPerformedYearOfASet, diff --git a/src/tests/src/libs/antares/concurrency/test_concurrency.cpp b/src/tests/src/libs/antares/concurrency/test_concurrency.cpp index 2e9cffbd1a..e0f9c02914 100644 --- a/src/tests/src/libs/antares/concurrency/test_concurrency.cpp +++ b/src/tests/src/libs/antares/concurrency/test_concurrency.cpp @@ -97,3 +97,27 @@ BOOST_AUTO_TEST_CASE(test_future_set_rethrows_first_submitted) futures.add(AddTask(*threadPool, failingTask>())); BOOST_CHECK_THROW(futures.join(), TestExceptionN<1>); } + +struct NonCopyableFunctionObject +{ + NonCopyableFunctionObject() = default; + NonCopyableFunctionObject(const NonCopyableFunctionObject&) = delete; + NonCopyableFunctionObject& operator=(const NonCopyableFunctionObject&) = delete; + + bool called = false; + + void operator()() + { + called = true; + } +}; + +BOOST_AUTO_TEST_CASE(allow_to_use_function_object_pointer) +{ + auto threadPool = createThreadPool(1); + auto functionObjectPtr = std::make_shared(); + BOOST_CHECK(!functionObjectPtr->called); + TaskFuture future = AddTask(*threadPool, functionObjectPtr); + future.get(); + BOOST_CHECK(functionObjectPtr->called); +}