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
2 changes: 0 additions & 2 deletions TUnit.Engine/Framework/TUnitServiceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,6 @@ public TUnitServiceProvider(IExtension extension,

TestExecutor = Register(new TestExecutor(hookExecutor, lifecycleCoordinator, beforeHookTaskCache, afterHookPairTracker, ContextProvider, EventReceiverOrchestrator));

var testExecutionGuard = Register(new TestExecutionGuard());
var testStateManager = Register(new TestStateManager());
var testContextRestorer = Register(new TestContextRestorer());
var testMethodInvoker = Register(new TestMethodInvoker());
Expand Down Expand Up @@ -223,7 +222,6 @@ public TUnitServiceProvider(IExtension extension,
// Create the new TestCoordinator that orchestrates the granular services
var testCoordinator = Register<ITestCoordinator>(
new TestCoordinator(
testExecutionGuard,
testStateManager,
MessageBus,
testContextRestorer,
Expand Down
16 changes: 16 additions & 0 deletions TUnit.Engine/Services/AfterHookPairTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ internal sealed class AfterHookPairTracker
// a test timeout cannot prematurely trigger session-level After hooks.
private volatile bool _sessionHookRegistered;

// Per-test callers would otherwise register one CancellationTokenRegistration per test
// for every assembly/class — 10k tests across 5 assemblies = 50k redundant registrations.
// First registration wins; subsequent calls short-circuit.
private readonly ConcurrentHashSet<Assembly> _assemblyHookRegistered = new();
private readonly ConcurrentHashSet<Type> _classHookRegistered = new();

// Track cancellation registrations for cleanup
private readonly ConcurrentBag<CancellationTokenRegistration> _registrations = [];

Expand Down Expand Up @@ -65,6 +71,11 @@ public void RegisterAfterAssemblyHook(
CancellationToken cancellationToken,
Func<Assembly, ValueTask<List<Exception>>> afterHookExecutor)
{
if (!_assemblyHookRegistered.Add(assembly))
{
return;
}

var registration = cancellationToken.Register(static state =>
{
var (pairTracker, assembly, afterHookExecutor) = ((AfterHookPairTracker, Assembly, Func<Assembly, ValueTask<List<Exception>>>))state!;
Expand All @@ -86,6 +97,11 @@ public void RegisterAfterClassHook(
HookExecutor hookExecutor,
CancellationToken cancellationToken)
{
if (!_classHookRegistered.Add(testClass))
{
return;
}

var registration = cancellationToken.Register(static state =>
{
var (pairTracker, testClass, hookExecutor) = ((AfterHookPairTracker, Type, HookExecutor))state!;
Expand Down
9 changes: 4 additions & 5 deletions TUnit.Engine/Services/TestExecution/TestCoordinator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace TUnit.Engine.Services.TestExecution;
/// </summary>
internal sealed class TestCoordinator : ITestCoordinator
{
private readonly TestExecutionGuard _executionGuard;
private readonly TestStateManager _stateManager;
private readonly ITUnitMessageBus _messageBus;
private readonly TestContextRestorer _contextRestorer;
Expand All @@ -26,7 +25,6 @@ internal sealed class TestCoordinator : ITestCoordinator
private readonly EventReceiverOrchestrator _eventReceiverOrchestrator;

public TestCoordinator(
TestExecutionGuard executionGuard,
TestStateManager stateManager,
ITUnitMessageBus messageBus,
TestContextRestorer contextRestorer,
Expand All @@ -36,7 +34,6 @@ public TestCoordinator(
TUnitFrameworkLogger logger,
EventReceiverOrchestrator eventReceiverOrchestrator)
{
_executionGuard = executionGuard;
_stateManager = stateManager;
_messageBus = messageBus;
_contextRestorer = contextRestorer;
Expand All @@ -47,9 +44,11 @@ public TestCoordinator(
_eventReceiverOrchestrator = eventReceiverOrchestrator;
}

// Dedup happens in TestRunner via its own ConcurrentDictionary<string, TCS<bool>> —
// it's the single entry point for both scheduler and dependency recursion, so a second
// guard here would just double the TCS/dict allocations per test.
public ValueTask ExecuteTestAsync(AbstractExecutableTest test, CancellationToken cancellationToken)
=> _executionGuard.TryStartExecutionAsync(test.TestId,
() => ExecuteTestInternalAsync(test, cancellationToken));
=> ExecuteTestInternalAsync(test, cancellationToken);

private async ValueTask ExecuteTestInternalAsync(AbstractExecutableTest test, CancellationToken cancellationToken)
{
Expand Down
54 changes: 0 additions & 54 deletions TUnit.Engine/Services/TestExecution/TestExecutionGuard.cs

This file was deleted.

Loading