Skip to content

Commit

Permalink
解决老中医提出的不能 syncAwait 然后捕获异常的问题。测试用例见 test7
Browse files Browse the repository at this point in the history
简单的来说,就是

```c++
try
{
    start_coro(some_coro_that_throw());
}
catch(...)
{
    这里抓不到异常
}
```

原因是 start_coro 实际上同 asio 的 co_spawn 一样,分叉了。
这样就没办法抓异常了,除非修改some_coro_that_throw()本身的代码。

如果是
```c++
co_await start_coro(some_coro_that_throw());
```

固然这样写,就抓的到异常了。但是在非协程上下文里,就无法使用了。

因此需要一种在非协程上下文环境里,同步等待协程的方式,并且能抓到异常。

所以这个补丁就是新增了一个 sync_await() 函数。具体使用就是

```c++
void 常规函数()
{
    try
    {
        sync_await(some_coro_that_throw());
    }
    catch(...)
    {
        这里能抓到异常
    }
}
```
  • Loading branch information
microcai committed Oct 21, 2024
1 parent 1e36f69 commit 37ab113
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 10 deletions.
96 changes: 86 additions & 10 deletions include/ucoro/awaitable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@ namespace ucoro
template<concepts::awaitable_type AwaitableType>
using awaitable_return_type = template_parameter_of<AwaitableType, awaitable>;

template<typename T>
struct exception_with_result
{
using type = std::variant<std::exception_ptr, T>;
};

template<>
struct exception_with_result<void>
{
using type = std::exception_ptr;
};

template<typename T>
using exception_with_result_t = typename exception_with_result<T>::type;


} // namespace traits

struct debug_coro_promise
Expand Down Expand Up @@ -239,7 +255,7 @@ namespace ucoro
exception_ = std::current_exception();
}

void get_value()
void get_value() const
{
if (exception_)
{
Expand Down Expand Up @@ -431,13 +447,7 @@ namespace ucoro
current_coro_handle_.promise().set_local(std::move(local));
}

auto detach()
{
auto launch_coro = [](awaitable<T> lazy) -> awaitable<T> { co_return co_await std::move(lazy); };
return launch_coro(std::move(*this));
}

auto detach(std::any local)
auto detach(std::any local = {})
{
auto launched_coro = [](awaitable<T> lazy) mutable -> awaitable<T>
{
Expand All @@ -452,6 +462,44 @@ namespace ucoro
return launched_coro;
}

template<typename Function> requires std::is_invocable_v<Function, ucoro::traits::exception_with_result_t<T>>
auto detach_with_callback(Function completion_handler)
{
return detach_with_callback<Function>(std::any{}, completion_handler);
}

template<typename Function> requires std::is_invocable_v<Function, ucoro::traits::exception_with_result_t<T>>
auto detach_with_callback(std::any local, Function completion_handler)
{
auto launched_coro = [](awaitable<T> lazy, auto completion_handler) mutable -> awaitable<void>
{
using result_wrapper = ucoro::traits::exception_with_result_t<T>;
try
{
if constexpr (std::is_void_v<T>)
{
co_await std::move(lazy);
completion_handler(result_wrapper{nullptr});
}
else
{
completion_handler(result_wrapper{co_await std::move(lazy)});
}
}
catch(...)
{
completion_handler(result_wrapper{std::current_exception()});
}
}(std::move(*this), std::move(completion_handler));

if (local.has_value())
{
launched_coro.set_local(local);
}

return launched_coro;
}

std::coroutine_handle<promise_type> current_coro_handle_;
};

Expand All @@ -463,6 +511,7 @@ namespace ucoro
auto result = awaitable<T>{std::coroutine_handle<awaitable_promise<T>>::from_promise(*this)};
return result;
}

} // namespace ucoro

//////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -598,9 +647,9 @@ namespace ucoro
//////////////////////////////////////////////////////////////////////////

template<typename T, typename callback>
ucoro::CallbackAwaiter<T, callback> callback_awaitable(callback&& cb)
auto callback_awaitable(callback&& cb) -> ucoro::awaitable<T>
{
return ucoro::CallbackAwaiter<T, callback>{std::forward<callback>(cb)};
co_return co_await ucoro::CallbackAwaiter<T, callback>{std::forward<callback>(cb)};
}

template<typename Awaitable, typename Local>
Expand All @@ -614,3 +663,30 @@ auto coro_start(Awaitable&& coro)
{
return coro.detach();
}

template<typename T>
auto sync_await(ucoro::awaitable<T> lazy, std::any local_ = {}) -> T
{
ucoro::traits::exception_with_result_t<T> result;

lazy.detach_with_callback(local_, [&](ucoro::traits::exception_with_result_t<T> result_)
{
result = result_;
});

if constexpr (std::is_void_v<T>)
{
if (result)
{
std::rethrow_exception(result);
}
}
else
{
if (std::holds_alternative<std::exception_ptr>(result))
{
std::rethrow_exception(std::get<std::exception_ptr>(result));
}
return std::get<T>(result);
}
}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set_target_properties(ucoro_tests PROPERTIES FOLDER "ucoro_tests")

add_subdirectory(test1)
add_subdirectory(test2)
add_subdirectory(test7)

find_package(Boost 1.60 COMPONENTS thread system atomic)
if(Boost_FOUND)
Expand Down
6 changes: 6 additions & 0 deletions tests/test7/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

add_executable(test7 test.cpp)
target_link_libraries(test7 ucoro)

add_test(NAME test7 COMMAND test7)
set_target_properties(test7 PROPERTIES FOLDER "ucoro_tests")
45 changes: 45 additions & 0 deletions tests/test7/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

#include "ucoro/awaitable.hpp"
#include <iostream>

ucoro::awaitable<int> test()
{
throw std::runtime_error("test throw");
co_return 1;
}


ucoro::awaitable<void> test2()
{
throw std::runtime_error("test throw");
co_return;
}

ucoro::awaitable<int> coro_compute()
{
try
{
sync_await(test2());
}
catch(const std::exception& e)
{
std::cerr << "exception in test2: " << e.what() << '\n';
}

co_return co_await test();
}

int main(int argc, char** argv)
{
try
{
std::string str = "hello";
sync_await(coro_compute(), str);
}
catch (std::exception& e)
{
std::cerr << "exception: " << e.what() << std::endl;
}

return 0;
}

0 comments on commit 37ab113

Please sign in to comment.