-
-
Notifications
You must be signed in to change notification settings - Fork 84
Fix test discovery intermittent hanging issues in reflection mode #2932
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This commit addresses multiple critical deadlock scenarios that could cause test discovery to hang intermittently: 1. Missing ConfigureAwait(false) calls (40+ locations fixed) - Added to all await calls in TestDiscoveryService, HookOrchestrator, and TestBuilderPipeline - Prevents SynchronizationContext capture that causes deadlocks in UI/ASP.NET environments 2. Thread pool exhaustion from unbounded parallelism - Added SemaphoreSlim throttling to limit concurrent assembly discovery to ProcessorCount * 2 - Prevents thread pool starvation when discovering tests in solutions with many assemblies 3. Static lock bottleneck causing serialization - Replaced HashSet<Assembly> with ConcurrentDictionary<Assembly, bool> - Replaced List<TestMetadata> with ConcurrentBag<TestMetadata> - Eliminated global lock that was serializing all parallel discovery operations These changes resolve hanging issues especially in: - WPF/WinForms applications with UI SynchronizationContext - ASP.NET applications with request context - Large solutions with 50+ assemblies - High-concurrency test discovery scenarios 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Claude finished @thomhurst's task —— View job Code Review: Fix test discovery intermittent hanging issuesReview Tasks
SummaryThis PR successfully addresses critical deadlock scenarios in test discovery. The changes are well-targeted and implement proper async patterns, concurrency control, and thread-safe collections. ✅ StrengthsConfigureAwait(false) Implementation
Concurrency Control (SemaphoreSlim)
Thread-Safe Collections
|
Removed complex channel-based streaming in favor of simpler approach that matches source generation mode: - Collect all tests first, then process dependencies - No channels, no producer/consumer pattern - Handles circular dependencies gracefully - Prevents deadlock scenarios from channel write conflicts This aligns reflection mode with the simpler source generation approach. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Claude finished @thomhurst's task —— View job Code Review: Fix test discovery intermittent hanging issues
Review Tasks
SummaryThis PR successfully addresses critical deadlock scenarios in test discovery. The changes implement proper async patterns, concurrency control, and thread-safe collections. However, there's one critical bug that needs immediate attention. ✅ StrengthsConfigureAwait(false) Implementation
Concurrency Control (SemaphoreSlim)
Thread-Safe Collections
❌ Critical Bug FoundNon-Existent Lock ReferenceLocation: await foreach (var dynamicTest in DiscoverDynamicTestsStreamingAsync(testSessionId, cancellationToken))
{
lock (_lock) // ❌ CRITICAL: _lock does not exist
{
_discoveredTests.Add(dynamicTest);
}
yield return dynamicTest;
} Issues:
Fix Required: Remove the lock entirely: await foreach (var dynamicTest in DiscoverDynamicTestsStreamingAsync(testSessionId, cancellationToken))
{
_discoveredTests.Add(dynamicTest); // ConcurrentBag.Add() is thread-safe
yield return dynamicTest;
}
|
@claude Fix the failing build |
Claude finished @thomhurst's task —— View job Tasks
Build Errors Found & Fixed
Changes Made
Status✅ Fixed: Committed as The compilation errors have been resolved:
Both fixes maintain the thread safety and async behavior while resolving the compilation issues. |
- Remove non-existent _lock reference in ReflectionTestDataCollector.cs - Remove ConfigureAwait(false) from Task.Yield() which doesn't support it Co-authored-by: Tom Longhurst <thomhurst@users.noreply.github.com>
Changed property injection from sequential foreach to parallel Task.WhenAll to fix failing tests: - Test_ParallelPropertyInitialization_ShouldInitializeContainersInParallel - Test_NestedParallelPropertyInitialization_ShouldInitializeAllLevelsInParallel The sequential processing was causing initialization to take longer than expected, preventing the tests from seeing the expected time savings from parallel initialization. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
…hurst/TUnit into fix/test-discovery-deadlock
Claude finished @thomhurst's task —— View job Code Review: Fix test discovery intermittent hanging issues
Review Tasks
SummaryThis PR successfully addresses critical deadlock scenarios in test discovery. The implementation demonstrates solid understanding of async patterns, concurrency control, and thread-safe collections. The changes are well-targeted and should significantly improve reliability in reflection mode test discovery. ✅ Strengths1. Comprehensive ConfigureAwait(false) Implementation
2. Smart Concurrency Control with SemaphoreSlim
3. Thread-Safe Collection Migration
4. Performance Optimizations
|
- Changed from Task.WhenAll pattern to ProcessInParallel() extension method - Replaced continue/break statements with return statements for async lambda - Added using EnumerableAsyncProcessor.Extensions - Follows the established pattern used in TestBuilderPipeline and AotTestDataCollector This fixes the CS0139 and CS8602 compilation errors while maintaining parallel execution. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Claude finished @thomhurst's task —— View job Code Review: Fix test discovery intermittent hanging issues
Review Tasks
SummaryThis PR successfully addresses critical deadlock scenarios in test discovery. The implementation demonstrates solid understanding of async patterns, concurrency control, and thread-safe collections. The changes are well-targeted and should significantly improve reliability in reflection mode test discovery. ✅ Strengths1. Comprehensive ConfigureAwait(false) Implementation
2. Smart Concurrency Control with SemaphoreSlim
3. Thread-Safe Collection Migration
4. Performance Optimizations
|
Use Task.Run with Task.WhenAll pattern instead of ProcessInParallel() since EnumerableAsyncProcessor is only available in TUnit.Engine, not TUnit.Core. This maintains parallel execution while avoiding the missing dependency. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses multiple critical deadlock scenarios that could cause test discovery to hang intermittently:
Missing ConfigureAwait(false) calls (40+ locations fixed)
Thread pool exhaustion from unbounded parallelism
Static lock bottleneck causing serialization