-
-
Notifications
You must be signed in to change notification settings - Fork 108
Closed
Description
Parent Epic
Part of #4159 - Performance Optimization: Hot Path Improvements
Problem
Two critical paths hold locks longer than necessary, limiting parallel throughput. The discovery path creates defensive copies while holding a lock, and the constraint scheduler evaluates LINQ inside a lock.
Evidence
| File | Lines | Issue | Impact |
|---|---|---|---|
TUnit.Engine/Discovery/ReflectionTestDataCollector.cs |
137-141 | Lock held during AddRange + defensive copy of entire list |
Serializes parallel assembly discovery |
TUnit.Engine/Scheduling/ConstraintKeyScheduler.cs |
56-69, 151-190 | LINQ .Any() evaluated inside lock, temp list rebuilt in lock |
Serializes constrained test scheduling |
Suggested Approach
For ReflectionTestDataCollector:
// Before: Defensive copy inside lock
lock (_discoveredTestsLock)
{
_discoveredTests.AddRange(newTests);
return new List<TestMetadata>(_discoveredTests); // Full copy while holding lock!
}
// After: Minimize lock scope
lock (_discoveredTestsLock)
{
_discoveredTests.AddRange(newTests);
}
// Return IReadOnlyList view or snapshot outside lock
return _discoveredTests.AsReadOnly();Alternative: Use ImmutableList<T> for lock-free reads with atomic swaps.
For ConstraintKeyScheduler:
// Before: LINQ inside lock
lock (lockObject)
{
canStart = !constraintKeys.Any(key => lockedKeys.Contains(key));
if (canStart) { /* ... */ }
}
// After: Manual loop, pre-allocate outside lock
bool canStart = true;
lock (lockObject)
{
foreach (var key in constraintKeys)
{
if (lockedKeys.Contains(key)) { canStart = false; break; }
}
if (canStart) { /* ... */ }
}Also move testsToStart list allocation outside the lock scope.
Verification
- Run parallel stress test with many constrained tests
- Profile lock contention using
dotnet-tracewith threading events - Measure throughput at high parallelism (16+ cores)
Risks
- Higher complexity - race conditions possible if not careful
ReflectionTestDataCollectorchange affects callers expecting a mutable copy- Requires thorough testing with parallel execution
Priority
P1 - Higher complexity, affects thread scaling at high core counts
Metadata
Metadata
Assignees
Labels
No labels