Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 5 additions & 3 deletions TUnit.Core/EngineCancellationToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,11 @@ private void OnProcessExit(object? sender, EventArgs e)
{
CancellationTokenSource.Cancel();

// Give After hooks a brief moment to execute via registered callbacks
// ProcessExit has limited time, so we can only wait briefly
Task.Delay(TimeSpan.FromMilliseconds(500)).GetAwaiter().GetResult();
// Give After hooks a brief moment to execute via registered callbacks.
// ProcessExit has limited time (~3s on Windows), so we can only wait briefly.
// Thread.Sleep is appropriate here: we're on a synchronous event handler thread
// and just need a simple delay — no need to involve the task scheduler.
Thread.Sleep(500);
}
}

Expand Down
6 changes: 5 additions & 1 deletion TUnit.Core/Executors/DedicatedThreadExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,11 @@ public override void Send(SendOrPostCallback d, object? state)
await tcs.Task.ConfigureAwait(false);
});

// This wait is safe because it's on a Task.Run thread without SynchronizationContext
// This blocking wait is intentional and safe from deadlocks because:
// 1. We verified above that the current thread is NOT the dedicated thread
// 2. The work is posted to the dedicated thread's queue via Post()
// 3. waitTask runs via Task.Run without a SynchronizationContext, so no context capture
// 4. SynchronizationContext.Send is synchronous by API contract — blocking is required
waitTask.GetAwaiter().GetResult();
}
}
Expand Down
Loading