Skip to content

Commit c69bf00

Browse files
authored
Merge pull request #1345 from rabbitmq/lukebakken/async-queue-declare
Implement QueueDeclareAsync
2 parents 26e5657 + 7eef2b3 commit c69bf00

8 files changed

+225
-44
lines changed

projects/RabbitMQ.Client/client/api/IChannel.cs

+15
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ string BasicConsume(
188188
void BasicNack(ulong deliveryTag, bool multiple, bool requeue);
189189

190190
#nullable enable
191+
191192
/// <summary>
192193
/// Publishes a message.
193194
/// </summary>
@@ -198,6 +199,7 @@ string BasicConsume(
198199
/// </remarks>
199200
void BasicPublish<TProperties>(string exchange, string routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body = default, bool mandatory = false)
200201
where TProperties : IReadOnlyBasicProperties, IAmqpHeader;
202+
201203
/// <summary>
202204
/// Publishes a message.
203205
/// </summary>
@@ -208,6 +210,7 @@ void BasicPublish<TProperties>(string exchange, string routingKey, in TPropertie
208210
/// </remarks>
209211
void BasicPublish<TProperties>(CachedString exchange, CachedString routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body = default, bool mandatory = false)
210212
where TProperties : IReadOnlyBasicProperties, IAmqpHeader;
213+
211214
/// <summary>
212215
/// Asynchronously publishes a message.
213216
/// </summary>
@@ -218,6 +221,7 @@ void BasicPublish<TProperties>(CachedString exchange, CachedString routingKey, i
218221
/// </remarks>
219222
ValueTask BasicPublishAsync<TProperties>(string exchange, string routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body = default, bool mandatory = false)
220223
where TProperties : IReadOnlyBasicProperties, IAmqpHeader;
224+
221225
/// <summary>
222226
/// Asynchronously publishes a message.
223227
/// </summary>
@@ -228,6 +232,7 @@ ValueTask BasicPublishAsync<TProperties>(string exchange, string routingKey, in
228232
/// </remarks>
229233
ValueTask BasicPublishAsync<TProperties>(CachedString exchange, CachedString routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body = default, bool mandatory = false)
230234
where TProperties : IReadOnlyBasicProperties, IAmqpHeader;
235+
231236
#nullable disable
232237

233238
/// <summary>
@@ -361,6 +366,16 @@ ValueTask BasicPublishAsync<TProperties>(CachedString exchange, CachedString rou
361366
/// <param name="arguments">Optional; additional queue arguments, e.g. "x-queue-type"</param>
362367
QueueDeclareOk QueueDeclare(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object> arguments);
363368

369+
/// <summary>
370+
/// Asynchronously declares a queue. See the <a href="https://www.rabbitmq.com/queues.html">Queues guide</a> to learn more.
371+
/// </summary>
372+
/// <param name="queue">The name of the queue. Pass an empty string to make the server generate a name.</param>
373+
/// <param name="durable">Should this queue will survive a broker restart?</param>
374+
/// <param name="exclusive">Should this queue use be limited to its declaring connection? Such a queue will be deleted when its declaring connection closes.</param>
375+
/// <param name="autoDelete">Should this queue be auto-deleted when its last consumer (if any) unsubscribes?</param>
376+
/// <param name="arguments">Optional; additional queue arguments, e.g. "x-queue-type"</param>
377+
ValueTask<QueueDeclareOk> QueueDeclareAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object> arguments);
378+
364379
/// <summary>
365380
/// Declares a queue. See the <a href="https://www.rabbitmq.com/queues.html">Queues guide</a> to learn more.
366381
/// </summary>

projects/RabbitMQ.Client/client/framing/Channel.cs

+6-10
Original file line numberDiff line numberDiff line change
@@ -197,15 +197,12 @@ public override void _Private_QueueBind(string queue, string exchange, string ro
197197

198198
public override void _Private_QueueDeclare(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, bool nowait, IDictionary<string, object> arguments)
199199
{
200+
/*
201+
* Note:
202+
* Even though nowait is a parameter, ChannelSend must be used
203+
*/
200204
var method = new QueueDeclare(queue, passive, durable, exclusive, autoDelete, nowait, arguments);
201-
if (nowait)
202-
{
203-
ChannelSend(method);
204-
}
205-
else
206-
{
207-
ChannelSend(method);
208-
}
205+
ChannelSend(method);
209206
}
210207

211208
public override uint _Private_QueueDelete(string queue, bool ifUnused, bool ifEmpty, bool nowait)
@@ -382,8 +379,7 @@ protected override bool DispatchAsynchronous(in IncomingCommand cmd)
382379
}
383380
case ProtocolCommandId.QueueDeclareOk:
384381
{
385-
HandleQueueDeclareOk(in cmd);
386-
return true;
382+
return HandleQueueDeclareOk(in cmd);
387383
}
388384
default: return false;
389385
}

projects/RabbitMQ.Client/client/impl/AutorecoveringChannel.cs

+8
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,14 @@ public void QueueDeclareNoWait(string queue, bool durable, bool exclusive, bool
430430
_connection.RecordQueue(new RecordedQueue(queue, queue.Length == 0, durable, exclusive, autoDelete, arguments));
431431
}
432432

433+
public async ValueTask<QueueDeclareOk> QueueDeclareAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object> arguments)
434+
{
435+
ThrowIfDisposed();
436+
QueueDeclareOk result = await _innerChannel.QueueDeclareAsync(queue, durable, exclusive, autoDelete, arguments);
437+
_connection.RecordQueue(new RecordedQueue(result.QueueName, queue.Length == 0, durable, exclusive, autoDelete, arguments));
438+
return result;
439+
}
440+
433441
public QueueDeclareOk QueueDeclarePassive(string queue)
434442
=> InnerChannel.QueueDeclarePassive(queue);
435443

projects/RabbitMQ.Client/client/impl/ChannelBase.cs

+111-16
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
using RabbitMQ.Client.Events;
4343
using RabbitMQ.Client.Exceptions;
4444
using RabbitMQ.Client.Framing.Impl;
45-
using RabbitMQ.Util;
4645

4746
namespace RabbitMQ.Client.Impl
4847
{
@@ -57,7 +56,6 @@ internal abstract class ChannelBase : IChannel, IRecoverable
5756
private readonly RpcContinuationQueue _continuationQueue = new RpcContinuationQueue();
5857
private readonly ManualResetEventSlim _flowControlBlock = new ManualResetEventSlim(true);
5958

60-
private readonly object _rpcLock = new object();
6159
private readonly object _confirmLock = new object();
6260
private readonly LinkedList<ulong> _pendingDeliveryTags = new LinkedList<ulong>();
6361

@@ -328,7 +326,8 @@ private void HandleCommand(in IncomingCommand cmd)
328326
{
329327
if (!DispatchAsynchronous(in cmd)) // Was asynchronous. Already processed. No need to process further.
330328
{
331-
_continuationQueue.Next().HandleCommand(in cmd);
329+
IRpcContinuation c = _continuationQueue.Next();
330+
c.HandleCommand(in cmd);
332331
}
333332
}
334333

@@ -337,12 +336,17 @@ protected void ChannelRpc<TMethod>(in TMethod method, ProtocolCommandId returnCo
337336
{
338337
var k = new SimpleBlockingRpcContinuation();
339338
IncomingCommand reply;
340-
lock (_rpcLock)
339+
_rpcSemaphore.Wait();
340+
try
341341
{
342342
Enqueue(k);
343343
Session.Transmit(in method);
344344
k.GetReply(ContinuationTimeout, out reply);
345345
}
346+
finally
347+
{
348+
_rpcSemaphore.Release();
349+
}
346350

347351
reply.ReturnMethodBuffer();
348352

@@ -358,12 +362,17 @@ protected TReturn ChannelRpc<TMethod, TReturn>(in TMethod method, ProtocolComman
358362
var k = new SimpleBlockingRpcContinuation();
359363
IncomingCommand reply;
360364

361-
lock (_rpcLock)
365+
_rpcSemaphore.Wait();
366+
try
362367
{
363368
Enqueue(k);
364369
Session.Transmit(in method);
365370
k.GetReply(ContinuationTimeout, out reply);
366371
}
372+
finally
373+
{
374+
_rpcSemaphore.Release();
375+
}
367376

368377
if (reply.CommandId != returnCommandId)
369378
{
@@ -783,13 +792,21 @@ protected void HandleConnectionUnblocked()
783792
Session.Connection.HandleConnectionUnblocked();
784793
}
785794

786-
protected void HandleQueueDeclareOk(in IncomingCommand cmd)
795+
protected bool HandleQueueDeclareOk(in IncomingCommand cmd)
787796
{
788-
var method = new Client.Framing.Impl.QueueDeclareOk(cmd.MethodBytes.Span);
789-
cmd.ReturnMethodBuffer();
790-
var k = (QueueDeclareRpcContinuation)_continuationQueue.Next();
791-
k.m_result = new QueueDeclareOk(method._queue, method._messageCount, method._consumerCount);
792-
k.HandleCommand(IncomingCommand.Empty); // release the continuation.
797+
if (_continuationQueue.TryPeek<QueueDeclareRpcContinuation>(out var k))
798+
{
799+
_continuationQueue.Next();
800+
var method = new Client.Framing.Impl.QueueDeclareOk(cmd.MethodBytes.Span);
801+
cmd.ReturnMethodBuffer();
802+
k.m_result = new QueueDeclareOk(method._queue, method._messageCount, method._consumerCount);
803+
k.HandleCommand(IncomingCommand.Empty); // release the continuation.
804+
return true;
805+
}
806+
else
807+
{
808+
return false;
809+
}
793810
}
794811

795812
public abstract void _Private_BasicCancel(string consumerTag, bool nowait);
@@ -844,12 +861,17 @@ public void BasicCancel(string consumerTag)
844861
{
845862
var k = new BasicConsumerRpcContinuation { m_consumerTag = consumerTag };
846863

847-
lock (_rpcLock)
864+
_rpcSemaphore.Wait();
865+
try
848866
{
849867
Enqueue(k);
850868
_Private_BasicCancel(consumerTag, false);
851869
k.GetReply(ContinuationTimeout);
852870
}
871+
finally
872+
{
873+
_rpcSemaphore.Release();
874+
}
853875
}
854876

855877
public void BasicCancelNoWait(string consumerTag)
@@ -872,7 +894,8 @@ public string BasicConsume(string queue, bool autoAck, string consumerTag, bool
872894

873895
var k = new BasicConsumerRpcContinuation { m_consumer = consumer };
874896

875-
lock (_rpcLock)
897+
_rpcSemaphore.Wait();
898+
try
876899
{
877900
Enqueue(k);
878901
// Non-nowait. We have an unconventional means of getting
@@ -881,6 +904,11 @@ public string BasicConsume(string queue, bool autoAck, string consumerTag, bool
881904
/*nowait:*/ false, arguments);
882905
k.GetReply(ContinuationTimeout);
883906
}
907+
finally
908+
{
909+
_rpcSemaphore.Release();
910+
}
911+
884912
string actualConsumerTag = k.m_consumerTag;
885913

886914
return actualConsumerTag;
@@ -889,12 +917,18 @@ public string BasicConsume(string queue, bool autoAck, string consumerTag, bool
889917
public BasicGetResult BasicGet(string queue, bool autoAck)
890918
{
891919
var k = new BasicGetRpcContinuation();
892-
lock (_rpcLock)
920+
921+
_rpcSemaphore.Wait();
922+
try
893923
{
894924
Enqueue(k);
895925
_Private_BasicGet(queue, autoAck);
896926
k.GetReply(ContinuationTimeout);
897927
}
928+
finally
929+
{
930+
_rpcSemaphore.Release();
931+
}
898932

899933
return k.m_result;
900934
}
@@ -982,12 +1016,17 @@ public void BasicRecover(bool requeue)
9821016
{
9831017
var k = new SimpleBlockingRpcContinuation();
9841018

985-
lock (_rpcLock)
1019+
_rpcSemaphore.Wait();
1020+
try
9861021
{
9871022
Enqueue(k);
9881023
_Private_BasicRecover(requeue);
9891024
k.GetReply(ContinuationTimeout);
9901025
}
1026+
finally
1027+
{
1028+
_rpcSemaphore.Release();
1029+
}
9911030
}
9921031

9931032
public abstract void BasicRecoverAsync(bool requeue);
@@ -1065,6 +1104,11 @@ public QueueDeclareOk QueueDeclare(string queue, bool durable, bool exclusive, b
10651104
return QueueDeclare(queue, false, durable, exclusive, autoDelete, arguments);
10661105
}
10671106

1107+
public ValueTask<QueueDeclareOk> QueueDeclareAsync(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object> arguments)
1108+
{
1109+
return QueueDeclareAsync(queue, false, durable, exclusive, autoDelete, arguments);
1110+
}
1111+
10681112
public void QueueDeclareNoWait(string queue, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object> arguments)
10691113
{
10701114
_Private_QueueDeclare(queue, false, durable, exclusive, autoDelete, true, arguments);
@@ -1196,17 +1240,44 @@ await CloseAsync(new ShutdownEventArgs(ShutdownInitiator.Library,
11961240
private QueueDeclareOk QueueDeclare(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object> arguments)
11971241
{
11981242
var k = new QueueDeclareRpcContinuation();
1199-
lock (_rpcLock)
1243+
1244+
_rpcSemaphore.Wait();
1245+
try
12001246
{
12011247
Enqueue(k);
12021248
_Private_QueueDeclare(queue, passive, durable, exclusive, autoDelete, false, arguments);
12031249
k.GetReply(ContinuationTimeout);
12041250
}
1251+
finally
1252+
{
1253+
_rpcSemaphore.Release();
1254+
}
1255+
12051256
QueueDeclareOk result = k.m_result;
12061257
CurrentQueue = result.QueueName;
12071258
return result;
12081259
}
12091260

1261+
private async ValueTask<QueueDeclareOk> QueueDeclareAsync(string queue, bool passive, bool durable, bool exclusive, bool autoDelete, IDictionary<string, object> arguments)
1262+
{
1263+
var k = new QueueDeclareAsyncRpcContinuation();
1264+
await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
1265+
try
1266+
{
1267+
Enqueue(k);
1268+
1269+
var method = new QueueDeclare(queue, passive, durable, exclusive, autoDelete, false, arguments);
1270+
await ModelSendAsync(method).ConfigureAwait(false);
1271+
1272+
QueueDeclareOk result = await k;
1273+
CurrentQueue = result.QueueName;
1274+
return result;
1275+
}
1276+
finally
1277+
{
1278+
_rpcSemaphore.Release();
1279+
}
1280+
}
12101281

12111282
public class BasicConsumerRpcContinuation : SimpleBlockingRpcContinuation
12121283
{
@@ -1228,5 +1299,29 @@ public class QueueDeclareRpcContinuation : SimpleBlockingRpcContinuation
12281299
{
12291300
public QueueDeclareOk m_result;
12301301
}
1302+
1303+
public class QueueDeclareAsyncRpcContinuation : AsyncRpcContinuation<QueueDeclareOk>
1304+
{
1305+
public override void HandleCommand(in IncomingCommand cmd)
1306+
{
1307+
try
1308+
{
1309+
var method = new Client.Framing.Impl.QueueDeclareOk(cmd.MethodBytes.Span);
1310+
var result = new QueueDeclareOk(method._queue, method._messageCount, method._consumerCount);
1311+
if (cmd.CommandId == ProtocolCommandId.QueueDeclareOk)
1312+
{
1313+
_tcs.TrySetResult(result);
1314+
}
1315+
else
1316+
{
1317+
_tcs.SetException(new InvalidOperationException($"Received unexpected command of type {cmd.CommandId}!"));
1318+
}
1319+
}
1320+
finally
1321+
{
1322+
cmd.ReturnMethodBuffer();
1323+
}
1324+
}
1325+
}
12311326
}
12321327
}

projects/RabbitMQ.Client/client/impl/RpcContinuationQueue.cs

+19
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,24 @@ public IRpcContinuation Next()
108108
{
109109
return Interlocked.Exchange(ref _outstandingRpc, s_tmp);
110110
}
111+
112+
///<summary>Peek at the next waiting continuation.</summary>
113+
///<remarks>
114+
///<para>
115+
/// It is an error to call this method when there are no
116+
/// waiting continuations.
117+
///</para>
118+
///</remarks>
119+
public bool TryPeek<T>(out T continuation) where T : IRpcContinuation
120+
{
121+
if (_outstandingRpc is T result)
122+
{
123+
continuation = result;
124+
return true;
125+
}
126+
127+
continuation = default;
128+
return false;
129+
}
111130
}
112131
}

0 commit comments

Comments
 (0)