Skip to content

Fix hang in AsyncLazy<T> when value factory synchronously blocks for UI thread - #3

Merged
Andrew Arnott (AArnott) merged 2 commits into
masterfrom
dev/andarno/fix278339
Oct 17, 2016
Merged

Andrew Arnott (AArnott) merged 2 commits into
masterfrom
dev/andarno/fix278339

Conversation

@AArnott

Copy link
Copy Markdown
Member

When the value factory passed to the constructor of an AsyncLazy tries to switch to the UI thread while inside a JTF.Run, and if the UI thread itself is in a JTF.Run and then calls AsyncLazy.GetValueAsync on that same instance, a deadlock occurs because the value factory is invoked within the AsyncLazy's own private lock on the background thread -- the same lock that the UI thread then blocks on such that it is unwilling to help the background thread complete the transition to the UI thread.

This fixes that deadlock by allowing the AsyncLazy to invoke the value factory outside its own private lock. In general, this pattern of invoking 3rd party code outside one's own private lock is a very good idea to avoid deadlocks.
To make this possible, AsyncLazy needs a JoinableTask immediately when the first caller claims the right to execute the value factory so that other threads can join it. Yet the only way to obtain a JoinableTask instance is to first invoke a delegate that represents the joinable task, so that must happen inside the lock obtained by the first caller. A new InlineResumable type is introduced to allow the value factory to yield but resumed inline so that we don't have to wait for a threadpool thread or the UI thread to schedule the real value factory later.

A few new tests for AsyncLazy are added to demonstrate the hangs are fixed.

Fixes devdiv bug 278339

@msftclas

Hi Andrew Arnott (@AArnott), I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!


It looks like you're a Microsoft contributor (Andrew Arnott). If you're full-time, we DON'T require a Contribution License Agreement. If you are a vendor, please DO sign the electronic Contribution License Agreement. It will take 2 minutes and there's no faxing! https://cla.microsoft.com.

TTYL, MSBOT;

@AArnott

Copy link
Copy Markdown
Member Author

Build validation results

@IlyaBiryukov

Copy link
Copy Markdown
Contributor

"In general, this pattern of invoking 3rd party code outside one's own private lock is a very good idea to avoid deadlocks." - a good piece of advise for the threading cookbook.

InlineResumable resumableAwaiter = null;
lock (this.syncObject)
{
// Note that if multiple threads hit GetValueAsync() before

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.

Delete this obsolete code comment.

@AArnott
Andrew Arnott (AArnott) merged commit 43c3d91 into master Oct 17, 2016
@AArnott
Andrew Arnott (AArnott) deleted the dev/andarno/fix278339 branch October 17, 2016 18:24
Andrew Arnott (AArnott) added a commit that referenced this pull request Apr 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants