Skip to content

Commit 5d3ba11

Browse files
committed
Test exception handling in connect_awaitable
1 parent 44625a7 commit 5d3ba11

2 files changed

Lines changed: 247 additions & 22 deletions

File tree

include/stdexec/__detail/__connect_awaitable.hpp

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -127,23 +127,24 @@ namespace STDEXEC
127127
, __awaiter_(__get_awaiter(static_cast<__awaitable_t&&>(__awaitable_)))
128128
{}
129129

130-
[[no_unique_addres]]
130+
[[no_unique_address]]
131131
__awaitable_t __awaitable_;
132-
[[no_unique_addres]]
132+
[[no_unique_address]]
133133
__awaiter_t __awaiter_;
134134
};
135135

136-
[[no_unique_addres]]
136+
[[no_unique_address]]
137137
_Awaitable __source_awaitable_;
138138
union
139139
{
140-
[[no_unique_addres]]
140+
[[no_unique_address]]
141141
__state __awaiter_;
142142
};
143143

144144
template <class _A>
145145
requires(!std::same_as<std::remove_cvref_t<_A>, __awaitable_state>)
146-
__awaitable_state(_A&& __awaitable) noexcept(__nothrow_constructible_from<_Awaitable, _A>)
146+
explicit __awaitable_state(_A&& __awaitable)
147+
noexcept(__nothrow_constructible_from<_Awaitable, _A>)
147148
: __source_awaitable_(static_cast<_A&&>(__awaitable))
148149
{}
149150

@@ -186,21 +187,22 @@ namespace STDEXEC
186187
STDEXEC_ASSERT(std::addressof(__awaitable) == std::addressof(__source));
187188
}
188189

189-
[[no_unique_addres]]
190+
[[no_unique_address]]
190191
__awaiter_t __awaiter_;
191192
};
192193

193-
[[no_unique_addres]]
194+
[[no_unique_address]]
194195
_Awaitable __source_awaitable_;
195196
union
196197
{
197-
[[no_unique_addres]]
198+
[[no_unique_address]]
198199
__state __awaiter_;
199200
};
200201

201202
template <class _A>
202203
requires(!std::same_as<std::remove_cvref_t<_A>, __awaitable_state>)
203-
__awaitable_state(_A&& __awaitable) noexcept(__nothrow_constructible_from<_Awaitable, _A>)
204+
explicit __awaitable_state(_A&& __awaitable)
205+
noexcept(__nothrow_constructible_from<_Awaitable, _A>)
204206
: __source_awaitable_(static_cast<_A&&>(__awaitable))
205207
{}
206208

@@ -241,21 +243,22 @@ namespace STDEXEC
241243
STDEXEC_ASSERT(std::addressof(__awaiter) == std::addressof(__awaiter_));
242244
}
243245

244-
[[no_unique_addres]]
246+
[[no_unique_address]]
245247
__awaiter_t __awaiter_;
246248
};
247249

248-
[[no_unique_addres]]
250+
[[no_unique_address]]
249251
_Awaitable __source_awaitable_;
250252
union
251253
{
252-
[[no_unique_addres]]
254+
[[no_unique_address]]
253255
__state __awaiter_;
254256
};
255257

256258
template <class _A>
257259
requires(!std::same_as<std::remove_cvref_t<_A>, __awaitable_state>)
258-
__awaitable_state(_A&& __awaitable) noexcept(__nothrow_constructible_from<_Awaitable, _A>)
260+
explicit __awaitable_state(_A&& __awaitable)
261+
noexcept(__nothrow_constructible_from<_Awaitable, _A>)
259262
: __source_awaitable_(static_cast<_A&&>(__awaitable))
260263
{}
261264

@@ -281,12 +284,13 @@ namespace STDEXEC
281284
{
282285
// _Awaitable has neither a distinct awaiter, nor a distinct awaitable
283286
// so we don't need separate storage for either
284-
[[no_unique_addres]]
287+
[[no_unique_address]]
285288
_Awaitable __awaiter_;
286289

287290
template <class _A>
288291
requires(!std::same_as<std::remove_cvref_t<_A>, __awaitable_state>)
289-
__awaitable_state(_A&& __awaitable) noexcept(__nothrow_constructible_from<_Awaitable, _A>)
292+
explicit __awaitable_state(_A&& __awaitable)
293+
noexcept(__nothrow_constructible_from<_Awaitable, _A>)
290294
: __awaiter_(static_cast<_A&&>(__awaitable))
291295
{}
292296

@@ -470,11 +474,11 @@ namespace STDEXEC
470474
void start() & noexcept
471475
{
472476
auto __coro = __co_impl(*this);
473-
__started_ = true;
474477

475478
STDEXEC_TRY
476479
{
477480
__awaiter_.construct(__coro);
481+
__started_ = true;
478482

479483
if (!__awaiter_.await_ready())
480484
{
@@ -576,9 +580,9 @@ namespace STDEXEC
576580
alignas(__storage_align) std::byte __storage_[__storage_size];
577581
[[no_unique_address]]
578582
bool __started_{false};
579-
[[no_unique_addres]]
583+
[[no_unique_address]]
580584
_Receiver __rcvr_;
581-
[[no_unique_addres]]
585+
[[neo_unique_addres]]
582586
__awaitable_state<_Awaitable, __promise_t> __awaiter_;
583587
};
584588
} // namespace __connect_await

test/stdexec/cpos/test_cpo_connect_awaitable.cpp

Lines changed: 225 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <catch2/catch.hpp>
2121
#include <stdexec/execution.hpp>
2222

23+
#include <stdexcept>
2324
#include <type_traits>
2425

2526
namespace ex = STDEXEC;
@@ -140,13 +141,13 @@ namespace
140141
awaitable_ref(Awaitable&) -> awaitable_ref<Awaitable>;
141142

142143
template <class Awaitable>
143-
requires requires(Awaitable& a) {
144-
{ operator co_await(a) };
144+
requires requires(Awaitable&& a) {
145+
{ operator co_await(std::move(a)) };
145146
}
146147
constexpr auto operator co_await(awaitable_ref<Awaitable> ref)
147-
noexcept(noexcept(operator co_await(*ref.awaitable_)))
148+
noexcept(noexcept(operator co_await(std::move(*ref.awaitable_))))
148149
{
149-
return operator co_await(*ref.awaitable_);
150+
return operator co_await(std::move(*ref.awaitable_));
150151
}
151152

152153
template <class T>
@@ -487,4 +488,224 @@ namespace
487488
with_as_awaitable<with_friend_co_await<symmetrically_suspending_awaitable<void>>>(false));
488489
}
489490
}
491+
492+
TEST_CASE("exceptions thrown from await_ready are reported to set_error",
493+
"[cpo][cpo_connect_awaitable]")
494+
{
495+
struct throw_on_ready : ready_awaitable<void>
496+
{
497+
static bool await_ready()
498+
{
499+
throw std::runtime_error("not ready!");
500+
}
501+
};
502+
503+
auto op = ex::connect(throw_on_ready{}, expect_error_receiver{});
504+
op.start();
505+
}
506+
507+
TEST_CASE("exceptions thrown from void-returning await_suspend are reported to set_error",
508+
"[cpo][cpo_connect_awaitable]")
509+
{
510+
struct throw_on_suspend : suspending_awaitable<void>
511+
{
512+
static void await_suspend(std::coroutine_handle<>)
513+
{
514+
throw std::runtime_error("do not suspend!");
515+
}
516+
} awaiter;
517+
518+
auto op = ex::connect(awaitable_ref{awaiter}, expect_error_receiver{});
519+
op.start();
520+
}
521+
522+
TEST_CASE("exceptions thrown from bool-returning await_suspend are reported to set_error",
523+
"[cpo][cpo_connect_awaitable]")
524+
{
525+
struct throw_on_suspend : suspending_awaitable<void>
526+
{
527+
static bool await_suspend(std::coroutine_handle<>)
528+
{
529+
throw std::runtime_error("do not suspend!");
530+
}
531+
} awaiter;
532+
533+
auto op = ex::connect(awaitable_ref{awaiter}, expect_error_receiver{});
534+
op.start();
535+
}
536+
537+
TEST_CASE("exceptions thrown from handle-returning await_suspend are reported to set_error",
538+
"[cpo][cpo_connect_awaitable]")
539+
{
540+
struct throw_on_suspend : suspending_awaitable<void>
541+
{
542+
static std::coroutine_handle<> await_suspend(std::coroutine_handle<>)
543+
{
544+
throw std::runtime_error("do not suspend!");
545+
}
546+
} awaiter;
547+
548+
auto op = ex::connect(awaitable_ref{awaiter}, expect_error_receiver{});
549+
op.start();
550+
}
551+
552+
TEST_CASE("exceptions thrown from immediately-invoked await_resume are reported to set_error",
553+
"[cpo][cpo_connect_awaitable]")
554+
{
555+
{
556+
struct throw_on_void_resume : ready_awaitable<void>
557+
{
558+
static void await_resume()
559+
{
560+
throw std::runtime_error("no result for you!");
561+
}
562+
};
563+
564+
auto op = ex::connect(throw_on_void_resume{}, expect_error_receiver{});
565+
op.start();
566+
}
567+
{
568+
struct throw_on_int_resume : ready_awaitable<void>
569+
{
570+
static int await_resume()
571+
{
572+
throw std::runtime_error("no result for you!");
573+
}
574+
};
575+
576+
auto op = ex::connect(throw_on_int_resume{}, expect_error_receiver{});
577+
op.start();
578+
}
579+
}
580+
581+
TEST_CASE("exceptions thrown from deferred-invoked await_resume are reported to set_error",
582+
"[cpo][cpo_connect_awaitable]")
583+
{
584+
{
585+
{
586+
struct throw_on_void_resume : suspending_awaitable<void>
587+
{
588+
static void await_resume()
589+
{
590+
throw std::runtime_error("no result for you!");
591+
}
592+
} awaitable;
593+
594+
auto op = ex::connect(awaitable_ref{awaitable}, expect_error_receiver{});
595+
op.start();
596+
awaitable.resume_parent();
597+
}
598+
{
599+
struct throw_on_int_resume : suspending_awaitable<void>
600+
{
601+
static int await_resume()
602+
{
603+
throw std::runtime_error("no result for you!");
604+
}
605+
} awaitable;
606+
607+
auto op = ex::connect(awaitable_ref{awaitable}, expect_error_receiver{});
608+
op.start();
609+
awaitable.resume_parent();
610+
}
611+
}
612+
}
613+
614+
template <template <class> class Wrapper = std::type_identity_t>
615+
struct throw_on_get_awaitable
616+
{
617+
template <class Promise>
618+
Wrapper<ready_awaitable<void>> as_awaitable(Promise&)
619+
{
620+
throw std::runtime_error("no awaitable for you!");
621+
}
622+
};
623+
624+
TEST_CASE("exceptions thrown from __get_awaitable are reported to set_error",
625+
"[cpo][cpo_connect_awaitable]")
626+
{
627+
{
628+
auto op = ex::connect(throw_on_get_awaitable{}, expect_error_receiver{});
629+
op.start();
630+
}
631+
632+
{
633+
auto op = ex::connect(throw_on_get_awaitable<with_member_co_await>{},
634+
expect_error_receiver{});
635+
op.start();
636+
}
637+
638+
{
639+
auto op = ex::connect(throw_on_get_awaitable<with_friend_co_await>{},
640+
expect_error_receiver{});
641+
op.start();
642+
}
643+
}
644+
645+
TEST_CASE("exceptions thrown from member operator co_await are reported to set_error",
646+
"[cpo][cpo_connect_awaitable]")
647+
{
648+
struct throw_on_co_await
649+
{
650+
ready_awaitable<void> operator co_await()
651+
{
652+
throw std::runtime_error("no awaitable for you!");
653+
}
654+
};
655+
656+
{
657+
auto op = ex::connect(throw_on_co_await{}, expect_error_receiver{});
658+
op.start();
659+
}
660+
661+
{
662+
auto op = ex::connect(with_as_awaitable<throw_on_co_await>{}, expect_error_receiver{});
663+
op.start();
664+
}
665+
}
666+
667+
struct throw_on_co_await
668+
{
669+
friend ready_awaitable<void> operator co_await(throw_on_co_await&&)
670+
{
671+
throw std::runtime_error("no awaitable for you!");
672+
}
673+
};
674+
675+
TEST_CASE("exceptions thrown from friend operator co_await are reported to set_error",
676+
"[cpo][cpo_connect_awaitable]")
677+
{
678+
{
679+
auto op = ex::connect(throw_on_co_await{}, expect_error_receiver{});
680+
op.start();
681+
}
682+
683+
{
684+
auto op = ex::connect(with_as_awaitable<throw_on_co_await>{}, expect_error_receiver{});
685+
op.start();
686+
}
687+
}
688+
689+
struct stop_on_suspend
690+
{
691+
static constexpr bool await_ready() noexcept
692+
{
693+
return false;
694+
}
695+
696+
template <class Promise>
697+
static constexpr auto
698+
await_suspend(std::coroutine_handle<Promise> coro) noexcept -> std::coroutine_handle<>
699+
{
700+
return coro.promise().unhandled_stopped();
701+
}
702+
703+
static constexpr void await_resume() noexcept {}
704+
};
705+
706+
TEST_CASE("promise().unhandled_stopped() invokes set_stopped", "[cpo][cpo_connect_awaitable]")
707+
{
708+
auto op = ex::connect(stop_on_suspend{}, expect_stopped_receiver{});
709+
op.start();
710+
}
490711
} // namespace

0 commit comments

Comments
 (0)