Skip to content

Commit 816da19

Browse files
Fix test hanging issue: remove manual OnDispose event triggering
The original fix introduced a deadlock/race condition by manually invoking OnDispose events after tracking objects with ObjectTracker. This caused tests to hang because: 1. ObjectTracker.TrackObject() adds disposal callbacks to events.OnDispose 2. Manual invocation of events.OnDispose triggered these callbacks immediately 3. This led to premature disposal and potential deadlocks in the disposal system Fixed by removing manual OnDispose invocations and letting ObjectTracker handle disposal automatically through the normal test lifecycle. Co-authored-by: Tom Longhurst <thomhurst@users.noreply.github.com>
1 parent 218bc66 commit 816da19

File tree

1 file changed

+6
-45
lines changed

1 file changed

+6
-45
lines changed

TUnit.Engine/Services/SingleTestExecutor.cs

Lines changed: 6 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -268,36 +268,12 @@ private async Task<TestResult> HandleSkippedTestInternalAsync(AbstractExecutable
268268
instance is not SkippedTestInstance &&
269269
instance is not PlaceholderInstance)
270270
{
271-
try
272-
{
273-
// For skipped tests, also ensure the test instance is tracked for disposal
274-
if (instance is IAsyncDisposable or IDisposable)
275-
{
276-
ObjectTracker.TrackObject(test.Context.Events, instance);
277-
}
278-
279-
// Trigger disposal of all tracked objects (including test instance and injected properties)
280-
if (test.Context.Events.OnDispose != null)
281-
{
282-
foreach (var invocation in test.Context.Events.OnDispose.InvocationList.OrderBy(x => x.Order))
283-
{
284-
try
285-
{
286-
await invocation.InvokeAsync(test.Context, test.Context).ConfigureAwait(false);
287-
}
288-
catch (Exception ex)
289-
{
290-
// Log but don't throw - we still need to dispose other objects
291-
await _logger.LogErrorAsync($"Error during OnDispose event for skipped test: {ex.Message}").ConfigureAwait(false);
292-
}
293-
}
294-
}
295-
}
296-
catch (Exception ex)
271+
// For skipped tests, also ensure the test instance is tracked for disposal
272+
if (instance is IAsyncDisposable or IDisposable)
297273
{
298-
// Log disposal errors but don't fail the skipped test
299-
await _logger.LogErrorAsync($"Error disposing skipped test instance: {ex.Message}").ConfigureAwait(false);
274+
ObjectTracker.TrackObject(test.Context.Events, instance);
300275
}
276+
// Note: ObjectTracker will handle disposal automatically when the test context ends
301277
}
302278

303279
return test.Result;
@@ -344,23 +320,8 @@ private async Task ExecuteTestWithHooksAsync(AbstractExecutableTest test, object
344320
}
345321
finally
346322
{
347-
// Trigger disposal of all tracked objects (including test instance and injected properties)
348-
// The ObjectTracker system ensures proper disposal order and reference counting
349-
if (test.Context.Events.OnDispose != null)
350-
{
351-
foreach (var invocation in test.Context.Events.OnDispose.InvocationList.OrderBy(x => x.Order))
352-
{
353-
try
354-
{
355-
await invocation.InvokeAsync(test.Context, test.Context).ConfigureAwait(false);
356-
}
357-
catch (Exception ex)
358-
{
359-
// Log but don't throw - we still need to dispose other objects
360-
await _logger.LogErrorAsync($"Error during OnDispose event: {ex.Message}").ConfigureAwait(false);
361-
}
362-
}
363-
}
323+
// Note: ObjectTracker will handle disposal automatically when the test context ends
324+
// No need to manually trigger disposal events here as it could cause deadlocks
364325
}
365326

366327
if (testException != null)

0 commit comments

Comments
 (0)