-
Notifications
You must be signed in to change notification settings - Fork 289
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 Async thread blocking on SqlConnection open for AAD modes #1213
Conversation
a885c11
to
48daafe
Compare
/cc @roji for async expertise |
Try ConfigureAwait(false) at all awaits to prevent the task scheduler trying make tasks continue on the thread that started them. In this library (Because it's a library) all awaits should be ConfigureAwait(false). |
Doesn't really matter in that specific case because |
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.
Yeah, this seems like the classical block-over-async deadlock (see this article by Stephen Cleary). Changing Task.Run should indeed resolve the Winforms deadlock, since the code runs without the Winforms SynchronizationContext.
However, this is still sync-over-async and therefore strongly discouraged, since it can cause various starvation/pseudo-deadlock effects. For example, if an application starts up and attempts to establish lots of connections simultaneously ("thundering herd"), this code would be executed many times in parallel. Each method would enqueue AcquireTokenAsync to the thread pool, and then block the current thread. If the current thread itself happens to be a TP thread (e.g. ASP.NET), the thread pool could quickly get exhausted, as its threads are all blocking synchronously waiting for AcquireTokenAsync to complete, but there are no available TP threads to execute it. The TP will hill-climb and create new threads - so this will eventually get resolved, and therefore isn't a true deadlock - but it's quite a problematic effect.
If authProvider doesn't provide a sync API for acquiring the token, then it's reasonable to throw NotSupportedException if a user tries to synchronously open a connection that needs this. Since I'm assuming that's not possible, another way around this is to have a private thread pool (e.g. of size 1), and use a SynchronizationContext to make sure continuations are executed on that pool. This would guarantee that AcquireTokenAsync never waits for execution because the regular TP is exhausted. For an example of Npgsql doing this internally, see this.
Fixes #1209
Breaking Change [Low impact]:
SqlException
replacingAggregateException
for Active Directory Authentication modes.