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

AsyncSemaphore deadlock: EnterAsync holds lock while blocking on cancellation handler which needs lock #1000

Closed
jasonmalinowski opened this issue Mar 22, 2022 · 1 comment · Fixed by #1001
Assignees
Labels

Comments

@jasonmalinowski
Copy link
Member

jasonmalinowski commented Mar 22, 2022

(This is being reported from the analysis from devdiv-1496681

Bug description

AsyncSemaphore.EnterAsync has this specific line:

Which calls:

cancellationTokenRegistration.Dispose();

If you get particularly unlucky with your timing, that call to cancellationTokenRegistration.Dispose() will block on the cancellation handler completing; if that's running on another thread and potentially blocked in it's own right, then the monitor is held and all other calls to AsyncSemaphore will block, synchronously.

I admit it's not immediately obvious to me if this will deadlock permanently:

lock (waiterInfo.Owner.syncObject)
might be trying to acquire the monitor as well in the cancellation handler -- we might have a case where the cancellation handler is acquiring the monitor and the monitor is blocking on the cancellation handler. But my analysis might be incorrect there.

I suspect one fix is to move info.Cleanup() outside the monitor lock, but not sure if other fixes are available too.

Repro steps

Have code using AsyncSemaphore with cancellation, and get unlucky.

@AArnott AArnott self-assigned this Mar 24, 2022
@AArnott AArnott added the bug label Mar 24, 2022
@AArnott
Copy link
Member

AArnott commented Mar 24, 2022

More notes from @jasonmalinowski's investigation:

404 threads are blocked trying to acquire the monitor in AsyncSemaphore.EnterAsync.
The one thread that does have the monitor is in here:

>	mscorlib.dll!System.Threading.SpinWait.SpinOnce() Line 146	C#
 	mscorlib.dll!System.Threading.CancellationTokenSource.WaitForCallbackToComplete(System.Threading.CancellationCallbackInfo callbackInfo) Line 946	C#
 	mscorlib.dll!System.Threading.CancellationTokenRegistration.Dispose() Line 90	C#
 	Microsoft.VisualStudio.Threading.dll!Microsoft.VisualStudio.Threading.AsyncSemaphore.WaiterInfo.Cleanup() Line 357	C#
 	Microsoft.VisualStudio.Threading.dll!Microsoft.VisualStudio.Threading.AsyncSemaphore.EnterAsync(System.TimeSpan timeout, System.Threading.CancellationToken cancellationToken) Line 147	C#

22 threads are here in StreamJsonRpc:

 	mscorlib.dll!System.Threading.SpinWait.SpinOnce() Line 146	C#
 	mscorlib.dll!System.Threading.CancellationTokenSource.WaitForCallbackToComplete(System.Threading.CancellationCallbackInfo callbackInfo) Line 946	C#
 	mscorlib.dll!System.Threading.CancellationTokenRegistration.Dispose() Line 90	C#
 	mscorlib.dll!System.Threading.CancellationTokenSource.Dispose(bool disposing) Line 582	C#
>	StreamJsonRpc.dll!StreamJsonRpc.JsonRpc.InvokeCoreAsync(StreamJsonRpc.Protocol.JsonRpcRequest request, System.Type expectedResultType, System.Threading.CancellationToken cancellationToken) Line 1906	C#

22 other threads (suspicious the same number?) are here:

>	Microsoft.VisualStudio.Threading.dll!Microsoft.VisualStudio.Threading.AsyncSemaphore.CancellationHandler(object ste) Line 234	C#

AArnott added a commit to AArnott/vs-threading that referenced this issue Mar 24, 2022
This avoids calling `CancellationTokenRegistration.Dispose` while holding a private lock in EnterAsync. There is one remaining case where we may hold a lock while calling it, but that's in the inlined case of EnterAsync and CTR will have its default value, and thus could not have anything to block on.

The problematic case that we're fixing is when the token is canceled while EnterAsync is executing between where the CTR is assigned and where we called `info.Cleanup()`. In such a case, we would deadlock because Cleanup would wait for handlers to complete but the handler is waiting for the lock.

Fixes microsoft#1000
@AArnott AArnott changed the title AsyncSemaphore.EnterAsync may block on cancellation while holding the monitor AsyncSemaphore deadlock: EnterAsync holds lock while blocking on cancellation handler which needs lock Mar 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants