From 5d9214ca5baf14ccb539ad0235f4ff3105fb5532 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sat, 15 Aug 2026 18:35:19 +0200 Subject: [PATCH 1/3] Stop libdispatch bulk after value capture failure --- include/exec/libdispatch_queue.hpp | 1 + test/exec/test_libdispatch.cpp | 45 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/include/exec/libdispatch_queue.hpp b/include/exec/libdispatch_queue.hpp index 936325d4a..c2a410ea8 100644 --- a/include/exec/libdispatch_queue.hpp +++ b/include/exec/libdispatch_queue.hpp @@ -481,6 +481,7 @@ namespace experimental::execution STDEXEC_CATCH_ALL { STDEXEC::set_error(std::move(shared_state_.rcvr_), std::current_exception()); + return; } } else diff --git a/test/exec/test_libdispatch.cpp b/test/exec/test_libdispatch.cpp index 3a852b4c4..ccf510672 100644 --- a/test/exec/test_libdispatch.cpp +++ b/test/exec/test_libdispatch.cpp @@ -106,5 +106,50 @@ namespace FAIL("invalid exception caught"); } } + + TEST_CASE("libdispatch bulk stops after value capture fails") + { + struct value_capture_error + {}; + + struct throwing_value + { + throwing_value() = default; + + throwing_value(throwing_value const &) + { + throw value_capture_error{}; + } + + throwing_value(throwing_value &&) + { + throw value_capture_error{}; + } + }; + + exec::libdispatch_queue queue; + auto sch = queue.get_scheduler(); + int bulk_calls = 0; + + auto sender = STDEXEC::schedule(sch) | STDEXEC::then([]() noexcept { return throwing_value{}; }) + | STDEXEC::bulk(STDEXEC::par, + 0, + [&](int, throwing_value &) noexcept { ++bulk_calls; }); + + STDEXEC_TRY + { + STDEXEC::sync_wait(std::move(sender)); + CHECK(false); + } + STDEXEC_CATCH(value_capture_error const &) + { + } + STDEXEC_CATCH_ALL + { + FAIL("invalid exception caught"); + } + + CHECK(bulk_calls == 0); + } #endif } // namespace From 3fefbdeff3f1560299273fe408d09658ca2f7454 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sat, 15 Aug 2026 20:09:44 +0200 Subject: [PATCH 2/3] Advertise libdispatch bulk capture errors --- include/exec/libdispatch_queue.hpp | 3 ++- test/exec/test_libdispatch.cpp | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/include/exec/libdispatch_queue.hpp b/include/exec/libdispatch_queue.hpp index c2a410ea8..94ed40a0f 100644 --- a/include/exec/libdispatch_queue.hpp +++ b/include/exec/libdispatch_queue.hpp @@ -305,7 +305,8 @@ namespace experimental::execution _WITH_PRETTY_SENDER_<__copy_cvref_t>, _WITH_ENVIRONMENT_(Env...)>(); } - else if constexpr (__nothrow_applicable) + else if constexpr (__nothrow_applicable + && __nothrow_decay_copyable) { return completion_signatures(); } diff --git a/test/exec/test_libdispatch.cpp b/test/exec/test_libdispatch.cpp index ccf510672..cff0225a6 100644 --- a/test/exec/test_libdispatch.cpp +++ b/test/exec/test_libdispatch.cpp @@ -17,6 +17,7 @@ #include "exec/libdispatch_queue.hpp" #include "stdexec/execution.hpp" #include "test_common/catch2.hpp" +#include "test_common/type_helpers.hpp" #include #include @@ -128,13 +129,16 @@ namespace }; exec::libdispatch_queue queue; - auto sch = queue.get_scheduler(); - int bulk_calls = 0; + auto sch = queue.get_scheduler(); auto sender = STDEXEC::schedule(sch) | STDEXEC::then([]() noexcept { return throwing_value{}; }) - | STDEXEC::bulk(STDEXEC::par, - 0, - [&](int, throwing_value &) noexcept { ++bulk_calls; }); + | STDEXEC::bulk(STDEXEC::par, 0, [](int, throwing_value &) noexcept {}); + + STATIC_REQUIRE( + set_equivalent>, + STDEXEC::completion_signatures>); STDEXEC_TRY { @@ -148,8 +152,6 @@ namespace { FAIL("invalid exception caught"); } - - CHECK(bulk_calls == 0); } #endif } // namespace From 49b48e98b3b1b245061b3ce50a433f68dda9b74c Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sat, 15 Aug 2026 22:03:55 +0200 Subject: [PATCH 3/3] Preserve libdispatch bulk value categories --- include/exec/libdispatch_queue.hpp | 4 +-- test/exec/test_libdispatch.cpp | 55 +++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/include/exec/libdispatch_queue.hpp b/include/exec/libdispatch_queue.hpp index 94ed40a0f..e9ded94cb 100644 --- a/include/exec/libdispatch_queue.hpp +++ b/include/exec/libdispatch_queue.hpp @@ -477,7 +477,7 @@ namespace experimental::execution { STDEXEC_TRY { - shared_state_.data_.template emplace(std::move(as)...); + shared_state_.data_.template emplace(static_cast(as)...); } STDEXEC_CATCH_ALL { @@ -487,7 +487,7 @@ namespace experimental::execution } else { - shared_state_.data_.template emplace(std::move(as)...); + shared_state_.data_.template emplace(static_cast(as)...); } if (shared_state_.shape_) diff --git a/test/exec/test_libdispatch.cpp b/test/exec/test_libdispatch.cpp index cff0225a6..2f242b117 100644 --- a/test/exec/test_libdispatch.cpp +++ b/test/exec/test_libdispatch.cpp @@ -31,7 +31,7 @@ namespace auto sch = queue.get_scheduler(); std::vector data{1, 2, 3, 4, 5}; - auto add = [](auto const & data) + auto add = [](auto const &data) { return std::accumulate(std::begin(data), std::end(data), 0); }; @@ -53,11 +53,11 @@ namespace std::vector data{1, 2, 3, 4, 5}; auto size = data.size(); - auto expensive_computation = [](auto i, auto& data) + auto expensive_computation = [](auto i, auto &data) { data[i] = 2 * data[i]; }; - auto add = [](auto const & data) + auto add = [](auto const &data) { return std::accumulate(std::begin(data), std::end(data), 0); }; @@ -86,7 +86,7 @@ namespace throw 999; return 2 * data[i]; }; - auto add = [](auto const & data) + auto add = [](auto const &data) { return std::accumulate(std::begin(data), std::end(data), 0); }; @@ -153,5 +153,52 @@ namespace FAIL("invalid exception caught"); } } + #endif + + TEST_CASE("libdispatch bulk preserves lvalue-reference value categories") + { + struct lvalue_value + { + lvalue_value() + : value(0) + {} + + explicit lvalue_value(int value) + : value(value) + {} + + lvalue_value(lvalue_value const &) noexcept = default; + + lvalue_value(lvalue_value &&other) noexcept(false) + : value(other.value) + { + other.moved_from = true; + } + + int value; + bool moved_from = false; + } value{42}; + + exec::libdispatch_queue queue; + auto sch = queue.get_scheduler(); + int seen = 0; + + auto sender = STDEXEC::schedule(sch) + | STDEXEC::then([&]() noexcept -> lvalue_value & { return value; }) + | STDEXEC::bulk(STDEXEC::par, + 1, + [&](int, lvalue_value &item) noexcept { seen = item.value; }); + + STATIC_REQUIRE( + set_equivalent>, + STDEXEC::completion_signatures>); + + auto result = STDEXEC::sync_wait(std::move(sender)); + + REQUIRE(result.has_value()); + CHECK(seen == 42); + CHECK_FALSE(value.moved_from); + } } // namespace