diff --git a/include/condy/awaiters.hpp b/include/condy/awaiters.hpp index 03c9bc94..4832e9ae 100644 --- a/include/condy/awaiters.hpp +++ b/include/condy/awaiters.hpp @@ -188,7 +188,7 @@ class [[nodiscard]] RangedParallelAwaiterBase { } public: - bool await_ready() const noexcept { return false; } + bool await_ready() const noexcept { return awaiters_.empty(); } template void await_suspend(std::coroutine_handle h) { @@ -249,6 +249,7 @@ using RangedWhenAllAwaiter = RangedParallelAwaiterBase< * @brief Awaiter to wait for any operation in a range to complete. * @details An awaiter that waits for any operation in a range to complete. * @tparam Awaiter The type of the awaiters in the range. + * @throws std::out_of_range if the range is empty. * @return std::pair A pair containing the index of the completed * awaiter and its result. */ diff --git a/include/condy/finish_handles.hpp b/include/condy/finish_handles.hpp index fb7120a3..230a4767 100644 --- a/include/condy/finish_handles.hpp +++ b/include/condy/finish_handles.hpp @@ -310,7 +310,10 @@ class RangedWhenAnyFinishHandle : public RangedParallelAnyFinishHandle { ReturnType extract_result() { auto r = Base::extract_result(); auto &[order, results] = r; - return std::make_pair(order[0], std::move(results[order[0]])); + // May throw out_of_range if the range is empty, which is expected and + // should be handled by caller. + auto idx = order.at(0); + return std::make_pair(idx, std::move(results[idx])); } }; diff --git a/tests/test_awaiter_operations.cpp b/tests/test_awaiter_operations.cpp index cb85f4d7..e8b69011 100644 --- a/tests/test_awaiter_operations.cpp +++ b/tests/test_awaiter_operations.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace condy::operators; @@ -162,6 +163,33 @@ TEST_CASE("test awaiter_operations - test ranged when_all") { context.reset(); } +TEST_CASE("test awaiter_operations - test when_all with empty range") { + condy::Ring ring; + io_uring_params params{}; + std::memset(¶ms, 0, sizeof(params)); + ring.init(8, ¶ms); + auto &context = condy::detail::Context::current(); + context.init(&ring, &runtime); + + size_t unfinished = 1; + auto func = [&]() -> condy::Coro { + using OpAwaiter = + decltype(condy::detail::make_op_awaiter(io_uring_prep_nop)); + std::vector awaiters; + auto r = co_await condy::when_all(std::move(awaiters)); + REQUIRE(r.empty()); + --unfinished; + }; + + auto coro = func(); + REQUIRE(unfinished == 1); + + coro.release().resume(); + REQUIRE(unfinished == 0); + + context.reset(); +} + TEST_CASE("test awaiter_operations - test ranged when_any") { condy::Ring ring; io_uring_params params{}; @@ -207,6 +235,33 @@ TEST_CASE("test awaiter_operations - test ranged when_any") { context.reset(); } +TEST_CASE("test awaiter_operations - test when_any with empty range") { + condy::Ring ring; + io_uring_params params{}; + std::memset(¶ms, 0, sizeof(params)); + ring.init(8, ¶ms); + auto &context = condy::detail::Context::current(); + context.init(&ring, &runtime); + + size_t unfinished = 1; + auto func = [&]() -> condy::Coro { + using OpAwaiter = + decltype(condy::detail::make_op_awaiter(io_uring_prep_nop)); + std::vector awaiters; + REQUIRE_THROWS_AS(co_await condy::when_any(std::move(awaiters)), + std::out_of_range); + --unfinished; + }; + + auto coro = func(); + REQUIRE(unfinished == 1); + + coro.release().resume(); + REQUIRE(unfinished == 0); + + context.reset(); +} + TEST_CASE("test awaiter_operations - test &&") { condy::Ring ring; io_uring_params params{};