Workaround MSVC bug that breaks when_all() and sync_wait().
MSVC 2017.8 generates bad code for when_all() and sync_wait() that causes a crash due to it resuming the coroutine at the wrong suspend-point in the expression 'co_yield co_await x'. Work around this by manually calling promise.yield_value() instead of using co_yield.
This commit is contained in:
parent
868c0f427d
commit
3864a047db
|
@ -66,6 +66,39 @@ namespace cppcoro
|
|||
return completion_notifier{};
|
||||
}
|
||||
|
||||
#if CPPCORO_COMPILER_MSVC
|
||||
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
|
||||
// See comment in make_sync_wait_task below.
|
||||
template<typename Awaitable>
|
||||
Awaitable&& await_transform(Awaitable&& awaitable)
|
||||
{
|
||||
return static_cast<Awaitable&&>(awaitable);
|
||||
}
|
||||
|
||||
struct get_promise_t {};
|
||||
static constexpr get_promise_t get_promise = {};
|
||||
|
||||
auto await_transform(get_promise_t)
|
||||
{
|
||||
class awaiter
|
||||
{
|
||||
public:
|
||||
awaiter(sync_wait_task_promise* promise) noexcept : m_promise(promise) {}
|
||||
bool await_ready() noexcept {
|
||||
return true;
|
||||
}
|
||||
void await_suspend(std::experimental::coroutine_handle<>) noexcept {}
|
||||
sync_wait_task_promise& await_resume() noexcept
|
||||
{
|
||||
return *m_promise;
|
||||
}
|
||||
private:
|
||||
sync_wait_task_promise* m_promise;
|
||||
};
|
||||
return awaiter{ this };
|
||||
}
|
||||
#endif
|
||||
|
||||
auto yield_value(reference result) noexcept
|
||||
{
|
||||
m_result = std::addressof(result);
|
||||
|
@ -221,7 +254,15 @@ namespace cppcoro
|
|||
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||
sync_wait_task<RESULT> make_sync_wait_task(AWAITABLE& awaitable)
|
||||
{
|
||||
co_yield co_await std::forward<AWAITABLE>(awaitable);
|
||||
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
|
||||
// to completely ignore the co_yield an never calls promise.yield_value().
|
||||
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
|
||||
// rather than before the 'co_yield'.
|
||||
// This bug is present in VS 2017.7 and VS 2017.8.
|
||||
auto& promise = co_await sync_wait_task_promise<RESULT>::get_promise;
|
||||
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
|
||||
|
||||
//co_yield co_await std::forward<AWAITABLE>(awaitable);
|
||||
}
|
||||
|
||||
template<
|
||||
|
@ -230,7 +271,7 @@ namespace cppcoro
|
|||
std::enable_if_t<std::is_void_v<RESULT>, int> = 0>
|
||||
sync_wait_task<void> make_sync_wait_task(AWAITABLE& awaitable)
|
||||
{
|
||||
co_await std::forward<AWAITABLE>(awaitable);
|
||||
co_await static_cast<AWAITABLE&&>(awaitable);
|
||||
}
|
||||
#else
|
||||
template<
|
||||
|
|
|
@ -76,6 +76,40 @@ namespace cppcoro
|
|||
assert(false);
|
||||
}
|
||||
|
||||
#if CPPCORO_COMPILER_MSVC
|
||||
// HACK: This is needed to work around a bug in MSVC 2017.7/2017.8.
|
||||
// See comment in make_when_all_task below.
|
||||
template<typename Awaitable>
|
||||
Awaitable&& await_transform(Awaitable&& awaitable)
|
||||
{
|
||||
return static_cast<Awaitable&&>(awaitable);
|
||||
}
|
||||
|
||||
struct get_promise_t {};
|
||||
static constexpr get_promise_t get_promise = {};
|
||||
|
||||
auto await_transform(get_promise_t)
|
||||
{
|
||||
class awaiter
|
||||
{
|
||||
public:
|
||||
awaiter(when_all_task_promise* promise) noexcept : m_promise(promise) {}
|
||||
bool await_ready() noexcept {
|
||||
return true;
|
||||
}
|
||||
void await_suspend(std::experimental::coroutine_handle<>) noexcept {}
|
||||
when_all_task_promise& await_resume() noexcept
|
||||
{
|
||||
return *m_promise;
|
||||
}
|
||||
private:
|
||||
when_all_task_promise* m_promise;
|
||||
};
|
||||
return awaiter{ this };
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
auto yield_value(RESULT&& result) noexcept
|
||||
{
|
||||
m_result = std::addressof(result);
|
||||
|
@ -267,7 +301,17 @@ namespace cppcoro
|
|||
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||
when_all_task<RESULT> make_when_all_task(AWAITABLE awaitable)
|
||||
{
|
||||
#if CPPCORO_COMPILER_MSVC
|
||||
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
|
||||
// to completely ignore the co_yield an never calls promise.yield_value().
|
||||
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
|
||||
// rather than before the 'co_yield'.
|
||||
// This bug is present in VS 2017.7 and VS 2017.8.
|
||||
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
|
||||
co_await promise.yield_value(co_await std::forward<AWAITABLE>(awaitable));
|
||||
#else
|
||||
co_yield co_await static_cast<AWAITABLE&&>(awaitable);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<
|
||||
|
@ -285,7 +329,17 @@ namespace cppcoro
|
|||
std::enable_if_t<!std::is_void_v<RESULT>, int> = 0>
|
||||
when_all_task<RESULT> make_when_all_task(std::reference_wrapper<AWAITABLE> awaitable)
|
||||
{
|
||||
#if CPPCORO_COMPILER_MSVC
|
||||
// HACK: Workaround another bug in MSVC where the expression 'co_yield co_await x' seems
|
||||
// to completely ignore the co_yield an never calls promise.yield_value().
|
||||
// The coroutine seems to be resuming the 'co_await' after the 'co_yield'
|
||||
// rather than before the 'co_yield'.
|
||||
// This bug is present in VS 2017.7 and VS 2017.8.
|
||||
auto& promise = co_await when_all_task_promise<RESULT>::get_promise;
|
||||
co_await promise.yield_value(co_await awaitable.get());
|
||||
#else
|
||||
co_yield co_await awaitable.get();
|
||||
#endif
|
||||
}
|
||||
|
||||
template<
|
||||
|
|
|
@ -21,7 +21,8 @@ namespace cppcoro
|
|||
#if CPPCORO_COMPILER_MSVC
|
||||
// HACK: Need to explicitly specify template argument to make_sync_wait_task
|
||||
// here to work around a bug in MSVC when passing parameters by universal
|
||||
// reference to
|
||||
// reference to a coroutine which causes the compiler to think it needs to
|
||||
// 'move' parameters passed by rvalue reference.
|
||||
auto task = detail::make_sync_wait_task<AWAITABLE>(awaitable);
|
||||
#else
|
||||
auto task = detail::make_sync_wait_task(std::forward<AWAITABLE>(awaitable));
|
||||
|
|
Loading…
Reference in a new issue