Skip to content
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

Fix issue with potential deadlock when reading a managed stream that we wrap from native code #1126

Closed
wants to merge 3 commits into from

Conversation

manodasanW
Copy link
Member

CsWinRT has some helper methods which allows to wrap a C# managed stream and pass it as an WinRT stream interface like IRandomStream. This is implemented by having a wrapper class in CsWinRT which satisfies the methods of the WinRT stream interfaces and calls the appropriate methods on the managed stream to implement them.

As part of this, the read / write methods return IAsyncOperations and when a synchronization context exists on the thread on which the async operation is created, we will queue the completion handlers onto the same context as part of the async operation infrastructure. But if the caller of the read / write is blocking on the same thread for them to get triggered and isn't pumping STA messages, we can end up in a deadlock as seen in the mentioned issue. This is specifically true when CreateStreamOverRandomAccessStream is used by apps to convert it to an IStream and is done on the UI thread or another thread with a synchronization context.

Apps / libraries can avoid this deadlock by doing it on a background / worker thread without a synchronization context. But given this will always deadlock in this scenario when used with CreateStreamOverRandomAccessStream, I am putting a targeted change in CsWinRT to accommodate for it. The change will make async operations created by our managed stream wrappers not need to run the completion handlers on the same context and thereby avoiding the deadlock. Given that the callers of this async operation should mostly be native (managed callers will probably use the stream directly) and thereby its handlers and given that SynchronizationContext look to be a managed concept, I believe this should be fine, but we will monitor to see if there is any issues that arise from this.

Fixes #670

…not need to run completion handlers on same context avoid deadlocks on STA threads.
@manodasanW manodasanW requested a review from ujjwalchadha March 7, 2022 23:03
@jlaanstra
Copy link
Collaborator

jlaanstra commented Mar 7, 2022

Doesn't this break the IAsyncOperation/IAsyncAction contract where the callbacks happen in the same apartment? Blocking on an async operation in an STA is a well known problem that generally can be easily prevented by not blocking on the STA.

/cc @kennykerr @oldnewthing for the IAsyncOperation/IAsyncAction contract. As far as I know these objects always marshal back to the calling apartment but I'm not immediately seeing in the docs that this is required. Closest I found so far is https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/concurrency-2#execution-contexts-resuming-and-switching-in-a-coroutine

@manodasanW
Copy link
Member Author

Doesn't this break the IAsyncOperation/IAsyncAction contract where the callbacks happen in the same apartment? Blocking on an async operation in an STA is a well known problem that generally can be easily prevented by not blocking on the STA.

/cc @kennykerr @oldnewthing for the IAsyncOperation/IAsyncAction contract. As far as I know these objects always marshal back to the calling apartment but I'm not immediately seeing in the docs that this is required. Closest I found so far is https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/concurrency-2#execution-contexts-resuming-and-switching-in-a-coroutine

I have been looking more into this and it looks like C++/WinRT achieves something similar using IContextCallback. But I noticed they do pass ICallbackWithNoReentrancyToApplicationSTA and have a fallback to call without context on error. Is that what C++/WinRT uses to avoid deadlocks such as in this scenario where the completion handler is trying to be triggered on the STA thread which is where the async operation is being waited on? @kennykerr?

If the above is correct, I wonder if we should instead be using context callback to do the call on the same thread for ASTA or if we should continue using SynchronizationContext which is what is being used right now to light up something similar.

@jlaanstra
Copy link
Collaborator

@jlaanstra
Copy link
Collaborator

I also think the current change can result in some interesting behavior when the IAsyncAction/Operation ends back in C# land and a C# caller calls AsTask() on this.

@kennykerr
Copy link
Contributor

The async operation is not required to complete on any particular thread or apartment. It is the responsibility of the waiter to synchronize/reschedule as desired. C++/WinRT captures the apartment context prior to suspension and resumes on that same context.

@jlaanstra
Copy link
Collaborator

The async operation is not required to complete on any particular thread or apartment. It is the responsibility of the waiter to synchronize/reschedule as desired. C++/WinRT captures the apartment context prior to suspension and resumes on that same context.

Sounds like we might be able to remove the context capture completely then.

@manodasanW
Copy link
Member Author

Thanks @kennykerr and @jlaanstra. Have a new PR (#1131) which addresses this.

@manodasanW manodasanW closed this Mar 9, 2022
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.

WebView2 hangs on reading final 0 bytes via NetFxToWinRtStreamAdapter
3 participants