summaryrefslogtreecommitdiffstats
path: root/library/cpp/threading
diff options
context:
space:
mode:
authorthegeorg <[email protected]>2025-07-15 18:16:09 +0300
committerthegeorg <[email protected]>2025-07-15 18:32:12 +0300
commitca650d2b24f88ba1c019c73b940e1e36d4bdec3f (patch)
tree919c5ccf30fb90980a624bf3bdf547794b3b5876 /library/cpp/threading
parentb88aa58c4c0ab58d67b96c80573946c58f3ffaa2 (diff)
coroutine_traits: Implicitly convert returned std::exception into erroneous TFuture
commit_hash:80a673361a58719a241d3536cd4ffdd7d1a274ea
Diffstat (limited to 'library/cpp/threading')
-rw-r--r--library/cpp/threading/future/core/coroutine_traits.h16
-rw-r--r--library/cpp/threading/future/ut_gtest/coroutine_traits_ut.cpp94
2 files changed, 89 insertions, 21 deletions
diff --git a/library/cpp/threading/future/core/coroutine_traits.h b/library/cpp/threading/future/core/coroutine_traits.h
index 14c5539eae0..502c5f8eb60 100644
--- a/library/cpp/threading/future/core/coroutine_traits.h
+++ b/library/cpp/threading/future/core/coroutine_traits.h
@@ -45,6 +45,12 @@ struct std::coroutine_traits<NThreading::TFuture<void>, Args...> {
template <typename T, typename... Args>
struct std::coroutine_traits<NThreading::TFuture<T>, Args...> {
struct promise_type {
+
+ static_assert(
+ !std::derived_from<T, std::exception>,
+ "TFuture<std::exception can not be used in coroutines"
+ );
+
NThreading::TFuture<T> get_return_object() noexcept {
return NThreading::TFuture<T>(State_);
}
@@ -69,6 +75,16 @@ struct std::coroutine_traits<NThreading::TFuture<T>, Args...> {
Y_ASSERT(success && "value already set");
}
+ template <typename E> requires std::derived_from<E, std::exception>
+ void return_value(E&& err) {
+ // Allow co_return std::exception instances in order to avoid stack unwinding
+ bool success = State_->TrySetException(
+ std::make_exception_ptr(std::move(err)),
+ /* deferCallbacks */ true
+ );
+ Y_ASSERT(success && "value already set");
+ }
+
void return_value(auto&& val) {
bool success = State_->TrySetValue(std::forward<decltype(val)>(val), /* deferCallbacks */ true);
Y_ASSERT(success && "value already set");
diff --git a/library/cpp/threading/future/ut_gtest/coroutine_traits_ut.cpp b/library/cpp/threading/future/ut_gtest/coroutine_traits_ut.cpp
index 4b3c6135a53..75de1d1c7fe 100644
--- a/library/cpp/threading/future/ut_gtest/coroutine_traits_ut.cpp
+++ b/library/cpp/threading/future/ut_gtest/coroutine_traits_ut.cpp
@@ -84,50 +84,102 @@ TEST(TestFutureTraits, Simple) {
);
}
-TEST(TestFutureTraits, Exception) {
+TEST(TestFutureTraits, ErrorViaThrow) {
TVector<TString> result;
- auto coroutine1 = [&result]() -> NThreading::TFuture<size_t> {
- result.push_back("coroutine1");
+ auto coroutineReturnValue = [&result]() -> NThreading::TFuture<size_t> {
+ result.push_back("coroutine_return_value");
co_return 1;
};
- auto coroutine2 = [&result]() -> NThreading::TFuture<size_t> {
- result.push_back("coroutine2");
- ythrow yexception() << "coroutine2 exception";
+ auto coroutineThrow = [&result]() -> NThreading::TFuture<size_t> {
+ result.push_back("coroutine_throw");
+ ythrow yexception() << "coroutine exception";
};
- auto coroutineAll = [&]() -> NThreading::TFuture<size_t> {
+ auto coroutineReturnValueThrow = [&]() -> NThreading::TFuture<size_t> {
Y_DEFER {
result.push_back("coroutine_all_destroy");
};
- result.push_back("pre_coroutine1");
- size_t coroutine1Res = co_await coroutine1();
- result.push_back("post_coroutine1");
+ result.push_back("pre_coroutine_return_value");
+ size_t res1 = co_await coroutineReturnValue();
+ result.push_back("post_coroutine_return_value");
- result.push_back("pre_coroutine2");
- size_t coroutine2Res = co_await coroutine2();
- result.push_back("post_coroutine2");
+ result.push_back("pre_coroutine_throw");
+ size_t res2 = co_await coroutineThrow();
+ result.push_back("post_coroutine_throw");
- co_return coroutine1Res + coroutine2Res;
+ co_return res1 + res2;
};
EXPECT_THROW_MESSAGE_HAS_SUBSTR(
- coroutineAll().GetValueSync(),
+ coroutineReturnValueThrow().GetValueSync(),
yexception,
- "coroutine2 exception"
+ "coroutine exception"
);
EXPECT_THAT(
result,
::testing::ContainerEq(
TVector<TString>({
- "pre_coroutine1",
- "coroutine1",
- "post_coroutine1",
+ "pre_coroutine_return_value",
+ "coroutine_return_value",
+ "post_coroutine_return_value",
- "pre_coroutine2",
- "coroutine2",
+ "pre_coroutine_throw",
+ "coroutine_throw",
+
+ "coroutine_all_destroy"
+ })
+ )
+ );
+}
+
+TEST(TestFutureTraits, ErrorViaReturnException) {
+
+ TVector<TString> result;
+
+ auto coroutineReturnValue = [&result]() -> NThreading::TFuture<size_t> {
+ result.push_back("coroutine_return_value");
+ co_return 1;
+ };
+
+ auto coroutineReturnException = [&result]() -> NThreading::TFuture<size_t> {
+ result.push_back("coroutine_return_exception");
+ co_return std::runtime_error("exception_to_return");
+ };
+
+ auto coroutineReturnValueReturnException = [&]() -> NThreading::TFuture<size_t> {
+ Y_DEFER {
+ result.push_back("coroutine_all_destroy");
+ };
+
+ result.push_back("pre_coroutine_return_value");
+ size_t res1 = co_await coroutineReturnValue();
+ result.push_back("post_coroutine_return_value");
+
+ result.push_back("pre_coroutine_return_exception");
+ size_t res2 = co_await coroutineReturnException();
+ result.push_back("post_coroutine_return_exception");
+
+ co_return res1 + res2;
+ };
+
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(
+ coroutineReturnValueReturnException().GetValueSync(),
+ std::runtime_error,
+ "exception_to_return"
+ );
+ EXPECT_THAT(
+ result,
+ ::testing::ContainerEq(
+ TVector<TString>({
+ "pre_coroutine_return_value",
+ "coroutine_return_value",
+ "post_coroutine_return_value",
+
+ "pre_coroutine_return_exception",
+ "coroutine_return_exception",
"coroutine_all_destroy"
})