From 1cce4c305e5224abddf1541ded0c6d01fea9f583 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Wed, 1 Jan 2020 04:56:04 +0700 Subject: [PATCH] aggregate_exception for ANY promise --- headers/promise.hpp/promise.hpp | 95 +++++++++++++++++++++++++-------- untests/promise_tests.cpp | 41 +++++++++++--- 2 files changed, 107 insertions(+), 29 deletions(-) diff --git a/headers/promise.hpp/promise.hpp b/headers/promise.hpp/promise.hpp index 284bd16..daff97e 100644 --- a/headers/promise.hpp/promise.hpp +++ b/headers/promise.hpp/promise.hpp @@ -86,6 +86,54 @@ namespace promise_hpp no_timeout, timeout }; + + // + // aggregate_exception + // + + class aggregate_exception final : public std::exception { + private: + using exceptions_t = std::vector; + using internal_state_t = std::shared_ptr; + public: + aggregate_exception() + : state_(std::make_shared()) {} + + explicit aggregate_exception(exceptions_t exceptions) + : state_(std::make_shared(std::move(exceptions))) {} + + aggregate_exception(const aggregate_exception& other) noexcept + : state_(other.state_) {} + + aggregate_exception& operator=(const aggregate_exception& other) noexcept { + if ( this != &other ) { + state_ = other.state_; + } + return *this; + } + + const char* what() const noexcept override { + return "Aggregate exception"; + } + + bool empty() const noexcept { + return (*state_).empty(); + } + + std::size_t size() const noexcept { + return (*state_).size(); + } + + std::exception_ptr at(std::size_t index) const { + return (*state_).at(index); + } + + std::exception_ptr operator[](std::size_t index) const noexcept { + return (*state_)[index]; + } + private: + internal_state_t state_; + }; } // ----------------------------------------------------------------------------- @@ -1165,15 +1213,12 @@ namespace promise_hpp , results(count) {} }; - return make_promise([begin, end](auto&& resolver, auto&& rejector){ + return make_promise( + [begin, end](auto&& resolver, auto&& rejector){ std::size_t result_index = 0; auto context = std::make_shared(std::distance(begin, end)); for ( Iter iter = begin; iter != end; ++iter, ++result_index ) { - (*iter).then([ - context, - resolver, - result_index - ](auto&& v) mutable { + (*iter).then([context, resolver, result_index](auto&& v) mutable { context->results[result_index] = std::forward(v); if ( !--context->success_counter ) { std::vector results; @@ -1183,7 +1228,9 @@ namespace promise_hpp } resolver(std::move(results)); } - }).except(rejector); + }).except([rejector](std::exception_ptr e) mutable { + rejector(e); + }); } }); } @@ -1201,27 +1248,33 @@ namespace promise_hpp template < typename Iter , typename SubPromise = typename std::iterator_traits::value_type - , typename SubPromiseResult = typename SubPromise::value_type > - promise + , typename SubPromiseResult = typename SubPromise::value_type + , typename ResultPromiseValueType = SubPromiseResult > + promise make_any_promise(Iter begin, Iter end) { if ( begin == end ) { - throw std::logic_error("at least one input promise must be provided for make_any_promise"); + return make_rejected_promise(aggregate_exception()); } struct context_t { std::atomic_size_t failure_counter{0u}; + std::vector exceptions; context_t(std::size_t count) - : failure_counter(count) {} + : failure_counter(count) + , exceptions(count) {} }; - return make_promise([begin, end](auto&& resolver, auto&& rejector){ + return make_promise( + [begin, end](auto&& resolver, auto&& rejector){ + std::size_t exception_index = 0; auto context = std::make_shared(std::distance(begin, end)); - for ( Iter iter = begin; iter != end; ++iter ) { + for ( Iter iter = begin; iter != end; ++iter, ++exception_index ) { (*iter).then([resolver](auto&& v) mutable { resolver(std::forward(v)); - }).except([context, rejector](std::exception_ptr e) mutable { + }).except([context, rejector, exception_index](std::exception_ptr e) mutable { + context->exceptions[exception_index] = e; if ( !--context->failure_counter ) { - rejector(e); + rejector(aggregate_exception(std::move(context->exceptions))); } }); } @@ -1241,14 +1294,12 @@ namespace promise_hpp template < typename Iter , typename SubPromise = typename std::iterator_traits::value_type - , typename SubPromiseResult = typename SubPromise::value_type > - promise + , typename SubPromiseResult = typename SubPromise::value_type + , typename ResultPromiseValueType = SubPromiseResult > + promise make_race_promise(Iter begin, Iter end) { - if ( begin == end ) { - throw std::logic_error("at least one input promise must be provided for make_race_promise"); - } - - return make_promise([begin, end](auto&& resolver, auto&& rejector){ + return make_promise( + [begin, end](auto&& resolver, auto&& rejector){ for ( Iter iter = begin; iter != end; ++iter ) { (*iter) .then(resolver) diff --git a/untests/promise_tests.cpp b/untests/promise_tests.cpp index 2bcda50..0102267 100644 --- a/untests/promise_tests.cpp +++ b/untests/promise_tests.cpp @@ -30,6 +30,30 @@ namespace } } + bool check_empty_aggregate_exception(std::exception_ptr e) { + try { + std::rethrow_exception(e); + } catch (pr::aggregate_exception& ee) { + return ee.empty(); + } catch (...) { + return false; + } + } + + bool check_two_aggregate_exception(std::exception_ptr e) { + try { + std::rethrow_exception(e); + } catch (pr::aggregate_exception& ee) { + if ( ee.size() != 2 ) { + return false; + } + return check_hello_fail_exception(ee[0]) + && check_hello_fail_exception(ee[1]); + } catch (...) { + return false; + } + } + class auto_thread final { public: template < typename F, typename... Args > @@ -890,9 +914,15 @@ TEST_CASE("promise") { } } SECTION("make_any_promise") { - REQUIRE_THROWS_AS( - pr::make_any_promise(std::vector>{}), - std::logic_error); + { + bool all_is_ok = false; + auto p = pr::make_any_promise(std::vector>{}); + p.except([&all_is_ok](std::exception_ptr e){ + all_is_ok = check_empty_aggregate_exception(e); + return 0; + }); + REQUIRE(all_is_ok); + } { auto p = pr::make_resolved_promise().then_any([](){ return std::vector>{ @@ -937,7 +967,7 @@ TEST_CASE("promise") { pr::make_rejected_promise(std::logic_error("hello fail")), pr::make_rejected_promise(std::logic_error("hello fail")) }).except([&all_is_ok](std::exception_ptr e){ - all_is_ok = true; + all_is_ok = check_two_aggregate_exception(e); return 0; }); REQUIRE(all_is_ok); @@ -1140,9 +1170,6 @@ TEST_CASE("promise") { } } SECTION("make_race_promise_fail") { - REQUIRE_THROWS_AS( - pr::make_race_promise(std::vector>{}), - std::logic_error); { bool call_fail_with_logic_error = false; bool not_call_then_on_reject = true;