Implemented QueueArgumentsAttribute (untested)
This commit is contained in:
parent
c75f893da8
commit
7143ad3c2f
@ -24,7 +24,7 @@ namespace ExampleLib
|
||||
private readonly IDependencyContainer dependencyResolver;
|
||||
private readonly int expectedDoneCount;
|
||||
private int doneCount;
|
||||
private readonly TaskCompletionSource<bool> doneSignal = new TaskCompletionSource<bool>();
|
||||
private readonly TaskCompletionSource<bool> doneSignal = new();
|
||||
|
||||
|
||||
/// <param name="dependencyResolver">Uses Tapeti's IDependencyContainer interface so you can easily switch an example to your favourite IoC container</param>
|
||||
|
@ -304,7 +304,7 @@ namespace Tapeti.Flow.Default
|
||||
|
||||
private readonly ITapetiConfig config;
|
||||
private readonly FlowProvider flowProvider;
|
||||
private readonly List<RequestInfo> requests = new List<RequestInfo>();
|
||||
private readonly List<RequestInfo> requests = new();
|
||||
|
||||
|
||||
public ParallelRequestBuilder(ITapetiConfig config, FlowProvider flowProvider)
|
||||
|
@ -29,9 +29,9 @@ namespace Tapeti.Flow.Default
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, CachedFlowState> flowStates = new ConcurrentDictionary<Guid, CachedFlowState>();
|
||||
private readonly ConcurrentDictionary<Guid, Guid> continuationLookup = new ConcurrentDictionary<Guid, Guid>();
|
||||
private readonly LockCollection<Guid> locks = new LockCollection<Guid>(EqualityComparer<Guid>.Default);
|
||||
private readonly ConcurrentDictionary<Guid, CachedFlowState> flowStates = new();
|
||||
private readonly ConcurrentDictionary<Guid, Guid> continuationLookup = new();
|
||||
private readonly LockCollection<Guid> locks = new(EqualityComparer<Guid>.Default);
|
||||
private HashSet<string> validatedMethods;
|
||||
|
||||
private readonly IFlowRepository repository;
|
||||
|
@ -60,7 +60,7 @@ namespace Tapeti.Flow.FlowHelpers
|
||||
internal volatile LockItem Next;
|
||||
|
||||
private readonly Dictionary<T, LockItem> locks;
|
||||
private readonly TaskCompletionSource<IDisposable> tcs = new TaskCompletionSource<IDisposable>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly TaskCompletionSource<IDisposable> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly T key;
|
||||
|
||||
public LockItem(Dictionary<T, LockItem> locks, T key)
|
||||
|
@ -18,7 +18,7 @@ namespace Tapeti.Flow.FlowHelpers
|
||||
}
|
||||
|
||||
|
||||
private static readonly Regex DeserializeRegex = new Regex("^(?<method>.+?)@(?<assembly>.+?):(?<type>.+?)$");
|
||||
private static readonly Regex DeserializeRegex = new("^(?<method>.+?)@(?<assembly>.+?):(?<type>.+?)$");
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
@ -10,7 +10,7 @@ namespace Tapeti.Serilog.Default
|
||||
public class DiagnosticContext : IDiagnosticContext
|
||||
{
|
||||
private readonly global::Serilog.ILogger logger;
|
||||
private readonly List<LogEventProperty> properties = new List<LogEventProperty>();
|
||||
private readonly List<LogEventProperty> properties = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
@ -129,10 +129,11 @@ namespace Tapeti.Serilog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void QueueExistsWarning(string queueName, Dictionary<string, string> arguments)
|
||||
public void QueueExistsWarning(string queueName, IReadOnlyDictionary<string, string> existingArguments, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
seriLogger.Warning("Tapeti: durable queue {queueName} exists with incompatible x-arguments ({arguments}) and will not be redeclared, queue will be consumed as-is",
|
||||
seriLogger.Warning("Tapeti: durable queue {queueName} exists with incompatible x-arguments ({existingArguments} vs. {arguments}) and will not be redeclared, queue will be consumed as-is",
|
||||
queueName,
|
||||
existingArguments,
|
||||
arguments);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,12 @@
|
||||
<NoWarn>1701;1702</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Core\**" />
|
||||
<EmbeddedResource Remove="Core\**" />
|
||||
<None Remove="Core\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
@ -22,8 +28,4 @@
|
||||
<ProjectReference Include="..\Tapeti\Tapeti.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Core\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -33,7 +33,7 @@ namespace Tapeti.Transient
|
||||
/// <inheritdoc />
|
||||
public async ValueTask Apply(IBindingTarget target)
|
||||
{
|
||||
QueueName = await target.BindDynamicDirect(dynamicQueuePrefix);
|
||||
QueueName = await target.BindDynamicDirect(dynamicQueuePrefix, null);
|
||||
router.TransientResponseQueueName = QueueName;
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ namespace Tapeti.Transient
|
||||
internal class TransientRouter
|
||||
{
|
||||
private readonly int defaultTimeoutMs;
|
||||
private readonly ConcurrentDictionary<Guid, TaskCompletionSource<object>> map = new ConcurrentDictionary<Guid, TaskCompletionSource<object>>();
|
||||
private readonly ConcurrentDictionary<Guid, TaskCompletionSource<object>> map = new();
|
||||
|
||||
/// <summary>
|
||||
/// The generated name of the dynamic queue to which responses should be sent.
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tapeti.Config
|
||||
@ -80,7 +81,8 @@ namespace Tapeti.Config
|
||||
/// </summary>
|
||||
/// <param name="messageClass">The message class to be bound to the queue</param>
|
||||
/// <param name="queueName">The name of the durable queue</param>
|
||||
ValueTask BindDurable(Type messageClass, string queueName);
|
||||
/// <param name="arguments">Optional arguments</param>
|
||||
ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Binds the messageClass to a dynamic auto-delete queue.
|
||||
@ -91,15 +93,17 @@ namespace Tapeti.Config
|
||||
/// </remarks>
|
||||
/// <param name="messageClass">The message class to be bound to the queue</param>
|
||||
/// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param>
|
||||
/// <param name="arguments">Optional arguments</param>
|
||||
/// <returns>The generated name of the dynamic queue</returns>
|
||||
ValueTask<string> BindDynamic(Type messageClass, string queuePrefix = null);
|
||||
ValueTask<string> BindDynamic(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Declares a durable queue but does not add a binding for a messageClass' routing key.
|
||||
/// Used for direct-to-queue messages.
|
||||
/// </summary>
|
||||
/// <param name="queueName">The name of the durable queue</param>
|
||||
ValueTask BindDurableDirect(string queueName);
|
||||
/// <param name="arguments">Optional arguments</param>
|
||||
ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Declares a dynamic queue but does not add a binding for a messageClass' routing key.
|
||||
@ -107,16 +111,18 @@ namespace Tapeti.Config
|
||||
/// </summary>
|
||||
/// <param name="messageClass">The message class which will be handled on the queue. It is not actually bound to the queue.</param>
|
||||
/// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param>
|
||||
/// <param name="arguments">Optional arguments</param>
|
||||
/// <returns>The generated name of the dynamic queue</returns>
|
||||
ValueTask<string> BindDynamicDirect(Type messageClass = null, string queuePrefix = null);
|
||||
ValueTask<string> BindDynamicDirect(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Declares a dynamic queue but does not add a binding for a messageClass' routing key.
|
||||
/// Used for direct-to-queue messages. Guarantees a unique queue.
|
||||
/// </summary>
|
||||
/// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param>
|
||||
/// <param name="arguments">Optional arguments</param>
|
||||
/// <returns>The generated name of the dynamic queue</returns>
|
||||
ValueTask<string> BindDynamicDirect(string queuePrefix = null);
|
||||
ValueTask<string> BindDynamicDirect(string queuePrefix, IReadOnlyDictionary<string, string> arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Marks the specified durable queue as having an obsolete binding. If after all bindings have subscribed, the queue only contains obsolete
|
||||
|
@ -74,11 +74,11 @@ namespace Tapeti.Connection
|
||||
/// <summary>
|
||||
/// Starts a consumer for the specified queue, using the provided bindings to handle messages.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
/// <param name="queueName"></param>
|
||||
/// <param name="consumer">The consumer implementation which will receive the messages from the queue</param>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
/// <returns>The consumer tag as returned by BasicConsume.</returns>
|
||||
Task<TapetiConsumerTag> Consume(CancellationToken cancellationToken, string queueName, IConsumer consumer);
|
||||
Task<TapetiConsumerTag> Consume(string queueName, IConsumer consumer, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Stops the consumer with the specified tag.
|
||||
@ -89,40 +89,43 @@ namespace Tapeti.Connection
|
||||
/// <summary>
|
||||
/// Creates a durable queue if it does not already exist, and updates the bindings.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
/// <param name="queueName">The name of the queue to create</param>
|
||||
/// <param name="bindings">A list of bindings. Any bindings already on the queue which are not in this list will be removed</param>
|
||||
Task DurableQueueDeclare(CancellationToken cancellationToken, string queueName, IEnumerable<QueueBinding> bindings);
|
||||
/// <param name="arguments">Optional arguments</param>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
Task DurableQueueDeclare(string queueName, IEnumerable<QueueBinding> bindings, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies a durable queue exists. Will raise an exception if it does not.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
/// <param name="queueName">The name of the queue to verify</param>
|
||||
Task DurableQueueVerify(CancellationToken cancellationToken, string queueName);
|
||||
/// <param name="arguments">Optional arguments</param>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
Task DurableQueueVerify(string queueName, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a durable queue.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
/// <param name="queueName">The name of the queue to delete</param>
|
||||
/// <param name="onlyIfEmpty">If true, the queue will only be deleted if it is empty otherwise all bindings will be removed. If false, the queue is deleted even if there are queued messages.</param>
|
||||
Task DurableQueueDelete(CancellationToken cancellationToken, string queueName, bool onlyIfEmpty = true);
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
Task DurableQueueDelete(string queueName, bool onlyIfEmpty, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dynamic queue.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
/// <param name="queuePrefix">An optional prefix for the dynamic queue's name. If not provided, RabbitMQ's default logic will be used to create an amq.gen queue.</param>
|
||||
Task<string> DynamicQueueDeclare(CancellationToken cancellationToken, string queuePrefix = null);
|
||||
/// <param name="arguments">Optional arguments</param>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
Task<string> DynamicQueueDeclare(string queuePrefix, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Add a binding to a dynamic queue.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
/// <param name="queueName">The name of the dynamic queue previously created using DynamicQueueDeclare</param>
|
||||
/// <param name="binding">The binding to add to the dynamic queue</param>
|
||||
Task DynamicQueueBind(CancellationToken cancellationToken, string queueName, QueueBinding binding);
|
||||
/// <param name="cancellationToken">Cancelled when the connection is lost</param>
|
||||
Task DynamicQueueBind(string queueName, QueueBinding binding, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection to RabbitMQ gracefully.
|
||||
|
@ -20,7 +20,7 @@ namespace Tapeti.Connection
|
||||
internal class TapetiChannel
|
||||
{
|
||||
private readonly Func<IModel> modelFactory;
|
||||
private readonly object taskQueueLock = new object();
|
||||
private readonly object taskQueueLock = new();
|
||||
private SingleThreadTaskQueue taskQueue;
|
||||
private readonly ModelProvider modelProvider;
|
||||
|
||||
|
@ -13,6 +13,7 @@ using RabbitMQ.Client.Exceptions;
|
||||
using Tapeti.Config;
|
||||
using Tapeti.Default;
|
||||
using Tapeti.Exceptions;
|
||||
using Tapeti.Helpers;
|
||||
|
||||
namespace Tapeti.Connection
|
||||
{
|
||||
@ -50,7 +51,7 @@ namespace Tapeti.Connection
|
||||
private readonly HttpClient managementClient;
|
||||
|
||||
// These fields must be locked using connectionLock
|
||||
private readonly object connectionLock = new object();
|
||||
private readonly object connectionLock = new();
|
||||
private long connectionReference;
|
||||
private RabbitMQ.Client.IConnection connection;
|
||||
private IModel consumeChannelModel;
|
||||
@ -61,12 +62,12 @@ namespace Tapeti.Connection
|
||||
|
||||
// These fields are for use in a single TapetiChannel's queue only!
|
||||
private ulong lastDeliveryTag;
|
||||
private readonly HashSet<string> deletedQueues = new HashSet<string>();
|
||||
private readonly HashSet<string> deletedQueues = new();
|
||||
|
||||
// These fields must be locked using confirmLock, since the callbacks for BasicAck/BasicReturn can run in a different thread
|
||||
private readonly object confirmLock = new object();
|
||||
private readonly Dictionary<ulong, ConfirmMessageInfo> confirmMessages = new Dictionary<ulong, ConfirmMessageInfo>();
|
||||
private readonly Dictionary<string, ReturnInfo> returnRoutingKeys = new Dictionary<string, ReturnInfo>();
|
||||
private readonly object confirmLock = new();
|
||||
private readonly Dictionary<ulong, ConfirmMessageInfo> confirmMessages = new();
|
||||
private readonly Dictionary<string, ReturnInfo> returnRoutingKeys = new();
|
||||
|
||||
|
||||
private class ConfirmMessageInfo
|
||||
@ -198,7 +199,7 @@ namespace Tapeti.Connection
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TapetiConsumerTag> Consume(CancellationToken cancellationToken, string queueName, IConsumer consumer)
|
||||
public async Task<TapetiConsumerTag> Consume(string queueName, IConsumer consumer, CancellationToken cancellationToken)
|
||||
{
|
||||
if (deletedQueues.Contains(queueName))
|
||||
return null;
|
||||
@ -285,7 +286,7 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
|
||||
private async Task<bool> GetDurableQueueDeclareRequired(string queueName)
|
||||
private async Task<bool> GetDurableQueueDeclareRequired(string queueName, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
var existingQueue = await GetQueueInfo(queueName);
|
||||
if (existingQueue == null)
|
||||
@ -294,18 +295,22 @@ namespace Tapeti.Connection
|
||||
if (!existingQueue.Durable || existingQueue.AutoDelete || existingQueue.Exclusive)
|
||||
throw new InvalidOperationException($"Durable queue {queueName} already exists with incompatible parameters, durable = {existingQueue.Durable} (expected True), autoDelete = {existingQueue.AutoDelete} (expected False), exclusive = {existingQueue.Exclusive} (expected False)");
|
||||
|
||||
if (existingQueue.Arguments.Count <= 0)
|
||||
if (arguments == null && existingQueue.Arguments.Count == 0)
|
||||
return true;
|
||||
|
||||
(logger as IBindingLogger)?.QueueExistsWarning(queueName, existingQueue.Arguments);
|
||||
|
||||
if (existingQueue.Arguments.NullSafeSameValues(arguments))
|
||||
return true;
|
||||
|
||||
(logger as IBindingLogger)?.QueueExistsWarning(queueName, existingQueue.Arguments, arguments);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DurableQueueDeclare(CancellationToken cancellationToken, string queueName, IEnumerable<QueueBinding> bindings)
|
||||
public async Task DurableQueueDeclare(string queueName, IEnumerable<QueueBinding> bindings, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
var declareRequired = await GetDurableQueueDeclareRequired(queueName);
|
||||
var declareRequired = await GetDurableQueueDeclareRequired(queueName, arguments);
|
||||
|
||||
var existingBindings = (await GetQueueBindings(queueName)).ToList();
|
||||
var currentBindings = bindings.ToList();
|
||||
@ -319,7 +324,7 @@ namespace Tapeti.Connection
|
||||
if (declareRequired)
|
||||
{
|
||||
bindingLogger?.QueueDeclare(queueName, true, false);
|
||||
channel.QueueDeclare(queueName, true, false, false);
|
||||
channel.QueueDeclare(queueName, true, false, false, GetDeclareArguments(arguments));
|
||||
}
|
||||
|
||||
foreach (var binding in currentBindings.Except(existingBindings))
|
||||
@ -337,10 +342,20 @@ namespace Tapeti.Connection
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DurableQueueVerify(CancellationToken cancellationToken, string queueName)
|
||||
|
||||
private static IDictionary<string, object> GetDeclareArguments(IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
if (!await GetDurableQueueDeclareRequired(queueName))
|
||||
if (arguments == null || arguments.Count == 0)
|
||||
return null;
|
||||
|
||||
return arguments.ToDictionary(p => p.Key, p => (object)Encoding.UTF8.GetBytes(p.Value));
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DurableQueueVerify(string queueName, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await GetDurableQueueDeclareRequired(queueName, arguments))
|
||||
return;
|
||||
|
||||
await GetTapetiChannel(TapetiChannelType.Consume).Queue(channel =>
|
||||
@ -355,7 +370,7 @@ namespace Tapeti.Connection
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DurableQueueDelete(CancellationToken cancellationToken, string queueName, bool onlyIfEmpty = true)
|
||||
public async Task DurableQueueDelete(string queueName, bool onlyIfEmpty, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!onlyIfEmpty)
|
||||
{
|
||||
@ -440,7 +455,7 @@ namespace Tapeti.Connection
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> DynamicQueueDeclare(CancellationToken cancellationToken, string queuePrefix = null)
|
||||
public async Task<string> DynamicQueueDeclare(string queuePrefix, IReadOnlyDictionary<string, string> arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
string queueName = null;
|
||||
var bindingLogger = logger as IBindingLogger;
|
||||
@ -454,11 +469,11 @@ namespace Tapeti.Connection
|
||||
{
|
||||
queueName = queuePrefix + "." + Guid.NewGuid().ToString("N");
|
||||
bindingLogger?.QueueDeclare(queueName, false, false);
|
||||
channel.QueueDeclare(queueName);
|
||||
channel.QueueDeclare(queueName, arguments: GetDeclareArguments(arguments));
|
||||
}
|
||||
else
|
||||
{
|
||||
queueName = channel.QueueDeclare().QueueName;
|
||||
queueName = channel.QueueDeclare(arguments: GetDeclareArguments(arguments)).QueueName;
|
||||
bindingLogger?.QueueDeclare(queueName, false, false);
|
||||
}
|
||||
});
|
||||
@ -467,7 +482,7 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DynamicQueueBind(CancellationToken cancellationToken, string queueName, QueueBinding binding)
|
||||
public async Task DynamicQueueBind(string queueName, QueueBinding binding, CancellationToken cancellationToken)
|
||||
{
|
||||
await GetTapetiChannel(TapetiChannelType.Consume).Queue(channel =>
|
||||
{
|
||||
@ -523,7 +538,7 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
|
||||
private static readonly List<HttpStatusCode> TransientStatusCodes = new List<HttpStatusCode>()
|
||||
private static readonly List<HttpStatusCode> TransientStatusCodes = new()
|
||||
{
|
||||
HttpStatusCode.GatewayTimeout,
|
||||
HttpStatusCode.RequestTimeout,
|
||||
@ -675,7 +690,7 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
|
||||
private readonly HashSet<string> declaredExchanges = new HashSet<string>();
|
||||
private readonly HashSet<string> declaredExchanges = new();
|
||||
|
||||
private void DeclareExchange(IModel channel, string exchange)
|
||||
{
|
||||
@ -842,7 +857,7 @@ namespace Tapeti.Connection
|
||||
GetTapetiChannel(TapetiChannelType.Consume).QueueRetryable(_ => { });
|
||||
};
|
||||
|
||||
capturedPublishChannelModel.ModelShutdown += (sender, args) =>
|
||||
capturedPublishChannelModel.ModelShutdown += (_, _) =>
|
||||
{
|
||||
lock (connectionLock)
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tapeti.Config;
|
||||
using Tapeti.Helpers;
|
||||
|
||||
namespace Tapeti.Connection
|
||||
{
|
||||
@ -13,7 +14,7 @@ namespace Tapeti.Connection
|
||||
private readonly Func<ITapetiClient> clientFactory;
|
||||
private readonly ITapetiConfig config;
|
||||
private bool consuming;
|
||||
private readonly List<TapetiConsumerTag> consumerTags = new List<TapetiConsumerTag>();
|
||||
private readonly List<TapetiConsumerTag> consumerTags = new();
|
||||
|
||||
private CancellationTokenSource initializeCancellationTokenSource;
|
||||
|
||||
@ -149,7 +150,7 @@ namespace Tapeti.Connection
|
||||
var queueName = group.Key;
|
||||
var consumer = new TapetiConsumer(cancellationToken, config, queueName, group);
|
||||
|
||||
return await clientFactory().Consume(cancellationToken, queueName, consumer);
|
||||
return await clientFactory().Consume(queueName, consumer, cancellationToken);
|
||||
}))).Where(t => t != null));
|
||||
}
|
||||
|
||||
@ -165,9 +166,10 @@ namespace Tapeti.Connection
|
||||
{
|
||||
public string QueueName;
|
||||
public List<Type> MessageClasses;
|
||||
public IReadOnlyDictionary<string, string> Arguments;
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, List<DynamicQueueInfo>> dynamicQueues = new Dictionary<string, List<DynamicQueueInfo>>();
|
||||
private readonly Dictionary<string, List<DynamicQueueInfo>> dynamicQueues = new();
|
||||
|
||||
|
||||
protected CustomBindingTarget(Func<ITapetiClient> clientFactory, IRoutingKeyStrategy routingKeyStrategy, IExchangeStrategy exchangeStrategy, CancellationToken cancellationToken)
|
||||
@ -185,38 +187,38 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
|
||||
public abstract ValueTask BindDurable(Type messageClass, string queueName);
|
||||
public abstract ValueTask BindDurableDirect(string queueName);
|
||||
public abstract ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments);
|
||||
public abstract ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments);
|
||||
public abstract ValueTask BindDurableObsolete(string queueName);
|
||||
|
||||
|
||||
public async ValueTask<string> BindDynamic(Type messageClass, string queuePrefix = null)
|
||||
public async ValueTask<string> BindDynamic(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
var result = await DeclareDynamicQueue(messageClass, queuePrefix);
|
||||
var result = await DeclareDynamicQueue(messageClass, queuePrefix, arguments);
|
||||
if (!result.IsNewMessageClass)
|
||||
return result.QueueName;
|
||||
|
||||
var routingKey = RoutingKeyStrategy.GetRoutingKey(messageClass);
|
||||
var exchange = ExchangeStrategy.GetExchange(messageClass);
|
||||
|
||||
await ClientFactory().DynamicQueueBind(CancellationToken, result.QueueName, new QueueBinding(exchange, routingKey));
|
||||
await ClientFactory().DynamicQueueBind(result.QueueName, new QueueBinding(exchange, routingKey), CancellationToken);
|
||||
|
||||
return result.QueueName;
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask<string> BindDynamicDirect(Type messageClass, string queuePrefix = null)
|
||||
public async ValueTask<string> BindDynamicDirect(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
var result = await DeclareDynamicQueue(messageClass, queuePrefix);
|
||||
var result = await DeclareDynamicQueue(messageClass, queuePrefix, arguments);
|
||||
return result.QueueName;
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask<string> BindDynamicDirect(string queuePrefix = null)
|
||||
public async ValueTask<string> BindDynamicDirect(string queuePrefix, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
// If we don't know the routing key, always create a new queue to ensure there is no overlap.
|
||||
// Keep it out of the dynamicQueues dictionary, so it can't be re-used later on either.
|
||||
return await ClientFactory().DynamicQueueDeclare(CancellationToken, queuePrefix);
|
||||
return await ClientFactory().DynamicQueueDeclare(queuePrefix, arguments, CancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@ -226,7 +228,7 @@ namespace Tapeti.Connection
|
||||
public bool IsNewMessageClass;
|
||||
}
|
||||
|
||||
private async Task<DeclareDynamicQueueResult> DeclareDynamicQueue(Type messageClass, string queuePrefix)
|
||||
private async Task<DeclareDynamicQueueResult> DeclareDynamicQueue(Type messageClass, string queuePrefix, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
// Group by prefix
|
||||
var key = queuePrefix ?? "";
|
||||
@ -241,7 +243,7 @@ namespace Tapeti.Connection
|
||||
foreach (var existingQueueInfo in prefixQueues)
|
||||
{
|
||||
// ReSharper disable once InvertIf
|
||||
if (!existingQueueInfo.MessageClasses.Contains(messageClass))
|
||||
if (!existingQueueInfo.MessageClasses.Contains(messageClass) && existingQueueInfo.Arguments.NullSafeSameValues(arguments))
|
||||
{
|
||||
// Allow this routing key in the existing dynamic queue
|
||||
var result = new DeclareDynamicQueueResult
|
||||
@ -258,11 +260,12 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
// Declare a new queue
|
||||
var queueName = await ClientFactory().DynamicQueueDeclare(CancellationToken, queuePrefix);
|
||||
var queueName = await ClientFactory().DynamicQueueDeclare(queuePrefix, arguments, CancellationToken);
|
||||
var queueInfo = new DynamicQueueInfo
|
||||
{
|
||||
QueueName = queueName,
|
||||
MessageClasses = new List<Type> { messageClass }
|
||||
MessageClasses = new List<Type> { messageClass },
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
prefixQueues.Add(queueInfo);
|
||||
@ -278,8 +281,15 @@ namespace Tapeti.Connection
|
||||
|
||||
private class DeclareDurableQueuesBindingTarget : CustomBindingTarget
|
||||
{
|
||||
private readonly Dictionary<string, List<Type>> durableQueues = new Dictionary<string, List<Type>>();
|
||||
private readonly HashSet<string> obsoleteDurableQueues = new HashSet<string>();
|
||||
private struct DurableQueueInfo
|
||||
{
|
||||
public List<Type> MessageClasses;
|
||||
public IReadOnlyDictionary<string, string> Arguments;
|
||||
}
|
||||
|
||||
|
||||
private readonly Dictionary<string, DurableQueueInfo> durableQueues = new();
|
||||
private readonly HashSet<string> obsoleteDurableQueues = new();
|
||||
|
||||
|
||||
public DeclareDurableQueuesBindingTarget(Func<ITapetiClient> clientFactory, IRoutingKeyStrategy routingKeyStrategy, IExchangeStrategy exchangeStrategy, CancellationToken cancellationToken) : base(clientFactory, routingKeyStrategy, exchangeStrategy, cancellationToken)
|
||||
@ -287,29 +297,50 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
|
||||
public override ValueTask BindDurable(Type messageClass, string queueName)
|
||||
public override ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
// Collect the message classes per queue so we can determine afterwards
|
||||
// if any of the bindings currently set on the durable queue are no
|
||||
// longer valid and should be removed.
|
||||
if (!durableQueues.TryGetValue(queueName, out var messageClasses))
|
||||
if (!durableQueues.TryGetValue(queueName, out var durableQueueInfo))
|
||||
{
|
||||
durableQueues.Add(queueName, new List<Type>
|
||||
durableQueues.Add(queueName, new DurableQueueInfo
|
||||
{
|
||||
messageClass
|
||||
MessageClasses = new List<Type>
|
||||
{
|
||||
messageClass
|
||||
},
|
||||
Arguments = arguments
|
||||
});
|
||||
}
|
||||
else if (!messageClasses.Contains(messageClass))
|
||||
messageClasses.Add(messageClass);
|
||||
else
|
||||
{
|
||||
if (!durableQueueInfo.Arguments.NullSafeSameValues(arguments))
|
||||
throw new TopologyConfigurationException($"Multiple conflicting QueueArguments attributes specified for queue {queueName}");
|
||||
|
||||
if (!durableQueueInfo.MessageClasses.Contains(messageClass))
|
||||
durableQueueInfo.MessageClasses.Add(messageClass);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override ValueTask BindDurableDirect(string queueName)
|
||||
public override ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
if (!durableQueues.ContainsKey(queueName))
|
||||
durableQueues.Add(queueName, new List<Type>());
|
||||
if (!durableQueues.TryGetValue(queueName, out var durableQueueInfo))
|
||||
{
|
||||
durableQueues.Add(queueName, new DurableQueueInfo
|
||||
{
|
||||
MessageClasses = new List<Type>(),
|
||||
Arguments = arguments
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!durableQueueInfo.Arguments.NullSafeSameValues(arguments))
|
||||
throw new TopologyConfigurationException($"Multiple conflicting QueueArguments attributes specified for queue {queueName}");
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
@ -334,7 +365,7 @@ namespace Tapeti.Connection
|
||||
{
|
||||
await Task.WhenAll(durableQueues.Select(async queue =>
|
||||
{
|
||||
var bindings = queue.Value.Select(messageClass =>
|
||||
var bindings = queue.Value.MessageClasses.Select(messageClass =>
|
||||
{
|
||||
var exchange = ExchangeStrategy.GetExchange(messageClass);
|
||||
var routingKey = RoutingKeyStrategy.GetRoutingKey(messageClass);
|
||||
@ -342,7 +373,7 @@ namespace Tapeti.Connection
|
||||
return new QueueBinding(exchange, routingKey);
|
||||
});
|
||||
|
||||
await client.DurableQueueDeclare(CancellationToken, queue.Key, bindings);
|
||||
await client.DurableQueueDeclare(queue.Key, bindings, queue.Value.Arguments, CancellationToken);
|
||||
}));
|
||||
}
|
||||
|
||||
@ -351,7 +382,7 @@ namespace Tapeti.Connection
|
||||
{
|
||||
await Task.WhenAll(obsoleteDurableQueues.Except(durableQueues.Keys).Select(async queue =>
|
||||
{
|
||||
await client.DurableQueueDelete(CancellationToken, queue);
|
||||
await client.DurableQueueDelete(queue, true, CancellationToken);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -359,7 +390,7 @@ namespace Tapeti.Connection
|
||||
|
||||
private class PassiveDurableQueuesBindingTarget : CustomBindingTarget
|
||||
{
|
||||
private readonly HashSet<string> durableQueues = new HashSet<string>();
|
||||
private readonly HashSet<string> durableQueues = new();
|
||||
|
||||
|
||||
public PassiveDurableQueuesBindingTarget(Func<ITapetiClient> clientFactory, IRoutingKeyStrategy routingKeyStrategy, IExchangeStrategy exchangeStrategy, CancellationToken cancellationToken) : base(clientFactory, routingKeyStrategy, exchangeStrategy, cancellationToken)
|
||||
@ -367,14 +398,14 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
|
||||
public override async ValueTask BindDurable(Type messageClass, string queueName)
|
||||
public override async ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
await VerifyDurableQueue(queueName);
|
||||
await VerifyDurableQueue(queueName, arguments);
|
||||
}
|
||||
|
||||
public override async ValueTask BindDurableDirect(string queueName)
|
||||
public override async ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
await VerifyDurableQueue(queueName);
|
||||
await VerifyDurableQueue(queueName, arguments);
|
||||
}
|
||||
|
||||
public override ValueTask BindDurableObsolete(string queueName)
|
||||
@ -383,12 +414,12 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
|
||||
private async Task VerifyDurableQueue(string queueName)
|
||||
private async Task VerifyDurableQueue(string queueName, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
if (!durableQueues.Add(queueName))
|
||||
return;
|
||||
|
||||
await ClientFactory().DurableQueueVerify(CancellationToken, queueName);
|
||||
await ClientFactory().DurableQueueVerify(queueName, arguments, CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@ -400,12 +431,12 @@ namespace Tapeti.Connection
|
||||
}
|
||||
|
||||
|
||||
public override ValueTask BindDurable(Type messageClass, string queueName)
|
||||
public override ValueTask BindDurable(Type messageClass, string queueName, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
public override ValueTask BindDurableDirect(string queueName)
|
||||
public override ValueTask BindDurableDirect(string queueName, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
@ -81,7 +81,13 @@ namespace Tapeti.Default
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void QueueExistsWarning(string queueName, Dictionary<string, string> arguments)
|
||||
public void QueueExistsWarning(string queueName, IReadOnlyDictionary<string, string> existingArguments, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
Console.WriteLine($"[Tapeti] Durable queue {queueName} exists with incompatible x-arguments ({GetArgumentsText(existingArguments)} vs. {GetArgumentsText(arguments)}) and will not be redeclared, queue will be consumed as-is");
|
||||
}
|
||||
|
||||
|
||||
private static string GetArgumentsText(IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
var argumentsText = new StringBuilder();
|
||||
foreach (var pair in arguments)
|
||||
@ -91,10 +97,11 @@ namespace Tapeti.Default
|
||||
|
||||
argumentsText.Append($"{pair.Key} = {pair.Value}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"[Tapeti] Durable queue {queueName} exists with incompatible x-arguments ({argumentsText}) and will not be redeclared, queue will be consumed as-is");
|
||||
|
||||
return argumentsText.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void QueueBind(string queueName, bool durable, string exchange, string routingKey)
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ namespace Tapeti.Default
|
||||
internal class ControllerBindingContext : IControllerBindingContext
|
||||
{
|
||||
private BindingTargetMode? bindingTargetMode;
|
||||
private readonly List<IControllerMiddlewareBase> middleware = new List<IControllerMiddlewareBase>();
|
||||
private readonly List<IControllerMiddlewareBase> middleware = new();
|
||||
private readonly List<ControllerBindingParameter> parameters;
|
||||
private readonly ControllerBindingResult result;
|
||||
|
||||
|
@ -117,10 +117,10 @@ namespace Tapeti.Default
|
||||
{
|
||||
case BindingTargetMode.Default:
|
||||
if (bindingInfo.QueueInfo.QueueType == QueueType.Dynamic)
|
||||
QueueName = await target.BindDynamic(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name);
|
||||
QueueName = await target.BindDynamic(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name, bindingInfo.QueueInfo.QueueArguments);
|
||||
else
|
||||
{
|
||||
await target.BindDurable(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name);
|
||||
await target.BindDurable(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name, bindingInfo.QueueInfo.QueueArguments);
|
||||
QueueName = bindingInfo.QueueInfo.Name;
|
||||
}
|
||||
|
||||
@ -128,10 +128,10 @@ namespace Tapeti.Default
|
||||
|
||||
case BindingTargetMode.Direct:
|
||||
if (bindingInfo.QueueInfo.QueueType == QueueType.Dynamic)
|
||||
QueueName = await target.BindDynamicDirect(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name);
|
||||
QueueName = await target.BindDynamicDirect(bindingInfo.MessageClass, bindingInfo.QueueInfo.Name, bindingInfo.QueueInfo.QueueArguments);
|
||||
else
|
||||
{
|
||||
await target.BindDurableDirect(bindingInfo.QueueInfo.Name);
|
||||
await target.BindDurableDirect(bindingInfo.QueueInfo.Name, bindingInfo.QueueInfo.QueueArguments);
|
||||
QueueName = bindingInfo.QueueInfo.Name;
|
||||
}
|
||||
|
||||
@ -316,7 +316,11 @@ namespace Tapeti.Default
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Optional arguments (x-arguments) passed when declaring the queue.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string> QueueArguments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the QueueInfo properties contain a valid combination.
|
||||
/// </summary>
|
||||
|
@ -16,8 +16,8 @@ namespace Tapeti.Default
|
||||
private const string ClassTypeHeader = "classType";
|
||||
|
||||
|
||||
private readonly ConcurrentDictionary<string, Type> deserializedTypeNames = new ConcurrentDictionary<string, Type>();
|
||||
private readonly ConcurrentDictionary<Type, string> serializedTypeNames = new ConcurrentDictionary<Type, string>();
|
||||
private readonly ConcurrentDictionary<string, Type> deserializedTypeNames = new();
|
||||
private readonly ConcurrentDictionary<Type, string> serializedTypeNames = new();
|
||||
private readonly JsonSerializerSettings serializerSettings;
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@ namespace Tapeti.Default
|
||||
{
|
||||
internal class MessageContext : IMessageContext
|
||||
{
|
||||
private readonly Dictionary<Type, IMessageContextPayload> payloads = new Dictionary<Type, IMessageContextPayload>();
|
||||
private readonly Dictionary<Type, IMessageContextPayload> payloads = new();
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -117,7 +117,7 @@ namespace Tapeti.Default
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public class KeyValuePayload : IMessageContextPayload, IDisposable, IAsyncDisposable
|
||||
{
|
||||
private readonly Dictionary<string, object> items = new Dictionary<string, object>();
|
||||
private readonly Dictionary<string, object> items = new();
|
||||
|
||||
|
||||
public KeyValuePayload(string key, object value)
|
||||
|
@ -10,7 +10,7 @@ namespace Tapeti.Default
|
||||
/// </summary>
|
||||
public class MessageProperties : IMessageProperties
|
||||
{
|
||||
private readonly Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
private readonly Dictionary<string, string> headers = new();
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -13,7 +13,7 @@ namespace Tapeti.Default
|
||||
/// </example>
|
||||
public class NamespaceMatchExchangeStrategy : IExchangeStrategy
|
||||
{
|
||||
private static readonly Regex NamespaceRegex = new Regex("^(Messaging\\.)?(?<exchange>[^\\.]+)", RegexOptions.Compiled | RegexOptions.Singleline);
|
||||
private static readonly Regex NamespaceRegex = new("^(Messaging\\.)?(?<exchange>[^\\.]+)", RegexOptions.Compiled | RegexOptions.Singleline);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -28,9 +28,9 @@ namespace Tapeti.Default
|
||||
(?(?<=[A-Z])[A-Z](?=[a-z])|[A-Z])
|
||||
)";
|
||||
|
||||
private static readonly Regex SeparatorRegex = new Regex(SeparatorPattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
|
||||
private static readonly Regex SeparatorRegex = new(SeparatorPattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, string> RoutingKeyCache = new ConcurrentDictionary<Type, string>();
|
||||
private static readonly ConcurrentDictionary<Type, string> RoutingKeyCache = new();
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
|
30
Tapeti/Helpers/DictionaryHelper.cs
Normal file
30
Tapeti/Helpers/DictionaryHelper.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tapeti.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for dictionaries.
|
||||
/// </summary>
|
||||
public static class DictionaryHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if two dictionaries are considered compatible. If either is null they are considered empty.
|
||||
/// </summary>
|
||||
public static bool NullSafeSameValues(this IReadOnlyDictionary<string, string> arguments1, IReadOnlyDictionary<string, string> arguments2)
|
||||
{
|
||||
if (arguments1 == null || arguments2 == null)
|
||||
return (arguments1 == null || arguments1.Count == 0) && (arguments2 == null || arguments2.Count == 0);
|
||||
|
||||
if (arguments1.Count != arguments2.Count)
|
||||
return false;
|
||||
|
||||
foreach (var pair in arguments1)
|
||||
{
|
||||
if (!arguments2.TryGetValue(pair.Key, out var value2) || value2 != arguments1[pair.Key])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -138,8 +138,9 @@ namespace Tapeti
|
||||
/// If the queue already exists but should be compatible QueueDeclare will be called instead.
|
||||
/// </summary>
|
||||
/// <param name="queueName">The name of the queue that is declared</param>
|
||||
/// <param name="arguments">The x-arguments of the existing queue</param>
|
||||
void QueueExistsWarning(string queueName, Dictionary<string, string> arguments);
|
||||
/// <param name="existingArguments">The x-arguments of the existing queue</param>
|
||||
/// <param name="arguments">The x-arguments of the queue that would be declared</param>
|
||||
void QueueExistsWarning(string queueName, IReadOnlyDictionary<string, string> existingArguments, IReadOnlyDictionary<string, string> arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Called before a binding is added to a queue.
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
@ -42,6 +42,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Tapeti.Annotations" Version="3.0.0" />
|
||||
<PackageReference Include="Tapeti.Annotations" Version="3.*-*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -18,7 +18,7 @@ namespace Tapeti
|
||||
public class TapetiConfig : ITapetiConfigBuilder, ITapetiConfigBuilderAccess
|
||||
{
|
||||
private Config config;
|
||||
private readonly List<IControllerBindingMiddleware> bindingMiddleware = new List<IControllerBindingMiddleware>();
|
||||
private readonly List<IControllerBindingMiddleware> bindingMiddleware = new();
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -225,9 +225,9 @@ namespace Tapeti
|
||||
/// <inheritdoc />
|
||||
internal class Config : ITapetiConfig
|
||||
{
|
||||
private readonly ConfigFeatures features = new ConfigFeatures();
|
||||
private readonly ConfigMiddleware middleware = new ConfigMiddleware();
|
||||
private readonly ConfigBindings bindings = new ConfigBindings();
|
||||
private readonly ConfigFeatures features = new();
|
||||
private readonly ConfigMiddleware middleware = new();
|
||||
private readonly ConfigBindings bindings = new();
|
||||
|
||||
public IDependencyResolver DependencyResolver { get; }
|
||||
public ITapetiConfigFeatues Features => features;
|
||||
@ -291,8 +291,8 @@ namespace Tapeti
|
||||
|
||||
internal class ConfigMiddleware : ITapetiConfigMiddleware
|
||||
{
|
||||
private readonly List<IMessageMiddleware> messageMiddleware = new List<IMessageMiddleware>();
|
||||
private readonly List<IPublishMiddleware> publishMiddleware = new List<IPublishMiddleware>();
|
||||
private readonly List<IMessageMiddleware> messageMiddleware = new();
|
||||
private readonly List<IPublishMiddleware> publishMiddleware = new();
|
||||
|
||||
|
||||
public IReadOnlyList<IMessageMiddleware> Message => messageMiddleware;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Tapeti.Annotations;
|
||||
@ -79,7 +80,7 @@ namespace Tapeti
|
||||
}
|
||||
|
||||
var methodQueueInfo = GetQueueInfo(method) ?? controllerQueueInfo;
|
||||
if (!(methodQueueInfo is { IsValid: true }))
|
||||
if (methodQueueInfo is not { IsValid: true })
|
||||
throw new TopologyConfigurationException(
|
||||
$"Method {method.Name} or controller {controller.Name} requires a queue attribute");
|
||||
|
||||
@ -136,12 +137,59 @@ namespace Tapeti
|
||||
if (dynamicQueueAttribute != null && durableQueueAttribute != null)
|
||||
throw new TopologyConfigurationException($"Cannot combine static and dynamic queue attributes on controller {member.DeclaringType?.Name} method {member.Name}");
|
||||
|
||||
|
||||
var queueArgumentsAttribute = member.GetCustomAttribute<QueueArgumentsAttribute>();
|
||||
|
||||
if (dynamicQueueAttribute != null)
|
||||
return new ControllerMethodBinding.QueueInfo { QueueType = QueueType.Dynamic, Name = dynamicQueueAttribute.Prefix };
|
||||
return new ControllerMethodBinding.QueueInfo { QueueType = QueueType.Dynamic, Name = dynamicQueueAttribute.Prefix, QueueArguments = GetQueueArguments(queueArgumentsAttribute) };
|
||||
|
||||
return durableQueueAttribute != null
|
||||
? new ControllerMethodBinding.QueueInfo { QueueType = QueueType.Durable, Name = durableQueueAttribute.Name }
|
||||
? new ControllerMethodBinding.QueueInfo { QueueType = QueueType.Durable, Name = durableQueueAttribute.Name, QueueArguments = GetQueueArguments(queueArgumentsAttribute) }
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
private static IReadOnlyDictionary<string, string> GetQueueArguments(QueueArgumentsAttribute queueArgumentsAttribute)
|
||||
{
|
||||
if (queueArgumentsAttribute == null)
|
||||
return null;
|
||||
|
||||
#if NETSTANDARD2_1_OR_GREATER
|
||||
var arguments = new Dictionary<string, string>(queueArgumentsAttribute.CustomArguments);
|
||||
#else
|
||||
var arguments = new Dictionary<string, string>();
|
||||
foreach (var pair in queueArgumentsAttribute.CustomArguments)
|
||||
arguments.Add(pair.Key, pair.Value);
|
||||
#endif
|
||||
|
||||
if (queueArgumentsAttribute.MaxLength > 0)
|
||||
arguments.Add(@"x-max-length", queueArgumentsAttribute.MaxLength.ToString());
|
||||
|
||||
if (queueArgumentsAttribute.MaxLengthBytes > 0)
|
||||
arguments.Add(@"x-max-length-bytes", queueArgumentsAttribute.MaxLengthBytes.ToString());
|
||||
|
||||
if (queueArgumentsAttribute.MessageTTL > 0)
|
||||
arguments.Add(@"x-message-ttl", queueArgumentsAttribute.MessageTTL.ToString());
|
||||
|
||||
switch (queueArgumentsAttribute.Overflow)
|
||||
{
|
||||
case RabbitMQOverflow.NotSpecified:
|
||||
break;
|
||||
case RabbitMQOverflow.DropHead:
|
||||
arguments.Add(@"x-overflow", @"drop-head");
|
||||
break;
|
||||
case RabbitMQOverflow.RejectPublish:
|
||||
arguments.Add(@"x-overflow", @"reject-publish");
|
||||
break;
|
||||
case RabbitMQOverflow.RejectPublishDeadletter:
|
||||
arguments.Add(@"x-overflow", @"reject-publish-dlx");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(queueArgumentsAttribute.Overflow), queueArgumentsAttribute.Overflow, "Unsupported Overflow value");
|
||||
}
|
||||
|
||||
|
||||
return arguments.Count > 0 ? arguments : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ namespace Tapeti.Tasks
|
||||
/// </summary>
|
||||
public class SingleThreadTaskQueue : IDisposable
|
||||
{
|
||||
private readonly object previousTaskLock = new object();
|
||||
private readonly object previousTaskLock = new();
|
||||
private Task previousTask = Task.CompletedTask;
|
||||
|
||||
private readonly Lazy<SingleThreadTaskScheduler> singleThreadScheduler = new Lazy<SingleThreadTaskScheduler>();
|
||||
private readonly Lazy<SingleThreadTaskScheduler> singleThreadScheduler = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
@ -70,7 +70,7 @@ namespace Tapeti.Tasks
|
||||
public override int MaximumConcurrencyLevel => 1;
|
||||
|
||||
|
||||
private readonly Queue<Task> scheduledTasks = new Queue<Task>();
|
||||
private readonly Queue<Task> scheduledTasks = new();
|
||||
private bool disposed;
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user