Description
Deadlock in Windows/Linux when 2 tests wait the tasks in sync.
Deadlock in Windows/Linux/macOS when 3 tests wait the tasks in sync. (macOS has 3 threads in GH Actions)
I cannot reproduce it locally, but GH Action always falls.
Other test frameworks run fine.
For 2 tests:
https://github.com/YegorStepanov/xunit-deadlock/actions/runs/3153937997
For 3 tests:
https://github.com/YegorStepanov/xunit-deadlock/actions/runs/3153957706
(I have set a 10 min timeout in GH Actions, without a timeout they fall after 6 hours)
Code
[Fact]
public void Method1()
{
Task<int> task = new BenchmarkClass1().Method();
bool isAsyncMethod = TaskHelper.TryAwaitTask(task, out object result);
Assert.True(isAsyncMethod);
Assert.Equal(1, result);
}
[Fact]
public void Method2()
{
Task<int> task = new BenchmarkClass2().Method();
bool isAsyncMethod = TaskHelper.TryAwaitTask(task, out object result);
Assert.True(isAsyncMethod);
Assert.Equal(42, result);
}
public class BenchmarkClass1
{
public async Task<int> Method()
{
await Task.Delay(1);
return 1;
}
}
public class BenchmarkClass2
{
public async Task<int> Method()
{
await Task.Delay(1);
return 42;
}
}
public static class TaskHelper
{
// we need await task in sync and return result.
// returns true if task
public static bool TryAwaitTask(object task, out object result)
{
result = null;
if (task is null)
{
return false;
}
// ValueTask<T>
var returnType = task.GetType();
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
{
var asTaskMethod = task.GetType().GetMethod("AsTask");
task = asTaskMethod.Invoke(task, null);
}
if (task is ValueTask valueTask)
task = valueTask.AsTask();
// Task or Task<T>
if (task is Task t)
{
if (TryGetTaskResult(t, out var taskResult))
result = taskResult;
return true;
}
return false;
}
// https://stackoverflow.com/a/52500763
private static bool TryGetTaskResult(Task task, out object result)
{
result = null;
var voidTaskType = typeof(Task<>).MakeGenericType(Type.GetType("System.Threading.Tasks.VoidTaskResult"));
if (voidTaskType.IsInstanceOfType(task))
{
task.GetAwaiter().GetResult();
return false;
}
var property = task.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance);
if (property is null)
{
return false;
}
result = property.GetValue(task);
return true;
}
}
Repro: https://github.com/YegorStepanov/xunit-deadlock
Occurred in: dotnet/BenchmarkDotNet#2114. Check comments/CI builds in PR, they can help.
If I add [Collection("Dissable parallelisation")]
or move the tests to one class, there is no deadlock.
cc @timcassell