From 3b52dd6021805f72358531fdac515ca60a9eec2b Mon Sep 17 00:00:00 2001 From: Daniel Marbach Date: Wed, 13 May 2026 20:01:26 +0200 Subject: [PATCH] Remove custom locking in KeyedServiceCollectionAdapter because the main collection is already thread-safe and we don't need to double lock since this locking would also affect production scenarios which are inheritently not thread safe anyway and the code was only introduced to make concurrent endpoint initialization thread safe. --- .../KeyedServiceCollectionAdapter.cs | 55 ++----------------- 1 file changed, 5 insertions(+), 50 deletions(-) diff --git a/src/NServiceBus.Core/Hosting/KeyedServices/KeyedServiceCollectionAdapter.cs b/src/NServiceBus.Core/Hosting/KeyedServices/KeyedServiceCollectionAdapter.cs index a9daeb0f80..1720e5b74c 100644 --- a/src/NServiceBus.Core/Hosting/KeyedServices/KeyedServiceCollectionAdapter.cs +++ b/src/NServiceBus.Core/Hosting/KeyedServices/KeyedServiceCollectionAdapter.cs @@ -7,7 +7,6 @@ namespace NServiceBus; using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Threading; using Microsoft.Extensions.DependencyInjection; class KeyedServiceCollectionAdapter : IServiceCollection @@ -19,38 +18,19 @@ public KeyedServiceCollectionAdapter(IServiceCollection inner, object serviceKey Inner = inner; ServiceKey = new KeyedServiceKey(serviceKey); - gate = sharedStates.GetValue(inner, _ => new SharedState()).Gate; } public KeyedServiceKey ServiceKey { get; } - public IServiceCollection Inner - { - get - { - using var _ = gate.EnterScope(); - return field; - } - } + public IServiceCollection Inner { get; } public ServiceDescriptor this[int index] { - get - { - using var _ = gate.EnterScope(); - return descriptors[index]; - } + get => descriptors[index]; set => throw new NotSupportedException("Replacing service descriptors is not supported for multi endpoint services."); } - public int Count - { - get - { - using var _ = gate.EnterScope(); - return descriptors.Count; - } - } + public int Count => descriptors.Count; public bool IsReadOnly => false; @@ -58,7 +38,6 @@ public void Add(ServiceDescriptor item) { ArgumentNullException.ThrowIfNull(item); - using Lock.Scope _ = gate.EnterScope(); var keyedDescriptor = EnsureKeyedDescriptor(item); descriptors.Add(keyedDescriptor); Inner.Add(keyedDescriptor); @@ -66,7 +45,6 @@ public void Add(ServiceDescriptor item) public void Clear() { - using Lock.Scope scope = gate.EnterScope(); foreach (var descriptor in descriptors) { _ = Inner.Remove(descriptor); @@ -80,27 +58,17 @@ public bool Contains(ServiceDescriptor item) { ArgumentNullException.ThrowIfNull(item); - using var _ = gate.EnterScope(); return descriptors.Contains(item); } - public void CopyTo(ServiceDescriptor[] array, int arrayIndex) - { - using var _ = gate.EnterScope(); - descriptors.CopyTo(array, arrayIndex); - } + public void CopyTo(ServiceDescriptor[] array, int arrayIndex) => descriptors.CopyTo(array, arrayIndex); - public IEnumerator GetEnumerator() - { - using var _ = gate.EnterScope(); - return ((IEnumerable)[.. descriptors]).GetEnumerator(); - } + public IEnumerator GetEnumerator() => ((IEnumerable)[.. descriptors]).GetEnumerator(); public int IndexOf(ServiceDescriptor item) { ArgumentNullException.ThrowIfNull(item); - using var _ = gate.EnterScope(); return descriptors.IndexOf(item); } @@ -110,7 +78,6 @@ public bool Remove(ServiceDescriptor item) { ArgumentNullException.ThrowIfNull(item); - using Lock.Scope scope = gate.EnterScope(); if (!descriptors.Remove(item)) { return false; @@ -123,7 +90,6 @@ public bool Remove(ServiceDescriptor item) public void RemoveAt(int index) { - using Lock.Scope scope = gate.EnterScope(); var descriptor = descriptors[index]; descriptors.RemoveAt(index); _ = Inner.Remove(descriptor); @@ -136,7 +102,6 @@ public bool ContainsService(Type serviceType) { ArgumentNullException.ThrowIfNull(serviceType); - using var _ = gate.EnterScope(); if (serviceTypeCounts.ContainsKey(serviceType)) { return true; @@ -252,14 +217,4 @@ static class UnsafeAccessor readonly List descriptors = []; readonly Dictionary serviceTypeCounts = []; readonly ConcurrentDictionary factories = new(); - readonly Lock gate; - - // We need to keep track of the locks per service collection to avoid potential deadlocks between different adapters sharing the same underlying collection. - // We use a ConditionalWeakTable here to avoid leaking memory in case service collections are not properly disposed. - static readonly ConditionalWeakTable sharedStates = []; - - sealed class SharedState - { - public Lock Gate { get; } = new(); - } } \ No newline at end of file