-
Notifications
You must be signed in to change notification settings - Fork 264
Resuming neutral context from STA should force background thread #662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The neutral apartment is not a thread, but rather
steals whatever thread you happen to be using
by temporarily converting it to a neutral apartment,
and then restoring the original apartment when it's done.
COM will secretly create neutral apartments for issuing
callbacks, and code may find itself inadvertently capturing
a neutral apartment when it thought it was capturing an MTA:
```cpp
fire_and_forget OnSomething()
{
auto previous = apartment_context();
co_await resume_foreground(dispatcher);
... do UI stuff ...
co_await previous;
... do slow stuff ...
}
```
If this code is called from a neutral apartment
on a borrowed MTA thread, the `co_await previous`
will switch to the neutral apartment while staying
on the UI thread, even though the code started on an
MTA thread. This is surprising behavior that has been
identified as the root cause of some hangs.
This change revises C++/WinRT to minimize the surprise
by always resuming neutral apartments on an MTA thread.
Previously, the `apartment_context` worried about only
two causes (STA and MTA), and it encoded the MTA as
a null pointer. Now, we have three cases to worry about,
so the trick of encoding the MTA as `nullptr` is not
going to work. The `apartment_context` now needs to
capture both the `IContextCallback` as well as the
apartment type of that context.
Explicitly carrying the apartment type removes the
need for the special `nullptr` sentinel value, so we
get rid of it.
Switching to a neutral apartment from an STA now
switches explicitly to an MTA thread before converting
the MTA thread to a neutral apartment. In other wirds,
what used to be
```cpp
co_await resume_on_nta;
```
is now, conceptually,
```cpp
co_await resume_background();
co_await resume_on_nta;
```
Implementing this "extra `co_await`" is not as pretty
as it sounds because we cannot use the coroutine machinery
to do the extra `co_await`. We have to code it up
explicitly.
New internal function `get_apartment_type()` returns a
`std::pair` of the apartment type and apartment type
qualifier. If COM is not initialized, then we pretend that
we are in the implicit MTA.
Internal function `impl::is_sta()` is renamed to
`impl::is_sta_thread()` since it now checks the underlying
thread, in addition to than the current apartment: It returns
true for an STA apartment, or for a neutral apartment
running on a borrowed STA thread.
Removed dependency of unit test on now-defunct internal function
`impl::is_sta()`.
Also, switch to an if/else chain in `resume_apartment`, since it actually reads pretty cleanly. * If COM is not initialized (`m_context` is null) or we are in the correct context already, then call the continuation directly. * If the destination is the MTA, then use a threadpool thread. * If the destination is the NA, and we are on an STA thread, then resume on a threadpool thread. * Otherwise resume synchronously.
|
The revised |
This is the crux, avoiding the problem of converting an STA to an NA. Emphasize this point. |
ChrisGuzak
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great work... thanks for doing this.
| } | ||
|
|
||
| inline bool is_sta() noexcept | ||
| inline std::pair<int32_t, int32_t> get_apartment_type() noexcept |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider mentioning the results hold APTTYPE_* and APTTYPEQUALIFIER_* values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already mentioned in the PR description.
|
/azp run |
|
No pipelines are associated with this pull request. |
The neutral apartment is not a thread, but rather steals whatever thread you happen to be using by temporarily converting it to a neutral apartment, and then restoring the original apartment when it's done.
COM will secretly create neutral apartments for issuing callbacks, and code may find itself inadvertently capturing a neutral apartment when it thought it was capturing an MTA:
If this code is called from a neutral apartment on a borrowed MTA thread, the final
co_await previouswill switch to the neutral apartment while staying on the UI thread, even though the code started on an MTA thread. This is surprising behavior that has been identified as the root cause of some hangs.This change revises C++/WinRT to minimize the surprise by resuming neutral apartments on an MTA thread if coming from an STA thread.
Previously, the
apartment_contextworried about only two causes (STA and MTA), and it encoded the MTA as a null pointer. Now, we have three cases to worry about, so the trick of encoding the MTA asnullptris not going to work. Theapartment_contextnow needs to capture both theIContextCallbackas well as the apartment type of that context.Explicitly carrying the apartment type removes the need for the special
nullptrsentinel value, so we get rid of it.Switching to a neutral apartment from an STA now switches explicitly to an MTA thread before converting the MTA thread to a neutral apartment. In other words, what used to be
co_await resume_on_nta;is now, conceptually,
Implementing this "extra
co_await" is not as pretty as it sounds because we cannot use the coroutine machinery to do the extraco_await. We have to code it up explicitly.New internal function
get_apartment_type()returns astd::pairof the apartment type and apartment type qualifier. If COM is not initialized, then we pretend that we are in the implicit MTA.Internal function
impl::is_sta()is renamed toimpl::is_sta_thread()since it now checks the apartment of the underlying thread, even if the current apartment is neutral.Removed dependency of unit test on now-defunct internal function
impl::is_sta().This PR incorporates #663 because I need
try_capture.