Skip to content

Commit 70fde17

Browse files
authored
Merge pull request #1 from codeinred/exceptions
Coroutines now rethrow exceptions
2 parents d7c16fd + 61b3092 commit 70fde17

12 files changed

+185
-29
lines changed

CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
1616
set(CONDUIT_TARGET_NAME ${PROJECT_NAME})
1717
set(CONDUIT_INCLUDE_BUILD_DIR "${PROJECT_SOURCE_DIR}/include/")
1818

19+
set(THREADS_PREFER_PTHREAD_FLAG ON)
20+
find_package(Threads REQUIRED)
21+
1922
add_executable(run_tests main.cpp)
2023

24+
target_link_libraries(run_tests PRIVATE Threads::Threads)
25+
26+
27+
2128
add_library(${CONDUIT_TARGET_NAME} INTERFACE)
2229

2330
target_compile_definitions(

include/conduit/continuation.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace conduit::promise {
66
template <class Alloc>
77
struct continuation : mixin::GetReturnObject<continuation<Alloc>, false>,
8-
mixin::UnhandledException<true>,
8+
mixin::UnhandledException<continuation<Alloc>>,
99
mixin::InitialSuspend<mixin::always>,
1010
mixin::FinalSuspend<mixin::never>,
1111
mixin::ReturnVoid,

include/conduit/coroutine.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace conduit::promise {
1515
// and can't be resumed or checked for being done
1616
struct coroutine : mixin::InitialSuspend<false>,
1717
mixin::FinalSuspend<false>,
18-
mixin::UnhandledException<>,
18+
mixin::UnhandledException<coroutine>,
1919
mixin::ReturnVoid {
2020

2121
auto get_return_object() noexcept { return conduit::coroutine{}; }

include/conduit/future.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace conduit::promise {
88
template <class ReturnValue>
99
struct future : mixin::GetReturnObject<future<ReturnValue>>,
1010
mixin::HasOwnerAndCallback,
11-
mixin::UnhandledException<true> {
11+
mixin::UnhandledException<future<ReturnValue>> {
1212
std::optional<ReturnValue> result;
1313
void return_value(ReturnValue const& r) { result.emplace(r); }
1414
void return_value(ReturnValue&& r) { result.emplace(std::move(r)); }
@@ -19,7 +19,7 @@ struct future : mixin::GetReturnObject<future<ReturnValue>>,
1919
template <class ReturnValue>
2020
struct optional_future : mixin::GetReturnObject<optional_future<ReturnValue>>,
2121
mixin::HasOwnerAndCallback,
22-
mixin::UnhandledException<true> {
22+
mixin::UnhandledException<optional_future<ReturnValue>> {
2323
std::optional<ReturnValue> result;
2424

2525
void return_value(ReturnValue const& r) { result.emplace(r); }

include/conduit/generator.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ template <class T>
88
struct generator : mixin::GetReturnObject<generator<T>>,
99
mixin::InitialSuspend<false>,
1010
mixin::FinalSuspend<true>,
11-
mixin::UnhandledException<>,
11+
mixin::UnhandledException<generator<T>>,
1212
mixin::ReturnVoid {
1313
private:
1414
// yielded value stored here

include/conduit/mixin/promise_parts.hpp

+52-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,39 @@
99
namespace conduit::mixin {
1010
enum suspend : bool { always = true, never = false };
1111

12+
#if CONDUIT_USE_GCC_EXCEPTION_WORKAROUND
13+
namespace detail {
14+
using remuse_coro_t = void (*)();
15+
using destroy_coro_t = void (*)();
16+
struct frame_header_t {
17+
remuse_coro_t resume_coro;
18+
destroy_coro_t destroy_coro;
19+
};
20+
} // namespace detail
21+
template <bool suspend>
22+
struct InitialSuspend {
23+
// If CONDUIT_USE_GCC_EXCEPTION_WORKAROUND is defined, then we need to keep
24+
// track of this value in order to destroy the frame manually. This value is
25+
// recorded inside initial_suspend_t
26+
detail::destroy_coro_t destroy_coro = nullptr;
27+
28+
struct initial_suspend_t {
29+
detail::destroy_coro_t& destroy_coro_ref;
30+
31+
inline constexpr bool await_ready() { return false; }
32+
inline bool await_suspend(std::coroutine_handle<> h) {
33+
destroy_coro_ref =
34+
((detail::frame_header_t*)h.address())->destroy_coro;
35+
return suspend; // The coroutine is resumed if suspend is false
36+
}
37+
inline constexpr void await_resume() noexcept {}
38+
};
39+
40+
inline constexpr auto initial_suspend() noexcept {
41+
return initial_suspend_t{destroy_coro};
42+
}
43+
};
44+
#else
1245
template <bool suspend>
1346
struct InitialSuspend {
1447
inline constexpr auto initial_suspend() noexcept {
@@ -19,6 +52,7 @@ struct InitialSuspend {
1952
}
2053
}
2154
};
55+
#endif
2256
template <bool suspend>
2357
struct FinalSuspend {
2458
inline constexpr auto final_suspend() noexcept {
@@ -32,13 +66,25 @@ struct FinalSuspend {
3266
struct ReturnVoid {
3367
inline constexpr void return_void() noexcept {}
3468
};
35-
template <bool IsNoexcept = true>
69+
70+
template <class DerivedPromise>
3671
struct UnhandledException {
37-
[[noreturn]] void unhandled_exception() noexcept { std::terminate(); }
38-
};
39-
template <>
40-
struct UnhandledException<false> {
41-
void unhandled_exception() noexcept {}
72+
void unhandled_exception() {
73+
// NB: for some reason, GCC doesn't destroy the coroutine frame if
74+
// there's an exception raised inside the coroutine. As a result, if
75+
// we're on GCC, we need to destroy it manually.
76+
77+
#ifdef CONDUIT_USE_GCC_EXCEPTION_WORKAROUND
78+
DerivedPromise& promise = static_cast<DerivedPromise&>(*this);
79+
auto coro_frame = static_cast<detail::frame_header_t*>(
80+
std::coroutine_handle<DerivedPromise>::from_promise(promise)
81+
.address());
82+
coro_frame->destroy_coro = promise.destroy_coro;
83+
std::coroutine_handle<>::from_address(coro_frame).destroy();
84+
#endif
85+
86+
std::rethrow_exception(std::current_exception());
87+
}
4288
};
4389
template <class Promise, bool IsNoexcept = true>
4490
struct GetReturnObject;

include/conduit/recursive_generator.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ template <
1818
// Type output by generator
1919
class T>
2020
struct recursive_generator : mixin::InitialSuspend<false>,
21-
mixin::UnhandledException<> {
21+
mixin::UnhandledException<recursive_generator<T>> {
2222
using return_object = conduit::recursive_generator<T>;
2323
using handle_type = std::coroutine_handle<recursive_generator>;
2424
unique_handle<recursive_generator>* sauce = nullptr;

include/conduit/source.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace conduit::promise {
1010
template <class ReturnValue>
1111
struct source : mixin::HasOwnerAndCallback,
1212
mixin::GetReturnObject<source<ReturnValue>>,
13-
mixin::UnhandledException<true> {
13+
mixin::UnhandledException<source<ReturnValue>> {
1414
private:
1515
ReturnValue const* pointer = nullptr;
1616

@@ -32,7 +32,7 @@ struct source : mixin::HasOwnerAndCallback,
3232
template <>
3333
struct source<void> : mixin::HasOwnerAndCallback,
3434
mixin::GetReturnObject<source<void>>,
35-
mixin::UnhandledException<true>,
35+
mixin::UnhandledException<source<void>>,
3636
mixin::ReturnVoid {
3737
public:
3838
auto yield_value(tags::nothing_t) noexcept { return callback.release(); }

include/conduit/task.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace conduit {
66
namespace promise {
77
struct task : mixin::GetReturnObject<task>,
88
mixin::HasOwnerAndCallback,
9-
mixin::UnhandledException<true>,
9+
mixin::UnhandledException<task>,
1010
mixin::ReturnVoid {};
1111
} // namespace promise
1212

include/conduit/util/stdlib_coroutine.hpp

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
#pragma once
22

3+
#if defined(__GNUC__) && !defined(__clang__)
4+
#if __GNUC__ == 10 && __GNUC_MINOR__ <= 2
5+
#define CONDUIT_USE_GCC_EXCEPTION_WORKAROUND 1
6+
#else
7+
#warning "GCC versions 10, 10.1, and 10.2 contained a bug \
8+
that resulted in the coroutine frame not being destroyed \
9+
when an exception was thrown. Conduit implements a work-around \
10+
for this bug that manually destroys the coroutine frame \
11+
when an exception is thrown. This workaround is only enabled \
12+
for these versions of GCC, so future versions of GCC that still \
13+
contain this bug are not accounted for. Please upgrade to a newer \
14+
version of conduit, or contact the maintainer at [email protected] if \
15+
a fix is needed for a newer version of GCC but with the same version of conduit."
16+
#endif
17+
#endif
18+
319
#if __has_include(<coroutine>)
420
#if defined(__clang__) && !defined(__cpp_impl_coroutine)
521
#define __cpp_impl_coroutine 201902L
622
#endif
723
#include <coroutine>
824
925
#ifdef __clang__
10-
namespace std::experimental {
11-
using std::coroutine_handle;
12-
using std::coroutine_traits;
26+
namespace std::experimental {
27+
using std::coroutine_handle;
28+
using std::coroutine_traits;
1329
} // namespace std::experimental
1430
#endif
1531

include/conduit/util/unique_handle.hpp

-5
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ struct unique_handle : private std::coroutine_handle<Promise> {
2424
// Type of the coroutine handle
2525
using handle = std::coroutine_handle<Promise>;
2626

27-
// true if the coroutine always suspends initially
28-
constexpr static bool suspends_initially =
29-
std::is_same_v<std::suspend_always,
30-
decltype(declPromise().initial_suspend())>;
31-
3227
// Returns true if the coroutine handle isn't null
3328
using super::operator bool;
3429
// Checks if the coroutine has finished executing

0 commit comments

Comments
 (0)