originate_on_cancel(false) fails on the second co_await due to missed call, and does not work for IAsyncActions. - #1616
Open
antmor wants to merge 2 commits into
Open
Conversation
cancellable_promise exposes both a setter (originate_on_cancel) and a getter (should_originate_on_cancel), but both consumption sites called the setter. Because its parameter defaults to true, each guard evaluation performed std::exchange(m_originate_on_cancel, true): it returned the previous value, so the first check behaved correctly, and then wrote the flag back to true. Every later cancellation on that same promise originated again. A test that cancels only once passes even with the bug present. Three awaiter resume paths also threw hresult_canceled unconditionally, so originate_on_cancel(false) had no effect on them at all: impl::check_status_canceled (reached from await_adapter::await_resume for any coroutine awaiting a WinRT async that completes Canceled), timespan_awaiter::await_resume (resume_after) and signal_awaiter::await_resume (resume_on_signal). cancellable_awaiter now captures the promise's preference in await_suspend, where the promise is known to be alive, and consults it on resume. Capturing at suspend time rather than reaching for the promise at resume time keeps await_adapter::await_resume const. check_status_canceled takes an originate parameter defaulting to true, so wait_get and any external callers keep originating exactly as before. Adds async_originate_count_on_cancel, covering zero, single and repeated cancellation checks plus each of the three awaiter paths. It counts winrt_throw_hresult_handler invocations as a proxy for RoOriginateLanguageException, since observing the latter requires an out-of-process debugger. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ecf487c-b73b-4c7c-940b-98920af404a5
…routine The awaiter captures its preference from the awaiting coroutine's promise, so an async that opts out only suppresses the origination sites it reaches directly. Whoever awaits it makes its own decision. fire_and_forget's promise_type does not derive from cancellable_promise, so a fire_and_forget awaiting an opted-out async still originates from await_adapter::await_resume. It cannot state a preference either, having no await_transform for get_cancellation_token. This is a common shape: a fire_and_forget starter that stores and awaits an annotated IAsyncAction. Giving the awaiting coroutine a cancellable promise is necessary but not sufficient. An IAsyncAction parent that does not itself opt out still originates, because the default is to originate. These cases document current behaviour rather than assert a desired end state. Routing a child's preference to its awaiter needs a way to carry it across the await, which is a larger change than the flag fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ecf487c-b73b-4c7c-940b-98920af404a5
Copilot started reviewing on behalf of
Ryan Shepherd (DefaultRyan)
August 19, 2026 17:51
View session
There was a problem hiding this comment.
Pull request overview
Fixes cancellation origination behavior in C++/WinRT coroutines so cancellation_token::originate_on_cancel(false) reliably suppresses RoOriginate... across repeated cancellation checks and across additional awaiter paths (await_adapter, resume_after, resume_on_signal), including for IAsyncAction scenarios called out in #1617.
Changes:
- Stop consulting the stateful setter
originate_on_cancel(...)for checks; instead consultshould_originate_on_cancel()and thread that preference into cancellation-throw sites. - Capture the “originate on cancel” preference at suspend time in
cancellable_awaiterso resume paths can honor it without reaching back to the promise. - Add a new regression test that counts
winrt_throw_hresult_handlerinvocations as a proxy for origination across multiple cancellation paths and repeated checks.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/test/test.vcxproj | Adds the new cancellation-origination regression test to the test project build. |
| test/test/async_originate_count_on_cancel.cpp | New test coverage for origination suppression across repeated cancellation checks and multiple awaiter resume paths. |
| strings/base_coroutine_threadpool.h | Captures per-promise origination preference at suspend time and applies it in timespan_awaiter / signal_awaiter cancellation throws. |
| strings/base_coroutine_foundation.h | Threads an originate choice into check_status_canceled and uses should_originate_on_cancel() in cancellation sites instead of the setter. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+257
to
+267
| auto async = make(resume.get(), originate); | ||
| async.Completed([&](auto&&, AsyncStatus status) | ||
| { | ||
| REQUIRE(status == expected); | ||
| SetEvent(completed.get()); | ||
| }); | ||
|
|
||
| if (cancel) | ||
| { | ||
| async.Cancel(); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug: #1617
originate_on_canceland gettershould_originate_on_cancel. The await_resume task would calloriginate_on_cancel(the setter) to check whether to RoOriginate the call. This is a bug, because the setter has a side effect. Its parameter defaults to true, so each call does std::exchange(m_originate_on_cancel, true): it returns the previous value, so the first check behaved correctly, and then wrote the flag back to true. Every later cancellation on that same promise originated again. A test that cancels only once passes even with the bug present.These three awaiter resume paths also threw hresult_canceled unconditionally, so originate_on_cancel(false) had no effect on them at all: impl::check_status_canceled (reached from await_adapter::await_resume for any coroutine awaiting a WinRT async that completes Canceled), timespan_awaiter::await_resume (resume_after) and signal_awaiter::await_resume (resume_on_signal).
Fix:
originate_on_cancel, call the getter instead.Test:
Adds async_originate_count_on_cancel, covering zero, single and repeated cancellation checks plus each of the three awaiter paths. It counts winrt_throw_hresult_handler invocations as a proxy for RoOriginateLanguageException, since observing the latter requires an out-of-process debugger.