Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2024-04-06 12:11:55

0001 #ifndef FWCore_Concurrency_chain_first_h
0002 #define FWCore_Concurrency_chain_first_h
0003 // -*- C++ -*-
0004 //
0005 // Package:     Concurrency
0006 // function  :     edm::waiting_task::chain::first
0007 //
0008 /**\function chain_first
0009 
0010  Description: Handles creation of a chain of WaitingTasks
0011 
0012  Usage:
0013     Use the function to begin constructing a chain of waiting tasks.
0014     Once the chain is declared, call lastTask() with a WaitingTaskHolder
0015     to get back a new WaitingTaskHolder or runLast() to schedule the chain to run.
0016 */
0017 //
0018 // Original Author:  Chris Jones
0019 //         Created:  Thu Feb 21 13:46:31 CST 2013
0020 // $Id$
0021 //
0022 
0023 // system include files
0024 #include <atomic>
0025 #include <exception>
0026 #include <memory>
0027 #include <type_traits>
0028 
0029 // user include files
0030 #include "FWCore/Concurrency/interface/WaitingTaskHolder.h"
0031 #include "FWCore/Utilities/interface/thread_safety_macros.h"
0032 
0033 // forward declarations
0034 
0035 namespace edm {
0036   namespace waiting_task::detail {
0037 
0038     template <class, class = void>
0039     struct has_exception_handling : std::false_type {};
0040 
0041     template <class T>
0042     struct has_exception_handling<T,
0043                                   std::void_t<decltype(std::declval<T&>()(
0044                                       static_cast<std::exception_ptr const*>(nullptr), edm::WaitingTaskHolder()))>>
0045         : std::true_type {};
0046 
0047     template <typename F>
0048     struct AutoExceptionHandler {
0049       AutoExceptionHandler(F&& iF) : f_{std::forward<F>(iF)} {}
0050 
0051       void operator()(std::exception_ptr const* iPtr, edm::WaitingTaskHolder h) noexcept {
0052         if (iPtr) {
0053           h.doneWaiting(*iPtr);
0054         } else {
0055           CMS_SA_ALLOW try { f_(h); } catch (...) {
0056             h.doneWaiting(std::current_exception());
0057           }
0058         }
0059       }
0060 
0061     private:
0062       F f_;
0063     };
0064 
0065     template <typename E, typename F>
0066     struct ExplicitExceptionHandler {
0067       ExplicitExceptionHandler(E&& iE, F&& iF) : except_(std::forward<E>(iE)), f_{std::forward<F>(iF)} {}
0068 
0069       void operator()(std::exception_ptr const* iPtr, edm::WaitingTaskHolder h) noexcept {
0070         if (iPtr) {
0071           except_(*iPtr);
0072           h.doneWaiting(*iPtr);
0073         } else {
0074           CMS_SA_ALLOW try { f_(h); } catch (...) {
0075             h.doneWaiting(std::current_exception());
0076           }
0077         }
0078       }
0079 
0080     private:
0081       E except_;
0082       F f_;
0083     };
0084 
0085     /**Creates a functor adaptor which assembled two different functors into one. To use, one calls the constructor immediately followed by the else_ method. The created functor has the following behavior:
0086  If a previous task had an exception, only the first functor given to the constructor, iE, will be run and passed the std::exception_ptr const. If there
0087  was no exception, then only the functor passed to else_, iF, will be run. If iF has an exception, it will be automatically propagated to the edm::WaitingTaskHolder. */
0088     template <typename E>
0089     struct IfExceptionAdapter {
0090       constexpr IfExceptionAdapter(E&& iE) : except_(std::forward<E>(iE)) {}
0091 
0092       template <typename F>
0093       constexpr auto else_(F&& iF) {
0094         return ExplicitExceptionHandler<E, F>(std::move(except_), std::forward<F>(iF));
0095       }
0096 
0097     private:
0098       E except_;
0099     };
0100 
0101     template <typename... T>
0102     struct WaitingTaskChain;
0103 
0104     template <typename F>
0105     struct Conditional {};
0106 
0107     template <typename F>
0108     struct ConditionalAdaptor {
0109       constexpr explicit ConditionalAdaptor(bool iCond, F&& iF) : f_(std::forward<F>(iF)), condition_(iCond) {}
0110 
0111       template <typename... T>
0112       [[nodiscard]] constexpr auto pipe(WaitingTaskChain<T...> iChain) {
0113         return WaitingTaskChain<Conditional<F>, T...>(condition_, std::move(f_), std::move(iChain));
0114       }
0115 
0116       F f_;
0117       bool condition_;
0118     };
0119 
0120     template <typename F>
0121     struct ThenAdaptor {
0122       constexpr explicit ThenAdaptor(F&& iF) : f_(std::forward<F>(iF)) {}
0123 
0124       template <typename... T>
0125       [[nodiscard]] constexpr auto pipe(WaitingTaskChain<T...> iChain) {
0126         return WaitingTaskChain<F, T...>(std::move(f_), std::move(iChain));
0127       }
0128 
0129     private:
0130       F f_;
0131     };
0132 
0133     struct RunLastAdaptor {
0134       explicit RunLastAdaptor(edm::WaitingTaskHolder iT) : task_(std::move(iT)) {}
0135 
0136       template <typename... T>
0137       constexpr void pipe(WaitingTaskChain<T...>&& iChain) {
0138         iChain.runLast(std::move(task_));
0139       }
0140 
0141     private:
0142       edm::WaitingTaskHolder task_;
0143     };
0144 
0145     struct LastTaskAdaptor {
0146       explicit LastTaskAdaptor(edm::WaitingTaskHolder iT) : task_(std::move(iT)) {}
0147 
0148       template <typename... T>
0149       constexpr auto pipe(WaitingTaskChain<T...>&& iChain) {
0150         return iChain.lastTask(std::move(task_));
0151       }
0152 
0153     private:
0154       edm::WaitingTaskHolder task_;
0155     };
0156 
0157     template <typename U>
0158     struct WaitingTaskChain<U> {
0159       constexpr explicit WaitingTaskChain(U&& iU) : f_{std::forward<U>(iU)} {}
0160 
0161       [[nodiscard]] WaitingTaskHolder lastTask(WaitingTaskHolder iV) {
0162         return WaitingTaskHolder(
0163             *iV.group(),
0164             make_waiting_task([f = std::move(f_), v = std::move(iV)](const std::exception_ptr* iPtr) mutable {
0165               f(iPtr, std::move(v));
0166             }));
0167       }
0168 
0169       void runLast(WaitingTaskHolder iH) { f_(nullptr, std::move(iH)); }
0170 
0171       template <typename V>
0172       friend auto operator|(WaitingTaskChain<U> iChain, V&& iV) {
0173         return iV.pipe(std::move(iChain));
0174       }
0175 
0176     private:
0177       U f_;
0178     };
0179 
0180     template <typename U, typename... T>
0181     struct WaitingTaskChain<U, T...> {
0182       explicit constexpr WaitingTaskChain(U&& iU, WaitingTaskChain<T...> iL)
0183           : l_(std::move(iL)), f_{std::forward<U>(iU)} {}
0184 
0185       [[nodiscard]] WaitingTaskHolder lastTask(WaitingTaskHolder iV) {
0186         return l_.lastTask(WaitingTaskHolder(
0187             *iV.group(),
0188             make_waiting_task([f = std::move(f_), v = std::move(iV)](std::exception_ptr const* iPtr) mutable {
0189               f(iPtr, std::move(v));
0190             })));
0191       }
0192 
0193       void runLast(WaitingTaskHolder iV) {
0194         l_.runLast(WaitingTaskHolder(
0195             *iV.group(),
0196             make_waiting_task([f = std::move(f_), v = std::move(iV)](std::exception_ptr const* iPtr) mutable {
0197               f(iPtr, std::move(v));
0198             })));
0199       }
0200 
0201       template <typename V>
0202       friend auto operator|(WaitingTaskChain<U, T...> iChain, V&& iV) {
0203         return iV.pipe(std::move(iChain));
0204       }
0205 
0206     private:
0207       WaitingTaskChain<T...> l_;
0208       U f_;
0209     };
0210 
0211     template <typename U, typename... T>
0212     struct WaitingTaskChain<Conditional<U>, T...> {
0213       explicit constexpr WaitingTaskChain(bool iCondition, U&& iU, WaitingTaskChain<T...> iL)
0214           : l_(std::move(iL)), f_{std::forward<U>(iU)}, condition_(iCondition) {}
0215 
0216       explicit constexpr WaitingTaskChain(Conditional<U> iC, WaitingTaskChain<T...> iL)
0217           : l_(std::move(iL)), f_{std::move<U>(iC.f_)}, condition_(iC.condition_) {}
0218 
0219       [[nodiscard]] WaitingTaskHolder lastTask(WaitingTaskHolder iV) {
0220         if (condition_) {
0221           return l_.lastTask(WaitingTaskHolder(
0222               *iV.group(),
0223               make_waiting_task([f = std::move(f_), v = std::move(iV)](std::exception_ptr const* iPtr) mutable {
0224                 f(iPtr, std::move(v));
0225               })));
0226         }
0227         return l_.lastTask(iV);
0228       }
0229 
0230       void runLast(WaitingTaskHolder iV) {
0231         if (condition_) {
0232           l_.runLast(WaitingTaskHolder(
0233               *iV.group(),
0234               make_waiting_task([f = std::move(f_), v = std::move(iV)](std::exception_ptr const* iPtr) mutable {
0235                 f(iPtr, std::move(v));
0236               })));
0237         } else {
0238           l_.runLast(iV);
0239         }
0240       }
0241 
0242       template <typename V>
0243       friend auto operator|(WaitingTaskChain<Conditional<U>, T...> iChain, V&& iV) {
0244         return iV.pipe(std::move(iChain));
0245       }
0246 
0247     private:
0248       WaitingTaskChain<T...> l_;
0249       U f_;
0250       bool condition_;
0251     };
0252 
0253   }  // namespace waiting_task::detail
0254   namespace waiting_task::chain {
0255 
0256     /** Sets the first task to be run as part of the task chain. The functor is expected to take either
0257    a single argument of type edm::WaitingTaskHolder or two arguments of type std::exception_ptr const* and WaitingTaskHolder. In the latter case, the pointer is only non-null if a previous task in the chain threw an exception.
0258    */
0259     template <typename F>
0260     [[nodiscard]] constexpr auto first(F&& iF) {
0261       using namespace detail;
0262       if constexpr (has_exception_handling<F>::value) {
0263         return WaitingTaskChain<F>(std::forward<F>(iF));
0264       } else {
0265         return WaitingTaskChain<AutoExceptionHandler<F>>(AutoExceptionHandler<F>(std::forward<F>(iF)));
0266       }
0267     }
0268 
0269     /**Define next task to run once this task has finished. Two different functor types are allowed
0270    1. The functor  takes a edm::WaitingTaskHolder as argument. If an exception happened in a previous task, the functor will NOT be run.
0271    If an exception happens while running the functor, the exception will be propagated to the WaitingTaskHolder.
0272    2. The functor takes a std::exception_ptr const* and WaitingTaskHolder. If an exception happened in a previous task, the first
0273    argument will be non-nullptr. In that case, the exception will NOT be automatically propagated to the WaitingTaskHolder. In addition,
0274    if the functor itself throws an exception, it is up to the functor to handle the exception.
0275    */
0276     template <typename O>
0277     [[nodiscard]] constexpr auto then(O&& iO) {
0278       using namespace detail;
0279       if constexpr (has_exception_handling<O>::value) {
0280         return ThenAdaptor<O>(std::forward<O>(iO));
0281       } else {
0282         return ThenAdaptor<AutoExceptionHandler<O>>(AutoExceptionHandler<O>(std::forward<O>(iO)));
0283       }
0284     }
0285 
0286     ///Only runs this task if the condition (which is known at the call time) is true. If false, this task will be skipped and the following task will run.
0287     template <typename O>
0288     [[nodiscard]] constexpr auto ifThen(bool iValue, O&& iO) {
0289       using namespace detail;
0290       if constexpr (has_exception_handling<O>::value) {
0291         return ConditionalAdaptor<O>(iValue, std::forward<O>(iO));
0292       } else {
0293         return ConditionalAdaptor<AutoExceptionHandler<O>>(iValue, AutoExceptionHandler<O>(std::forward<O>(iO)));
0294       }
0295     }
0296 
0297     [[nodiscard]] inline auto runLast(edm::WaitingTaskHolder iTask) { return detail::RunLastAdaptor(std::move(iTask)); }
0298 
0299     [[nodiscard]] inline auto lastTask(edm::WaitingTaskHolder iTask) {
0300       return detail::LastTaskAdaptor(std::move(iTask));
0301     }
0302 
0303     /**Creates a functor adaptor which assembled two different functors into one. To use, one calls the constructor immediately followed by the else_ method. The created functor has the following behavior:
0304  If a previous task had an exception, only the first functor given to the constructor, iE, will be run and passed the std::exception_ptr const. If there
0305  was no exception, then only the functor passed to else_, iF, will be run. If iF has an exception, it will be automatically propagated to the edm::WaitingTaskHolder. */
0306     template <typename E>
0307     [[nodiscard]] constexpr auto ifException(E&& iE) {
0308       return detail::IfExceptionAdapter(std::forward<E>(iE));
0309     }
0310 
0311   }  // namespace waiting_task::chain
0312 }  // namespace edm
0313 
0314 #endif