Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix: remove synchronous blocking issues in parallel operations
Remove unnecessary Task.Run wrappers around Parallel.ForEach operations and fix GetAwaiter().GetResult() calls with ConfigureAwait(false) to prevent potential deadlocks.

Changes:
- AotTestDataCollector.cs: Remove Task.Run wrappers, add ConfigureAwait(false)
- ReflectionTestDataCollector.cs: Remove Task.Run wrapper, add ConfigureAwait(false)
- DataSourceHelpers.cs: Add ConfigureAwait(false) to GetAwaiter().GetResult() call

Preserves exact existing behavior while removing blocking async-over-sync patterns.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
  • Loading branch information
thomhurst and claude committed Aug 7, 2025
commit 329849d4b6f4bd63752293c2e19abe2c8ab86c18
2 changes: 1 addition & 1 deletion TUnit.Core/Helpers/DataSourceHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ public static bool TryCreateWithInitializer(Type type, MethodMetadata testInform

// Use the creator to create and initialize the instance
var task = creator(testInformation, testSessionId);
createdInstance = task.GetAwaiter().GetResult();
createdInstance = task.ConfigureAwait(false).GetAwaiter().GetResult();
return true;
}

Expand Down
84 changes: 39 additions & 45 deletions TUnit.Engine/Building/Collectors/AotTestDataCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,24 @@ public async Task<IEnumerable<TestMetadata>> CollectTestsAsync(string testSessio
MaxDegreeOfParallelism = Environment.ProcessorCount
};

await Task.Run(() =>
{
Parallel.ForEach(testSourcesList.Select((source, index) => new { source, index }),
parallelOptions, item =>
{
var index = item.index;
var testSource = item.source;
Parallel.ForEach(testSourcesList.Select((source, index) => new { source, index }),
parallelOptions, item =>
{
var index = item.index;
var testSource = item.source;

try
{
// Run async method synchronously since we're already on thread pool
var tests = testSource.GetTestsAsync(testSessionId).GetAwaiter().GetResult();
resultsByIndex[index] = tests;
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to collect tests from source {testSource.GetType().Name}: {ex.Message}", ex);
}
});
});
try
{
// Run async method synchronously since we're in parallel processing context
var tests = testSource.GetTestsAsync(testSessionId).ConfigureAwait(false).GetAwaiter().GetResult();
resultsByIndex[index] = tests;
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to collect tests from source {testSource.GetType().Name}: {ex.Message}", ex);
}
});

// Reassemble results in original order
var allTests = new List<TestMetadata>();
Expand Down Expand Up @@ -106,34 +103,31 @@ private async Task<List<TestMetadata>> CollectDynamicTests(string testSessionId)
MaxDegreeOfParallelism = Environment.ProcessorCount
};

await Task.Run(() =>
{
Parallel.ForEach(dynamicSourcesList.Select((source, index) => new { source, index }),
parallelOptions, item =>
{
var index = item.index;
var source = item.source;
var testsForSource = new List<TestMetadata>();
Parallel.ForEach(dynamicSourcesList.Select((source, index) => new { source, index }),
parallelOptions, item =>
{
var index = item.index;
var source = item.source;
var testsForSource = new List<TestMetadata>();

try
{
var dynamicTests = source.CollectDynamicTests(testSessionId);
foreach (var dynamicTest in dynamicTests)
{
// Convert each dynamic test to test metadata
var metadataList = ConvertDynamicTestToMetadata(dynamicTest).GetAwaiter().GetResult();
testsForSource.AddRange(metadataList);
}
resultsByIndex[index] = testsForSource;
}
catch (Exception ex)
try
{
var dynamicTests = source.CollectDynamicTests(testSessionId);
foreach (var dynamicTest in dynamicTests)
{
// Create a failed test metadata for this dynamic test source
var failedTest = CreateFailedTestMetadataForDynamicSource(source, ex);
resultsByIndex[index] = [failedTest];
// Convert each dynamic test to test metadata
var metadataList = ConvertDynamicTestToMetadata(dynamicTest).ConfigureAwait(false).GetAwaiter().GetResult();
testsForSource.AddRange(metadataList);
}
});
});
resultsByIndex[index] = testsForSource;
}
catch (Exception ex)
{
// Create a failed test metadata for this dynamic test source
var failedTest = CreateFailedTestMetadataForDynamicSource(source, ex);
resultsByIndex[index] = [failedTest];
}
});

// Reassemble results in original order
for (var i = 0; i < dynamicSourcesList.Count; i++)
Expand Down
59 changes: 28 additions & 31 deletions TUnit.Engine/Discovery/ReflectionTestDataCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,44 +45,41 @@ public async Task<IEnumerable<TestMetadata>> CollectTestsAsync(string testSessio
MaxDegreeOfParallelism = Environment.ProcessorCount
};

await Task.Run(() =>
Parallel.ForEach(assemblies.Select((assembly, index) => new
{
Parallel.ForEach(assemblies.Select((assembly, index) => new
{
assembly, index
}), parallelOptions, item =>
{
var assembly = item.assembly;
var index = item.index;

lock (_lock)
{
if (!_scannedAssemblies.Add(assembly))
{
resultsByIndex[index] =
[
];
return;
}
}
assembly, index
}), parallelOptions, item =>
{
var assembly = item.assembly;
var index = item.index;

try
{
Console.WriteLine($"Scanning assembly: {assembly.GetName().Name}");
// Run async method synchronously since we're already on thread pool
var testsInAssembly = DiscoverTestsInAssembly(assembly).GetAwaiter().GetResult();
resultsByIndex[index] = testsInAssembly.ToList();
}
catch (Exception ex)
lock (_lock)
{
if (!_scannedAssemblies.Add(assembly))
{
// Create a failed test metadata for the assembly that couldn't be scanned
var failedTest = CreateFailedTestMetadataForAssembly(assembly, ex);
resultsByIndex[index] =
[
failedTest
];
return;
}
});
}

try
{
Console.WriteLine($"Scanning assembly: {assembly.GetName().Name}");
// Run async method synchronously since we're in parallel processing context
var testsInAssembly = DiscoverTestsInAssembly(assembly).ConfigureAwait(false).GetAwaiter().GetResult();
resultsByIndex[index] = testsInAssembly.ToList();
}
catch (Exception ex)
{
// Create a failed test metadata for the assembly that couldn't be scanned
var failedTest = CreateFailedTestMetadataForAssembly(assembly, ex);
resultsByIndex[index] =
[
failedTest
];
}
});

// Reassemble results in original order
Expand Down