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
4 changes: 3 additions & 1 deletion src/NServiceBus.AcceptanceTesting/ScenarioWithContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ public IScenarioWithEndpointBehavior<TContext> Done(Func<TContext, TaskCompletio

readonly List<IComponentBehavior> behaviors = [];
int componentCount = 0;
readonly IServiceCollection services = new ServiceCollection();
// The default service collection is not thread safe but the acceptance testing framework does concurrent starting of endpoints and service registration, so we need to use a thread safe collection here.
// In the future we probably want to change the framework to not allow concurrent modifications to the service collection, but for now this is a simpler change.
readonly IServiceCollection services = new ThreadSafeServiceCollection();
Task? doneTask;
readonly TaskCompletionSource<(TContext scenarioContext, CancellationToken cancellationToken)> kickOffTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
Func<TContext, TaskCompletionSource>? doneFunc;
Expand Down
3 changes: 2 additions & 1 deletion src/NServiceBus.AcceptanceTesting/Support/ScenarioRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ async Task<RunResult> PerformTestRun(CancellationToken cancellationToken)

runResult.ActiveEndpoints = [.. endpoints.Select(r => r.Name)];

runDescriptor.ServiceProvider = runDescriptor.Services.BuildServiceProvider(runDescriptor.Settings.Get<ServiceProviderOptions>());
var services = runDescriptor.Services is ThreadSafeServiceCollection safe ? safe.Unwrap() : runDescriptor.Services;
runDescriptor.ServiceProvider = services.BuildServiceProvider(runDescriptor.Settings.Get<ServiceProviderOptions>());

await PerformScenarios(endpoints, cancellationToken).ConfigureAwait(false);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
namespace NServiceBus.AcceptanceTesting.Support;

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

sealed class ThreadSafeServiceCollection : IServiceCollection
{
public int Count
{
get
{
using var _ = gate.EnterScope();
return inner.Count;
}
}

public bool IsReadOnly => inner.IsReadOnly;

public ServiceDescriptor this[int index]
{
get
{
using var _ = gate.EnterScope();
return inner[index];
}
set
{
using var _ = gate.EnterScope();
inner[index] = value;
}
}

public void Add(ServiceDescriptor item)
{
using var _ = gate.EnterScope();
inner.Add(item);
}

public void Clear()
{
using var _ = gate.EnterScope();
inner.Clear();
}

public bool Contains(ServiceDescriptor item)
{
using var _ = gate.EnterScope();
return inner.Contains(item);
}

public void CopyTo(ServiceDescriptor[] array, int arrayIndex)
{
using var _ = gate.EnterScope();
inner.CopyTo(array, arrayIndex);
}

public bool Remove(ServiceDescriptor item)
{
using var _ = gate.EnterScope();
return inner.Remove(item);
}

public IEnumerator<ServiceDescriptor> GetEnumerator()
{
using var _ = gate.EnterScope();
IEnumerable<ServiceDescriptor> snapshot = [.. inner];
return snapshot.GetEnumerator();
}

public int IndexOf(ServiceDescriptor item)
{
using var _ = gate.EnterScope();
return inner.IndexOf(item);
}

public void Insert(int index, ServiceDescriptor item)
{
using var _ = gate.EnterScope();
inner.Insert(index, item);
}

public void RemoveAt(int index)
{
using var _ = gate.EnterScope();
inner.RemoveAt(index);
}

internal IServiceCollection Unwrap() => inner;

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

readonly ServiceCollection inner = [];
readonly Lock gate = new();
}
Loading