-
-
Notifications
You must be signed in to change notification settings - Fork 108
Description
Summary
TimeoutHelper.ExecuteWithTimeoutAsync creates unnecessary allocations even when no timeout is configured. This was identified via dotnet-trace profiling of the speed-comparison ScaleTests, where TimeoutHelper consumed 5.16% of CPU time for tests with no timeout.
Profiling Evidence
From dotnet-trace on ScaleTests (150 simple sync tests):
TimeoutHelper+<ExecuteWithTimeoutAsync>d__1.MoveNext() 5.16% inclusive
Root Cause
In TUnit.Engine/Helpers/TimeoutHelper.cs lines 59-78, the "fast path" when timeout is null still allocates when cancellationToken.CanBeCanceled is true:
if (!timeout.HasValue)
{
var task = taskFactory(cancellationToken);
if (!cancellationToken.CanBeCanceled) // This is rarely true
{
return await task.ConfigureAwait(false);
}
// ALLOCATES: TaskCompletionSource + Task.WhenAny for every test!
var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
using var reg = cancellationToken.Register(...);
return await await Task.WhenAny(task, tcs.Task).ConfigureAwait(false);
}Since cancellationToken.CanBeCanceled is typically true (tests run with cancellation support), every test creates:
- A
TaskCompletionSource - A cancellation registration
- A
Task.WhenAnyoperation
Suggested Fix
When timeout is null, simply await the task directly. Cancellation is cooperative - the test code is already passed the token and should check it:
if (!timeout.HasValue)
{
return await taskFactory(cancellationToken).ConfigureAwait(false);
}The Task.WhenAny pattern is only needed when we want to return control immediately on timeout. Cancellation doesn't need this - we just need to propagate the exception when it occurs.
Impact
- Reduces per-test overhead by ~5% for tests without timeouts (majority of tests)
- Reduces allocations by removing TCS + registration + WhenAny per test
- Will improve TUnit's performance in DataDrivenTests and ScaleTests benchmarks where TUnit currently loses to other frameworks