Skip to content

Add support for pause & resume of thread-bound items in coroutines#621

Open
jonwis wants to merge 8 commits intomicrosoft:masterfrom
jonwis:user/jonwis/coroutine-watcher
Open

Add support for pause & resume of thread-bound items in coroutines#621
jonwis wants to merge 8 commits intomicrosoft:masterfrom
jonwis:user/jonwis/coroutine-watcher

Conversation

@jonwis
Copy link
Copy Markdown
Member

@jonwis jonwis commented Feb 24, 2026

We saw heap & stack corruptions with code like:

auto watcher = wil::ThreadFailureCallback([](wil::FailureInfo const&) { return false; });
auto q = co_await SomethingAsync();
THROW_HR_IF(E_FAIL, q != 6);

This type and types that use it are inherently thread-bound, and there's no guarantee that the continuation happens on the same thread.

Instead, this new helper lets you say:

auto watcher = wil::ThreadFailureCallback(...);
auto q = co_await wil::with_watcher(watcher, SomethingAsync());
THROW_HR_IF(E_FAIL, q != 6);

... and the awaitable will unhook the watcher on the source thread during suspend and reattach it on the continuation thread before processing exception propagation, etc.

Comment thread cmake/common_build_flags.cmake
Comment thread include/wil/coroutine.h Outdated
Comment thread include/wil/coroutine.h Outdated
@jonwis
Copy link
Copy Markdown
Member Author

jonwis commented Feb 25, 2026

So I'm on these:

cmake version 4.2.3
CMake suite maintained and supported by Kitware (kitware.com/cmake).

clang version 20.1.8
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\Microsoft Visual Studio\18\Professional\VC\Tools\Llvm\x64\bin

Installed just today. I can undo the change to the cmake file - I can't reproduce it building from a vs x64 build window with the scripts or with manual "cmake --preset clang ; cmake --build --preset clang-debug" ...

Comment thread include/wil/coroutine.h
Comment on lines +678 to +687
// Priority tags for SFINAE-based overload resolution
struct get_awaiter_priority_fallback
{
};
struct get_awaiter_priority_free_op : get_awaiter_priority_fallback
{
};
struct get_awaiter_priority_member_op : get_awaiter_priority_free_op
{
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a wil::details::priority_tag<N> I added (moved) somewhat recently. May be better to just re-use it? Higher N gets higher priority.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, oops, it's not in the details namespace

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please fix and use the standard priority_tag<> type instead

Comment thread include/wil/result.h
Comment thread include/wil/coroutine.h

template <typename T>
auto await_suspend(T&& handle) noexcept(
noexcept(std::declval<TChildAwaitable>().await_suspend(std::forward<T>(handle))) && noexcept(pausable.suspend()))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that

Suggested change
noexcept(std::declval<TChildAwaitable>().await_suspend(std::forward<T>(handle))) && noexcept(pausable.suspend()))
noexcept(child_awaitable.await_suspend(std::forward<T>(handle))) && noexcept(pausable.suspend()))

should work... did that give you issues?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really wish noexcept(auto) was a thing 😭

Comment thread include/wil/coroutine.h
return child_awaitable.await_suspend(std::forward<T>(handle));
}

auto await_resume() noexcept(noexcept(std::declval<TChildAwaitable>().await_resume()))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You check noexcept(pausable.suspend()) above, but not noexcept(pausable.resume()) here?

Comment thread include/wil/coroutine.h
auto with_watcher(TWatcher& watcher, TAwaitable&& awaitable)
{
using awaiter_t = std::decay_t<decltype(details::coro::get_awaiter(std::forward<TAwaitable>(awaitable)))>;
return details::coro::coroutine_withsuspend_awaiter<TWatcher, awaiter_t>{watcher, std::forward<TAwaitable>(awaitable)};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this need to call get_awaiter on the awaitable?

Comment thread include/wil/result.h

void resume() WI_NOEXCEPT
{
if (!IsWatching())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly for my own curiosity (the code as-is is probably fine to be safe) - is there a valid scenario where we end up calling resume() when IsWatching() is true? Seems like if that's the case, there was a mis-match with suspend()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants