Last active
December 12, 2019 18:55
-
-
Save binki/033a61e832ac211f1a9df593b0d3aa91 to your computer and use it in GitHub Desktop.
AsyncEx-3.0.1 AsyncConditionVariable deadlock repro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace AsyncExAsyncProducerConsumerQueueDeadlock | |
{ | |
class FuncUtil | |
{ | |
public static T Invoke<T>(Func<T> func) => func(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Nito.AsyncEx; | |
using System; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace AsyncExAsyncProducerConsumerQueueDeadlock | |
{ | |
class Program | |
{ | |
static async Task Main(string[] args) | |
{ | |
var asyncLock = new AsyncLock(); | |
var asyncCondition = new AsyncConditionVariable(asyncLock); | |
using (var cancelSource = new CancellationTokenSource()) | |
{ | |
var ct = cancelSource.Token; | |
Task waiter1Task; | |
// 1. Lock the lock. | |
using (await asyncLock.LockAsync()) | |
{ | |
Console.WriteLine("Main locked"); | |
// 2. Add a waiter on the lock. | |
waiter1Task = FuncUtil.Invoke(async () => | |
{ | |
Task cancellerTask; | |
Console.WriteLine("Waiter 1 waiting."); | |
using (await asyncLock.LockAsync()) | |
{ | |
// Now that we have continued, we should be holding asyncCondition._mutex. | |
Console.WriteLine($"Waiter 1 locked"); | |
// 4. Cancel on another thread. | |
cancellerTask = Task.Run(() => | |
{ | |
Console.WriteLine($"Canceller cancelling…"); | |
cancelSource.Cancel(); | |
Console.WriteLine($"Canceller cancelled"); | |
}); | |
Console.WriteLine($"Waiter 1 sleeping…"); | |
// 5. Ensure that the Cancel() was called on another thread. It should start waiting | |
// on asyncCondition._mutex. | |
Thread.Sleep(1000); | |
Console.WriteLine($"Waiter 1 woke, notifying…"); | |
// 6. Trigger an attempt to have asyncCondition.WaitAsync(ct) from step 3 unregister. | |
// | |
// In AsyncEx-3.0.1, this calls TaskCompletionSource.SetResult(). In AsyncEx-4.0.1, | |
// it instead calls TrySetResultWithBackgroundContinuations(), avoiding the deadlock. | |
asyncCondition.Notify(); | |
Console.WriteLine($"Waiter 1 sleeping more…"); | |
Thread.Sleep(1000); | |
Console.WriteLine($"Waiter 1 woke up"); | |
} | |
Console.WriteLine($"Waiter 1 unlocked"); | |
await cancellerTask; | |
}); | |
// 3. Release using condition with cancellation token. | |
Console.WriteLine($"Main waiting on condition…"); | |
await asyncCondition.WaitAsync(ct); | |
Console.WriteLine($"Main woke from condition."); | |
} | |
await waiter1Task; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@StephenCleary Ah, I didn’t realize that. I probably have introduced subtle bugs due to my hacks. I am going to see if upgrading to 5.0.0 is viable.
If you have time: is V3 known/expected to have unavoidable deadlocks? Is my above repro doing something it shouldn’t be?
Thanks so much for your response!