Skip to content
Draft
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
46 changes: 0 additions & 46 deletions src/NServiceBus.AcceptanceTesting/Support/KeyedServiceKey.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,10 @@ namespace NServiceBus
public static void DisableMessageTypeInference<T>(this NServiceBus.Serialization.SerializationExtensions<T> config)
where T : NServiceBus.Serialization.SerializationDefinition { }
}
public static class ServiceCollectionExtensions
{
public static void AddNServiceBusEndpoint(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, NServiceBus.EndpointConfiguration endpointConfiguration, object? endpointIdentifier = null) { }
}
public static class SettingsExtensions
{
public static string EndpointName(this NServiceBus.Settings.IReadOnlySettings settings) { }
Expand Down Expand Up @@ -2633,4 +2637,4 @@ namespace NServiceBus.Unicast.Transport
{
public static NServiceBus.Transport.OutgoingMessage Create(NServiceBus.MessageIntent intent) { }
}
}
}
72 changes: 72 additions & 0 deletions src/NServiceBus.Core/Hosting/EndpointStarter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#nullable enable

namespace NServiceBus;

using System;
using System.Threading;
using System.Threading.Tasks;

class EndpointStarter(
IStartableEndpointWithExternallyManagedContainer startableEndpoint,
IServiceProvider serviceProvider,
object serviceKey,
KeyedServiceCollectionAdapter services) : IEndpointStarter
{
public object LoggingSlot => serviceKey;

public async ValueTask<IEndpointInstance> GetOrStart(CancellationToken cancellationToken = default)
{
if (endpoint != null)
{
return endpoint;
}

await startSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

try
{
if (endpoint != null)
{
return endpoint;
}

LoggingBridge.RegisterMicrosoftFactoryIfAvailable(serviceProvider, LoggingSlot);
using var _ = LoggingBridge.BeginScope(LoggingSlot);

keyedServices = new KeyedServiceProviderAdapter(serviceProvider, serviceKey, services);

endpoint = await startableEndpoint.Start(keyedServices, cancellationToken).ConfigureAwait(false);

return endpoint;
}
finally
{
startSemaphore.Release();
}
}

public async ValueTask DisposeAsync()
{
if (endpoint == null || keyedServices == null)
{
return;
}

if (endpoint != null)
{
using var _ = LoggingBridge.BeginScope(LoggingSlot);
await endpoint.Stop().ConfigureAwait(false);
}

if (keyedServices != null)
{
await keyedServices.DisposeAsync().ConfigureAwait(false);
}
startSemaphore.Dispose();
}

readonly SemaphoreSlim startSemaphore = new(1, 1);

IEndpointInstance? endpoint;
KeyedServiceProviderAdapter? keyedServices;
}
57 changes: 57 additions & 0 deletions src/NServiceBus.Core/Hosting/IEndpointStarter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#nullable enable

namespace NServiceBus;

using System;
using System.Threading;
using System.Threading.Tasks;
using Logging;

interface IEndpointStarter : IAsyncDisposable, IMessageSession
{
object LoggingSlot { get; }

ValueTask<IEndpointInstance> GetOrStart(CancellationToken cancellationToken = default);

async Task IMessageSession.Send(object message, SendOptions sendOptions, CancellationToken cancellationToken)
{
using var _ = LogManager.BeginSlotScope(LoggingSlot);
var messageSession = await GetOrStart(cancellationToken).ConfigureAwait(false);
await messageSession.Send(message, sendOptions, cancellationToken).ConfigureAwait(false);
}

async Task IMessageSession.Send<T>(Action<T> messageConstructor, SendOptions sendOptions, CancellationToken cancellationToken)
{
using var _ = LogManager.BeginSlotScope(LoggingSlot);
var messageSession = await GetOrStart(cancellationToken).ConfigureAwait(false);
await messageSession.Send(messageConstructor, sendOptions, cancellationToken).ConfigureAwait(false);
}

async Task IMessageSession.Publish(object message, PublishOptions publishOptions, CancellationToken cancellationToken)
{
using var _ = LogManager.BeginSlotScope(LoggingSlot);
var messageSession = await GetOrStart(cancellationToken).ConfigureAwait(false);
await messageSession.Publish(message, publishOptions, cancellationToken).ConfigureAwait(false);
}

async Task IMessageSession.Publish<T>(Action<T> messageConstructor, PublishOptions publishOptions, CancellationToken cancellationToken)
{
using var _ = LogManager.BeginSlotScope(LoggingSlot);
var messageSession = await GetOrStart(cancellationToken).ConfigureAwait(false);
await messageSession.Publish(messageConstructor, publishOptions, cancellationToken).ConfigureAwait(false);
}

async Task IMessageSession.Subscribe(Type eventType, SubscribeOptions subscribeOptions, CancellationToken cancellationToken)
{
using var _ = LogManager.BeginSlotScope(LoggingSlot);
var messageSession = await GetOrStart(cancellationToken).ConfigureAwait(false);
await messageSession.Subscribe(eventType, subscribeOptions, cancellationToken).ConfigureAwait(false);
}

async Task IMessageSession.Unsubscribe(Type eventType, UnsubscribeOptions unsubscribeOptions, CancellationToken cancellationToken)
{
using var _ = LogManager.BeginSlotScope(LoggingSlot);
var messageSession = await GetOrStart(cancellationToken).ConfigureAwait(false);
await messageSession.Unsubscribe(eventType, unsubscribeOptions, cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace NServiceBus.AcceptanceTesting.Support;
#nullable enable

namespace NServiceBus;

using System;
using System.Collections;
Expand All @@ -20,7 +22,6 @@ public KeyedServiceCollectionAdapter(IServiceCollection inner, object serviceKey

public ServiceDescriptor this[int index]
{
// we assume no more modifications can occur at this point and therefore read without a lock
get => descriptors[index];
set => throw new NotSupportedException("Replacing service descriptors is not supported for multi endpoint services.");
}
Expand Down Expand Up @@ -59,19 +60,17 @@ public bool Contains(ServiceDescriptor item)
{
ArgumentNullException.ThrowIfNull(item);

// we assume no more modifications can occur at this point and therefore read without a lock
return descriptors.Contains(item);
}

public void CopyTo(ServiceDescriptor[] array, int arrayIndex) => descriptors.CopyTo(array, arrayIndex);

public IEnumerator<ServiceDescriptor> GetEnumerator() => descriptors.GetEnumerator(); // we assume no more modifications can occur at this point and therefore read without a lock
public IEnumerator<ServiceDescriptor> GetEnumerator() => descriptors.GetEnumerator();

public int IndexOf(ServiceDescriptor item)
{
ArgumentNullException.ThrowIfNull(item);

// we assume no more modifications can occur at this point and therefore read without a lock
return descriptors.IndexOf(item);
}

Expand Down Expand Up @@ -111,7 +110,6 @@ public bool ContainsService(Type serviceType)
{
ArgumentNullException.ThrowIfNull(serviceType);

// we assume no more modifications can occur at this point and therefore read without a lock
if (serviceTypes.Contains(serviceType))
{
return true;
Expand Down Expand Up @@ -154,7 +152,6 @@ ServiceDescriptor EnsureKeyedDescriptor(ServiceDescriptor descriptor)
return descriptor.Lifetime == ServiceLifetime.Singleton ? ActivatorUtilities.CreateInstance(keyedProvider, descriptor.KeyedImplementationType) :
factories.GetOrAdd(descriptor.KeyedImplementationType, type => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes))(keyedProvider, []);
}, descriptor.Lifetime);
// Crazy hack to work around generic constraint checks
UnsafeAccessor.GetImplementationType(keyedDescriptor) = descriptor.KeyedImplementationType;
}
else
Expand Down Expand Up @@ -187,7 +184,6 @@ ServiceDescriptor EnsureKeyedDescriptor(ServiceDescriptor descriptor)
return descriptor.Lifetime == ServiceLifetime.Singleton ? ActivatorUtilities.CreateInstance(keyedProvider, descriptor.ImplementationType) :
factories.GetOrAdd(descriptor.ImplementationType, type => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes))(keyedProvider, []);
}, descriptor.Lifetime);
// Crazy hack to work around generic constraint checks
UnsafeAccessor.GetImplementationType(keyedDescriptor) = descriptor.ImplementationType;
}
else
Expand Down
99 changes: 99 additions & 0 deletions src/NServiceBus.Core/Hosting/KeyedServices/KeyedServiceKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#nullable enable

namespace NServiceBus;

using System;

/// <summary>
/// Represents a composite key used for resolving services in a keyed service collection,
/// combining a base key with an optional service-specific key.
/// </summary>
public sealed class KeyedServiceKey
{
/// <summary>
/// Represents a composite key used for resolving services in a keyed service collection.
/// Combines a base key with an optional service-specific key.
/// </summary>
public KeyedServiceKey(object baseKey, object? serviceKey = null)
{
if (baseKey is KeyedServiceKey key)
{
BaseKey = key.BaseKey;
ServiceKey = key.ServiceKey;

if (serviceKey is not null)
{
ServiceKey = serviceKey;
}
}
else
{
BaseKey = baseKey;
ServiceKey = serviceKey;
}
}

/// <summary>
/// Gets the base key component of the composite key, which is used to identify a service
/// in a keyed service collection. This value is mandatory and serves as the primary
/// identifier in the composite key structure.
/// </summary>
public object BaseKey { get; }

/// <summary>
/// Gets the service-specific key component of the composite key, which is optional and used to
/// further differentiate services within the same base key in a keyed service collection.
/// </summary>
public object? ServiceKey { get; }

/// <summary>
/// Determines whether the specified object is equal to the current instance of the KeyedServiceKey.
/// </summary>
/// <param name="obj">The object to compare with the current KeyedServiceKey, or <c>null</c>.</param>
/// <returns>
/// <c>true</c> if the specified object is equal to the current KeyedServiceKey; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object? obj)
{
if (obj is KeyedServiceKey other)
{
return Equals(BaseKey, other.BaseKey) && Equals(ServiceKey, other.ServiceKey);
}
return Equals(BaseKey, obj);
}

/// <summary>
/// Returns a hash code for the current instance of the KeyedServiceKey.
/// Combines the hash code of the base key and, if present, the service-specific key.
/// </summary>
/// <returns>
/// An integer representing the hash code of the current KeyedServiceKey instance.
/// </returns>
public override int GetHashCode() => ServiceKey == null ? BaseKey.GetHashCode() : HashCode.Combine(BaseKey, ServiceKey);

/// <summary>
/// Returns a string representation of the current KeyedServiceKey instance.
/// If the service-specific key is not present, returns the string representation
/// of the base key. Otherwise, returns a composite string representation of both
/// the base key and the service-specific key.
/// </summary>
/// <returns>
/// A string representation of the current instance, including both the base key
/// and the service-specific key, if present.
/// </returns>
public override string? ToString() => ServiceKey == null ? BaseKey.ToString() : $"({BaseKey}, {ServiceKey})";

/// <summary>
/// Creates a new instance of the <see cref="KeyedServiceKey"/> with the specified base key
/// and a predefined value indicating a wildcard key.
/// </summary>
/// <param name="baseKey">The base key to use for the composite service key.</param>
/// <returns>A <see cref="KeyedServiceKey"/> representing the wildcard configuration with the provided base key.</returns>
public static KeyedServiceKey AnyKey(object baseKey) => new(baseKey, Any);

/// <summary>
/// Represents a constant wildcard value used in <see cref="KeyedServiceKey"/> to signify a match against
/// any service-specific key within the keyed service collection.
/// </summary>
public const string Any = "_______<ANY>_______";
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace NServiceBus.AcceptanceTesting.Support;
#nullable enable

namespace NServiceBus;

using System;
using System.Collections;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace NServiceBus.AcceptanceTesting.Support;
#nullable enable

namespace NServiceBus;

using System;
using System.Threading.Tasks;
Expand Down
Loading
Loading