From 9feb96948070162c977587450bf887a03d19144b Mon Sep 17 00:00:00 2001 From: Chris Cotter Date: Sat, 24 Jun 2023 00:22:48 -0400 Subject: [PATCH 1/7] Update finally to support value channel references This aligns with the general idea that senders should be able to send references and values, so the adaptor algorithms should preserve the types in value channels when there is no specific requirement to modify the value channel type. In making this change, the CreateTest.AwaitTest test failed to compile. This appears to be the create operation accepting universal reference `Ts&&...` arguments which are then forwarded to the underlying receiver's `set_value`. However, in this test, the type of `result` is `int&`, but the create sender only sends `int`. Before this change (and before the schedule affine task), something else ended up decaying the parameter's type further downstream. With the scheduler affine task and its reliance on `finally`, the `finally` receiver's `set_value` strictly enforces that it's only called with the expected type, which caused the compilation error with my changes. Fundamentally, the create sender was incorrectly sending `int&` despite advertising it only sends `int`. I updated the create algorithm to faithfully send exactly what it advertises to fix the problem. I left finally's existing behavior of decay_t-ing error types since I ran into other issues trying to get that to work, and it seems less useful/likely (though, I don't see why it shouldn't be done later). Note that per discussion in #541, this is a source and behavior breaking change. --- include/unifex/create.hpp | 18 +++--- include/unifex/finally.hpp | 11 ++-- test/create_test.cpp | 51 ++++++++++++---- test/finally_test.cpp | 116 +++++++++++++++++++++++++++++++++++-- 4 files changed, 167 insertions(+), 29 deletions(-) diff --git a/include/unifex/create.hpp b/include/unifex/create.hpp index 86113713b..a655c0c5d 100644 --- a/include/unifex/create.hpp +++ b/include/unifex/create.hpp @@ -24,7 +24,7 @@ namespace unifex { namespace _create { -template +template struct _op { struct type { explicit type(Receiver rec, Fn fn, Context ctx) @@ -36,7 +36,7 @@ struct _op { template (typename... Ts) (requires receiver_of) void set_value(Ts&&... ts) noexcept(is_nothrow_receiver_of_v) { - unifex::set_value((Receiver&&) rec_, (Ts&&) ts...); + unifex::set_value((Receiver&&) rec_, (ValueTypes) ts...); } template (typename Error) @@ -79,10 +79,10 @@ struct _op { }; }; -template -using _operation = typename _op::type; +template +using _operation = typename _op::type; -template +template struct _snd_base { struct type { template class Variant> @@ -101,14 +101,14 @@ struct _snd_base { (requires derived_from, type> AND constructible_from> AND constructible_from>) - friend _operation, Fn, Context> + friend _operation, Fn, Context, ValueTypes...> tag_invoke(tag_t, Self&& self, Receiver&& rec) noexcept(std::is_nothrow_constructible_v< - _operation, + _operation, Receiver, member_t, member_t>) { - return _operation, Fn, Context>{ + return _operation, Fn, Context, ValueTypes...>{ (Receiver&&) rec, ((Self&&) self).fn_, ((Self&&) self).ctx_}; @@ -121,7 +121,7 @@ struct _snd_base { template struct _snd { - struct type : _snd_base::type { + struct type : _snd_base::type { template class Variant, template class Tuple> using value_types = Variant>; }; diff --git a/include/unifex/finally.hpp b/include/unifex/finally.hpp index c3c012cf5..fee455a80 100644 --- a/include/unifex/finally.hpp +++ b/include/unifex/finally.hpp @@ -71,7 +71,7 @@ namespace unifex SourceSender, CompletionSender, Receiver, - std::decay_t...>::type; + Values...>::type; template < typename SourceSender, @@ -384,7 +384,7 @@ namespace unifex auto* const op = op_; UNIFEX_TRY { - unifex::activate_union_member...>>( + unifex::activate_union_member>( op->value_, static_cast(values)...); } UNIFEX_CATCH (...) { std::move(*this).set_error(std::current_exception()); @@ -411,8 +411,7 @@ namespace unifex }); unifex::start(completionOp); } UNIFEX_CATCH (...) { - using decayed_tuple_t = std::tuple...>; - unifex::deactivate_union_member(op->value_); + unifex::deactivate_union_member>(op->value_); unifex::set_error( static_cast(op->receiver_), std::current_exception()); } @@ -593,7 +592,7 @@ namespace unifex sender_value_types_t< remove_cvref_t, manual_lifetime_union, - decayed_tuple::template apply> + std::tuple> value_; }; @@ -647,7 +646,7 @@ namespace unifex template class Variant, template class Tuple> using value_types = typename sender_traits:: - template value_types::template apply>; + template value_types; // This can produce any of the error_types of SourceSender, or of // CompletionSender or an exception_ptr corresponding to an exception thrown diff --git a/test/create_test.cpp b/test/create_test.cpp index b1ae2d259..650d721c1 100644 --- a/test/create_test.cpp +++ b/test/create_test.cpp @@ -30,6 +30,9 @@ using namespace unifex; namespace { + +int global; + struct CreateTest : testing::Test { unifex::single_thread_context someThread; unifex::async_scope someScope; @@ -47,6 +50,14 @@ struct CreateTest : testing::Test { }); } + void anIntRefAPI(void* context, void (*completed)(void* context, int& result)) { + // Execute some work asynchronously on some other thread. When its + // work is finished, pass the result to the callback. + someScope.detached_spawn_call_on(someThread.get_scheduler(), [=]() noexcept { + completed(context, global); + }); + } + void aVoidAPI(void* context, void (*completed)(void* context)) { // Execute some work asynchronously on some other thread. When its // work is finished, pass the result to the callback. @@ -58,18 +69,38 @@ struct CreateTest : testing::Test { } // anonymous namespace TEST_F(CreateTest, BasicTest) { - auto snd = [this](int a, int b) { - return create([a, b, this](auto& rec) { - static_assert(receiver_of); - anIntAPI(a, b, &rec, [](void* context, int result) { - unifex::void_cast(context).set_value(result); + { + auto snd = [this](int a, int b) { + return create([a, b, this](auto& rec) { + static_assert(receiver_of); + anIntAPI(a, b, &rec, [](void* context, int result) { + unifex::void_cast(context).set_value(result); + }); }); - }); - }(1, 2); + }(1, 2); - std::optional res = sync_wait(std::move(snd)); - ASSERT_TRUE(res.has_value()); - EXPECT_EQ(*res, 3); + std::optional res = sync_wait(std::move(snd)); + ASSERT_TRUE(res.has_value()); + EXPECT_EQ(*res, 3); + } + + { + auto snd = [this]() { + return create([this](auto& rec) { + static_assert(receiver_of); + anIntRefAPI(&rec, [](void* context, int& result) { + unifex::void_cast(context).set_value(result); + }); + }); + }(); + + std::optional> res = sync_wait(std::move(snd)); + ASSERT_TRUE(res.has_value()); + global = 0; + EXPECT_EQ(*res, 0); + global = 10; + EXPECT_EQ(*res, 10); + } } TEST_F(CreateTest, VoidWithContextTest) { diff --git a/test/finally_test.cpp b/test/finally_test.cpp index 338836b8d..b29d1ce1f 100644 --- a/test/finally_test.cpp +++ b/test/finally_test.cpp @@ -14,19 +14,24 @@ * limitations under the License. */ #include + #include -#include -#include -#include -#include #include #include #include +#include #include #include +#include +#include +#include +#include +#include #include #include +#include +#include #include @@ -45,6 +50,109 @@ TEST(Finally, Value) { EXPECT_EQ(res->second, context.get_thread_id()); } +TEST(Finally, Ref) { + { + int a = 0; + + auto sndr = just_from([&a]() -> int& { return a; }) + | finally(just()); + using Sndr = decltype(sndr); + + static_assert(std::is_same_v< + Sndr::value_types, + std::variant> + >); + static_assert(std::is_same_v< + Sndr::error_types, + std::variant + >); + static_assert(!Sndr::sends_done); + + auto res = std::move(sndr) | sync_wait(); + + ASSERT_FALSE(!res); + EXPECT_EQ(*res, 0); + a = 10; + EXPECT_EQ(*res, 10); + } + + { + int a = 0; + + auto res = just_from([&a]() -> const int& { return a; }) + | finally(just()) + | sync_wait(); + + ASSERT_FALSE(!res); + EXPECT_EQ(*res, 0); + a = 10; + EXPECT_EQ(*res, 10); + } + + { + int a = 0; + + auto res = just_from([&a]() -> int& { return a; }) + | finally(just()) + | then([](int& i) -> int& { return i; }) + | sync_wait(); + + ASSERT_FALSE(!res); + EXPECT_EQ(*res, 0); + a = 10; + EXPECT_EQ(*res, 10); + } +} + +struct sends_error_ref { + + template < + template class Variant, + template class Tuple> + using value_types = Variant>; + + template