Skip to content

perf: skip TimeoutHelper wrap when no explicit [Timeout] is set #5711

@thomhurst

Description

@thomhurst

Summary

Every test is wrapped in TimeoutHelper.ExecuteWithTimeoutAsync because TestDetails.Timeout is always set to the default (30 min), even when the user never applied [Timeout]. Estimated ~5% CPU + 4 extra allocations per test (linked CancellationTokenSource, TaskCompletionSource, CancellationTokenRegistration, Task.WhenAny array).

This is likely the single biggest ROI fix from the profiling session.

Evidence

Trace shows TimeoutHelper.ExecuteWithTimeoutAsync as ~5% inclusive / ~3% direct in the per-test path.

Root cause

TUnitSettings.Default.Timeouts.DefaultTestTimeout (30 min) is copied into TestDetails.Timeout during build, so testTimeout.HasValue is always true:

  • TUnit.Engine/Building/TestBuilderPipeline.cs:260, 387, 466, 519
  • TUnit.Engine/Building/TestBuilder.cs:1089, 1182
  • Check-site: TUnit.Engine/TestExecutor.cs:203

Every test then goes through TUnit.Engine/Helpers/TimeoutHelper.cs:26 with the full allocation set.

Proposed fix

  • Leave TestDetails.Timeout = null when the user did not specify [Timeout].
  • Only run through TimeoutHelper.ExecuteWithTimeoutAsync when a timeout is actually requested.
  • If a global default is still desirable, apply it as a coarser session-level guard rather than per-test.

Expected impact

  • ~5% CPU reduction per test.
  • Removes 4 allocations per test — cuts GC pressure dramatically on large suites.

Files

  • TUnit.Engine/Building/TestBuilderPipeline.cs
  • TUnit.Engine/Building/TestBuilder.cs
  • TUnit.Engine/TestExecutor.cs
  • TUnit.Engine/Helpers/TimeoutHelper.cs

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETPull requests that update .net codeenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions