From e09d26f3c6b2dc04b8041b4e190d404ea54275b5 Mon Sep 17 00:00:00 2001 From: Denis Blank Date: Sat, 31 Aug 2019 03:30:03 +0200 Subject: [PATCH] First work on passing an exception to the executable work * Make work r-value callable only by default --- include/continuable/continuable-types.hpp | 11 +- include/continuable/continuable-work-base.hpp | 178 ++++++++++++++++++ include/continuable/continuable.hpp | 1 + include/continuable/detail/core/base.hpp | 78 ++++++-- include/continuable/detail/other/erasure.hpp | 44 ++++- include/continuable/operations/async.hpp | 4 +- test/playground/test-playground.cpp | 17 ++ 7 files changed, 302 insertions(+), 31 deletions(-) create mode 100644 include/continuable/continuable-work-base.hpp diff --git a/include/continuable/continuable-types.hpp b/include/continuable/continuable-types.hpp index 5a6ca6a..c83aee9 100644 --- a/include/continuable/continuable-types.hpp +++ b/include/continuable/continuable-types.hpp @@ -35,6 +35,7 @@ #include #include #include +#include #include namespace cti { @@ -82,14 +83,16 @@ using promise = promise_base, // signature_arg_t>; /// Defines a non-copyable type erasure which is capable of carrying -/// callable objects passed to executors. +/// callable objects passed to executors. Additionally the outstanding work +/// can be resolved through an exception. /// -/// \note You can always define your own work with a type erasure of +/// \note You can always define your own cancelable_work with a type erasure of /// choice, the type erasure wrapper just needs to accept a -/// callable object which is callable with a `void()` signature. +/// callable object which is callable with a `void()` and +/// `void(exception_arg_t, exception_t)` signature. /// /// \since 4.0.0 -using work = detail::erasure::work; +using work = work_base; /// \} } // namespace cti diff --git a/include/continuable/continuable-work-base.hpp b/include/continuable/continuable-work-base.hpp new file mode 100644 index 0000000..e8cf847 --- /dev/null +++ b/include/continuable/continuable-work-base.hpp @@ -0,0 +1,178 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v4.0.0 + + Copyright(c) 2015 - 2019 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_WORK_BASE_HPP_INCLUDED +#define CONTINUABLE_WORK_BASE_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cti { +/// \defgroup Base Base +/// provides classes and functions to create continuable_base objects. +/// \{ + +/// The work_base makes it possible to resolve an asynchronous +/// continuable through it's result or through an error type. +/// +/// Use the work type defined in `continuable/continuable_types.hpp`, +/// in order to use this class. +/// +/// If we want to resolve the work_base trough the call operator, +/// and we want to resolve it through an exception, we must call it with a +/// exception_arg_t as first and the exception as second argument. +/// Additionally the work is resolveable only through its call +/// operator when invoked as an r-value. +/// +/// \since 4.0.0 +template +class work_base + /// \cond false + : detail::util::non_copyable +/// \endcond +{ // clang-format on + + /// \cond false + // The work type + Data data_; + /// \endcond + +public: + /// Constructor for constructing an empty work + explicit work_base() = default; + /// Constructor accepting the data object + explicit work_base(Data data) : data_(std::move(data)) { + } + + /// \cond false + work_base(work_base&&) = default; + work_base(work_base const&) = delete; + + work_base& operator=(work_base&&) = default; + work_base& operator=(work_base const&) = delete; + /// \endcond + + /// Constructor accepting any object convertible to the data object + template , Data>::value>* = nullptr> + /* implicit */ work_base(OData&& data) : data_(std::forward(data)) { + } + + /// Assignment operator accepting any object convertible to the data object + template , Data>::value>* = nullptr> + work_base& operator=(OData&& data) { + data_ = std::forward(data); + return *this; + } + + /// Resolves the continuation with the given values. + /// + /// \throws This method never throws an exception. + /// + /// \attention This method may only be called once, + /// when the work is valid operator bool() returns true. + /// Calling this method will invalidate the work such that + /// subsequent calls to operator bool() will return false. + /// This behaviour is only consistent in work_base and + /// non type erased promises may behave differently. + /// Invoking an invalid work_base is undefined! + /// + /// \since 4.0.0 + void operator()() && noexcept { + std::move(data_)(); + data_ = nullptr; + } + /// Resolves the continuation with the given exception. + /// + /// \throws This method never throws an exception. + /// + /// \attention This method may only be called once, + /// when the work is valid operator bool() returns true. + /// Calling this method will invalidate the work such that + /// subsequent calls to operator bool() will return false. + /// This behaviour is only consistent in work_base and + /// non type erased promises may behave differently. + /// Invoking an invalid work_base is undefined! + /// + /// \since 4.0.0 + void operator()(exception_arg_t tag, exception_t exception) && noexcept { + std::move(data_)(tag, std::move(exception)); + data_ = nullptr; + } + + /// Resolves the continuation with the given values. + /// + /// \throws This method never throws an exception. + /// + /// \attention This method may only be called once, + /// when the work is valid operator bool() returns true. + /// Calling this method will invalidate the work such that + /// subsequent calls to operator bool() will return false. + /// This behaviour is only consistent in work_base and + /// non type erased promises may behave differently. + /// Invoking an invalid work_base is undefined! + /// + /// \since 4.0.0 + void set_value() noexcept { + std::move(data_)(); + data_ = nullptr; + } + + /// Resolves the continuation with the given exception. + /// + /// \throws This method never throws an exception. + /// + /// \attention This method may only be called once, + /// when the work is valid operator bool() returns true. + /// Calling this method will invalidate the work such that + /// subsequent calls to operator bool() will return false. + /// This behaviour is only consistent in work_base and + /// non type erased promises may behave differently. + /// Invoking an invalid work_base is undefined! + /// + /// \since 4.0.0 + void set_exception(exception_t exception) noexcept { + std::move(data_)(exception_arg_t{}, std::move(exception)); + data_ = nullptr; + } +}; +/// \} +} // namespace cti + +#endif // CONTINUABLE_WORK_BASE_HPP_INCLUDED diff --git a/include/continuable/continuable.hpp b/include/continuable/continuable.hpp index cbbc43a..c1564c7 100644 --- a/include/continuable/continuable.hpp +++ b/include/continuable/continuable.hpp @@ -57,5 +57,6 @@ namespace cti {} #include #include #include +#include #endif // CONTINUABLE_HPP_INCLUDED diff --git a/include/continuable/detail/core/base.hpp b/include/continuable/detail/core/base.hpp index d6d7f14..0fa2a5d 100644 --- a/include/continuable/detail/core/base.hpp +++ b/include/continuable/detail/core/base.hpp @@ -472,36 +472,76 @@ constexpr auto invoker_of(identity>) { } // namespace decoration /// Invoke the callback immediately -template +template void on_executor(types::this_thread_executor_tag, Invoker&& invoker, + Callback&& callback, NextCallback&& next_callback, Args&&... args) { // Invoke the callback with the decorated invoker immediately - std::forward(invoker)(std::forward(args)...); + std::forward(invoker)(std::forward(callback), + std::forward(next_callback), + std::forward(args)...); } -/// Invoke the callback through the given executor -template -void on_executor(Executor&& executor, Invoker&& invoker, Args&&... args) { +template +class work_proxy { +public: + work_proxy(Invoker&& invoker, Callback&& callback, + NextCallback&& next_callback, std::tuple&& args) + : invoker_(std::move(invoker)), callback_(std::move(callback)), + next_callback_(std::move(next_callback)), args_(std::move(args)) { + } + ~work_proxy() = default; + work_proxy(work_proxy&&) = default; + work_proxy(work_proxy const&) = delete; + work_proxy& operator=(work_proxy&&) = default; + work_proxy& operator=(work_proxy const&) = delete; - // Create a worker object which when invoked calls the callback with the - // the returned arguments. - auto work = [ - invoker = std::forward(invoker), - args = std::make_tuple(std::forward(args)...) - ]() mutable { + void operator()() && { traits::unpack( [&](auto&&... captured_args) { - // Just use the packed dispatch method which dispatches the work on - // the current thread. - on_executor(types::this_thread_executor_tag{}, std::move(invoker), - std::forward(captured_args)...); + // Just use the packed dispatch method which dispatches the work_proxy + // on the current thread. + std::move(invoker_)( + std::move(callback_), std::move(next_callback_), + std::forward(captured_args)...); }, - std::move(args)); - }; + std::move(args_)); + } - // Pass the work callable object to the executor - std::forward(executor)(std::move(work)); + void operator()(exception_arg_t, exception_t exception) && { + std::move(next_callback_)(exception_arg_t{}, std::move(exception)); + } + + void set_exception(exception_t exception) noexcept { + std::move(next_callback_)(exception_arg_t{}, std::move(exception)); + } + +private: + Invoker invoker_; + Callback callback_; + NextCallback next_callback_; + std::tuple args_; +}; + +/// Invoke the callback through the given executor +template +void on_executor(Executor&& executor, Invoker&& invoker, Callback&& callback, + NextCallback&& next_callback, Args&&... args) { + + // Create a work_proxy object which when invoked calls the callback with the + // the returned arguments and pass the work_proxy callable object to the + // executor + using work_proxy_t = + work_proxy, std::decay_t, + std::decay_t...>; + std::forward(executor)(work_proxy_t( + std::forward(invoker), std::forward(callback), + std::forward(next_callback), + std::make_tuple(std::forward(args)...))); } /// Tells whether we potentially move the chain upwards and handle the result diff --git a/include/continuable/detail/other/erasure.hpp b/include/continuable/detail/other/erasure.hpp index 6b83bfd..c42b91c 100644 --- a/include/continuable/detail/other/erasure.hpp +++ b/include/continuable/detail/other/erasure.hpp @@ -178,12 +178,24 @@ public: }; #endif -using work_erasure_t = fu2::unique_function; +using work_erasure_t = + fu2::function_base, true, false, + void()&&, void(exception_arg_t, exception_t) &&>; #ifdef CONTINUABLE_HAS_IMMEDIATE_TYPES using work = work_erasure_t; #else -class work : public work_erasure_t { +class work; + +template +struct is_work : std::false_type {}; +template <> +struct is_work : std::true_type {}; + +class work { + using erasure_t = work_erasure_t; + erasure_t erasure_; + public: work() = default; ~work() = default; @@ -192,11 +204,31 @@ public: work& operator=(work const&) = delete; work& operator=(work&&) = default; - using work_erasure_t::work_erasure_t; - using work_erasure_t::operator=; - using work_erasure_t::operator(); + template < + typename T, + std::enable_if_t::value>* = nullptr, + std::enable_if_t>::value>* = nullptr> + /* implicit */ work(T&& callable) : erasure_(std::forward(callable)) { + } + + template < + typename T, + std::enable_if_t::value>* = nullptr, + std::enable_if_t>::value>* = nullptr> + work& operator=(T&& callable) { + erasure_ = std::forward(callable); + return *this; + } + + void operator()() && { + std::move(erasure_)(); + } + + void operator()(exception_arg_t, exception_t e) && { + std::move(erasure_)(exception_arg_t{}, std::move(e)); + } }; -#endif // CONTINUABLE_HAS_IMMEDIATE_TYPES +#endif } // namespace erasure } // namespace detail } // namespace cti diff --git a/include/continuable/operations/async.hpp b/include/continuable/operations/async.hpp index a48a406..e56ee2f 100644 --- a/include/continuable/operations/async.hpp +++ b/include/continuable/operations/async.hpp @@ -61,7 +61,7 @@ namespace cti { /// \param args The arguments which are passed to the callable upon invocation. /// /// \returns A continuable_base which asynchronous result type will -/// be computated with the same rules as continuable_base::then . +/// be computed with the same rules as continuable_base::then . /// /// \since 4.0.0 /// @@ -100,7 +100,7 @@ auto async(Callable&& callable, Args&&... args) { /// \param args The arguments which are passed to the callable upon invocation. /// /// \returns A continuable_base which asynchronous result type will -/// be computated with the same rules as continuable_base::then . +/// be computed with the same rules as continuable_base::then . /// /// \since 4.0.0 /// diff --git a/test/playground/test-playground.cpp b/test/playground/test-playground.cpp index 0c5e288..793c00c 100644 --- a/test/playground/test-playground.cpp +++ b/test/playground/test-playground.cpp @@ -26,4 +26,21 @@ using namespace cti; int main(int, char**) { // ... + auto e = std::make_exception_ptr(std::exception("huhu")); + + async_on( + [] { + // + int i = 0; + }, + [&](work work) { + int i = 0; + (void)i; + work.set_exception(e); // + }) + .fail([](exception_t e) { + // + int i = 0; + (void)i; + }); }