Skip to content

Commit 5b64ca9

Browse files
authored
Merge pull request #1311 from stebet/asyncconnectandpublish
Adding fully asynchronous versions of connect and publish.
2 parents e909e1f + ab93546 commit 5b64ca9

24 files changed

+426
-134
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ csharp_style_inlined_variable_declaration = true:suggestion
115115
csharp_style_throw_expression = true:suggestion
116116
csharp_style_conditional_delegate_call = true:suggestion
117117

118+
# Async methods should have "Async" suffix
119+
dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
120+
dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
121+
dotnet_naming_rule.async_methods_end_in_async.severity = warning
122+
123+
dotnet_naming_symbols.any_async_methods.applicable_kinds = method
124+
dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
125+
dotnet_naming_symbols.any_async_methods.required_modifiers = async
126+
127+
dotnet_naming_style.end_in_async.required_suffix = Async
128+
dotnet_naming_style.end_in_async.capitalization = pascal_case_style
129+
118130
# Other features
119131
csharp_style_prefer_index_operator = false:none
120132
csharp_style_prefer_range_operator = false:none

projects/Benchmarks/Benchmarks.csproj

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
3+
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
44
<TargetFrameworks>net6.0;net472</TargetFrameworks>
5+
</PropertyGroup>
6+
7+
<PropertyGroup Condition="!$([MSBuild]::IsOSPlatform('Windows'))">
8+
<TargetFramework>net6.0</TargetFramework>
9+
</PropertyGroup>
10+
11+
<PropertyGroup>
512
<OutputType>Exe</OutputType>
613
<AssemblyOriginatorKeyFile>../rabbit.snk</AssemblyOriginatorKeyFile>
714
<SignAssembly>true</SignAssembly>

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,26 @@ void BasicPublish<TProperties>(string exchange, string routingKey, in TPropertie
208208
/// </remarks>
209209
void BasicPublish<TProperties>(CachedString exchange, CachedString routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body = default, bool mandatory = false)
210210
where TProperties : IReadOnlyBasicProperties, IAmqpHeader;
211+
/// <summary>
212+
/// Asynchronously publishes a message.
213+
/// </summary>
214+
/// <remarks>
215+
/// <para>
216+
/// Routing key must be shorter than 255 bytes.
217+
/// </para>
218+
/// </remarks>
219+
ValueTask BasicPublishAsync<TProperties>(string exchange, string routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body = default, bool mandatory = false)
220+
where TProperties : IReadOnlyBasicProperties, IAmqpHeader;
221+
/// <summary>
222+
/// Asynchronously publishes a message.
223+
/// </summary>
224+
/// <remarks>
225+
/// <para>
226+
/// Routing key must be shorter than 255 bytes.
227+
/// </para>
228+
/// </remarks>
229+
ValueTask BasicPublishAsync<TProperties>(CachedString exchange, CachedString routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body = default, bool mandatory = false)
230+
where TProperties : IReadOnlyBasicProperties, IAmqpHeader;
211231
#nullable disable
212232

213233
/// <summary>

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
using System;
3333
using System.Collections.Generic;
34+
using System.Threading.Tasks;
3435
using RabbitMQ.Client.client.impl;
3536

3637
namespace RabbitMQ.Client
@@ -93,6 +94,12 @@ public static void BasicPublish(this IChannel channel, string exchange, string r
9394

9495
public static void BasicPublish(this IChannel channel, CachedString exchange, CachedString routingKey, ReadOnlyMemory<byte> body = default, bool mandatory = false)
9596
=> channel.BasicPublish(exchange, routingKey, in EmptyBasicProperty.Empty, body, mandatory);
97+
98+
public static ValueTask BasicPublishAsync(this IChannel channel, string exchange, string routingKey, ReadOnlyMemory<byte> body = default, bool mandatory = false)
99+
=> channel.BasicPublishAsync(exchange, routingKey, in EmptyBasicProperty.Empty, body, mandatory);
100+
101+
public static ValueTask BasicPublishAsync(this IChannel channel, CachedString exchange, CachedString routingKey, ReadOnlyMemory<byte> body = default, bool mandatory = false)
102+
=> channel.BasicPublishAsync(exchange, routingKey, in EmptyBasicProperty.Empty, body, mandatory);
96103
#nullable disable
97104

98105
/// <summary>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
//---------------------------------------------------------------------------
3131

3232
using System.Collections.Generic;
33+
using System.Threading.Tasks;
3334
using RabbitMQ.Client.client.framing;
3435
using RabbitMQ.Client.Impl;
3536

@@ -109,6 +110,11 @@ public override void _Private_ConnectionOpen(string virtualHost)
109110
ChannelSend(new ConnectionOpen(virtualHost));
110111
}
111112

113+
public override ValueTask _Private_ConnectionOpenAsync(string virtualHost)
114+
{
115+
return ModelSendAsync(new ConnectionOpen(virtualHost));
116+
}
117+
112118
public override void _Private_ConnectionSecureOk(byte[] response)
113119
{
114120
ChannelSend(new ConnectionSecureOk(response));

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,14 @@ public void BasicPublish<TProperties>(CachedString exchange, CachedString routin
318318
where TProperties : IReadOnlyBasicProperties, IAmqpHeader
319319
=> InnerChannel.BasicPublish(exchange, routingKey, in basicProperties, body, mandatory);
320320

321+
public ValueTask BasicPublishAsync<TProperties>(string exchange, string routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body, bool mandatory)
322+
where TProperties : IReadOnlyBasicProperties, IAmqpHeader
323+
=> InnerChannel.BasicPublishAsync(exchange, routingKey, in basicProperties, body, mandatory);
324+
325+
public ValueTask BasicPublishAsync<TProperties>(CachedString exchange, CachedString routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body, bool mandatory)
326+
where TProperties : IReadOnlyBasicProperties, IAmqpHeader
327+
=> InnerChannel.BasicPublishAsync(exchange, routingKey, in basicProperties, body, mandatory);
328+
321329
public void BasicQos(uint prefetchSize, ushort prefetchCount, bool global)
322330
{
323331
ThrowIfDisposed();

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

Lines changed: 86 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ namespace RabbitMQ.Client.Impl
4949
internal abstract class ChannelBase : IChannel, IRecoverable
5050
{
5151
///<summary>Only used to kick-start a connection open
52-
///sequence. See <see cref="Connection.Open"/> </summary>
53-
internal BlockingCell<ConnectionStartDetails> m_connectionStartCell;
52+
///sequence. See <see cref="Connection.OpenAsync"/> </summary>
53+
internal TaskCompletionSource<ConnectionStartDetails> m_connectionStartCell;
5454

55+
// AMQP only allows one RPC operation to be active at a time.
56+
private readonly SemaphoreSlim _rpcSemaphore = new SemaphoreSlim(1, 1);
5557
private readonly RpcContinuationQueue _continuationQueue = new RpcContinuationQueue();
5658
private readonly ManualResetEventSlim _flowControlBlock = new ManualResetEventSlim(true);
5759

@@ -239,32 +241,18 @@ private async Task CloseAsync(ShutdownEventArgs reason, bool abort)
239241
}
240242
}
241243

242-
internal void ConnectionOpen(string virtualHost)
244+
internal async ValueTask ConnectionOpenAsync(string virtualHost)
243245
{
244-
var k = new SimpleBlockingRpcContinuation();
245-
lock (_rpcLock)
246-
{
247-
Enqueue(k);
248-
try
249-
{
250-
_Private_ConnectionOpen(virtualHost);
251-
}
252-
catch (AlreadyClosedException)
253-
{
254-
// let continuation throw OperationInterruptedException,
255-
// which is a much more suitable exception before connection
256-
// negotiation finishes
257-
}
258-
k.GetReply(HandshakeContinuationTimeout);
259-
}
246+
await _Private_ConnectionOpenAsync(virtualHost).TimeoutAfter(HandshakeContinuationTimeout);
260247
}
261248

262-
internal ConnectionSecureOrTune ConnectionSecureOk(byte[] response)
249+
internal async ValueTask<ConnectionSecureOrTune> ConnectionSecureOkAsync(byte[] response)
263250
{
264-
var k = new ConnectionStartRpcContinuation();
265-
lock (_rpcLock)
251+
var k = new ConnectionSecureOrTuneContinuation();
252+
await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
253+
Enqueue(k);
254+
try
266255
{
267-
Enqueue(k);
268256
try
269257
{
270258
_Private_ConnectionSecureOk(response);
@@ -275,31 +263,40 @@ internal ConnectionSecureOrTune ConnectionSecureOk(byte[] response)
275263
// which is a much more suitable exception before connection
276264
// negotiation finishes
277265
}
278-
k.GetReply(HandshakeContinuationTimeout);
266+
267+
return await k;
268+
}
269+
finally
270+
{
271+
_rpcSemaphore.Release();
279272
}
280-
return k.m_result;
281273
}
282274

283-
internal ConnectionSecureOrTune ConnectionStartOk(IDictionary<string, object> clientProperties, string mechanism, byte[] response, string locale)
275+
internal async ValueTask<ConnectionSecureOrTune> ConnectionStartOkAsync(IDictionary<string, object> clientProperties, string mechanism, byte[] response,
276+
string locale)
284277
{
285-
var k = new ConnectionStartRpcContinuation();
286-
lock (_rpcLock)
278+
var k = new ConnectionSecureOrTuneContinuation();
279+
await _rpcSemaphore.WaitAsync().ConfigureAwait(false);
280+
Enqueue(k);
281+
try
287282
{
288-
Enqueue(k);
289283
try
290284
{
291-
_Private_ConnectionStartOk(clientProperties, mechanism,
292-
response, locale);
285+
_Private_ConnectionStartOk(clientProperties, mechanism, response, locale);
293286
}
294287
catch (AlreadyClosedException)
295288
{
296289
// let continuation throw OperationInterruptedException,
297290
// which is a much more suitable exception before connection
298291
// negotiation finishes
299292
}
300-
k.GetReply(HandshakeContinuationTimeout);
293+
294+
return await k;
295+
}
296+
finally
297+
{
298+
_rpcSemaphore.Release();
301299
}
302-
return k.m_result;
303300
}
304301

305302
protected abstract bool DispatchAsynchronous(in IncomingCommand cmd);
@@ -324,7 +321,7 @@ internal void FinishClose()
324321
Session.Close(reason);
325322
}
326323

327-
m_connectionStartCell?.ContinueWithValue(null);
324+
m_connectionStartCell?.TrySetResult(null);
328325
}
329326

330327
private void HandleCommand(in IncomingCommand cmd)
@@ -385,6 +382,12 @@ protected void ChannelSend<T>(in T method) where T : struct, IOutgoingAmqpMethod
385382
Session.Transmit(in method);
386383
}
387384

385+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
386+
protected ValueTask ModelSendAsync<T>(in T method) where T : struct, IOutgoingAmqpMethod
387+
{
388+
return Session.TransmitAsync(in method);
389+
}
390+
388391
[MethodImpl(MethodImplOptions.AggressiveInlining)]
389392
protected void ChannelSend<TMethod, THeader>(in TMethod method, in THeader header, ReadOnlyMemory<byte> body)
390393
where TMethod : struct, IOutgoingAmqpMethod
@@ -397,6 +400,19 @@ protected void ChannelSend<TMethod, THeader>(in TMethod method, in THeader heade
397400
Session.Transmit(in method, in header, body);
398401
}
399402

403+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
404+
protected ValueTask ModelSendAsync<TMethod, THeader>(in TMethod method, in THeader header, ReadOnlyMemory<byte> body)
405+
where TMethod : struct, IOutgoingAmqpMethod
406+
where THeader : IAmqpHeader
407+
{
408+
if (!_flowControlBlock.IsSet)
409+
{
410+
_flowControlBlock.Wait();
411+
}
412+
413+
return Session.TransmitAsync(in method, in header, body);
414+
}
415+
400416
internal void OnCallbackException(CallbackExceptionEventArgs args)
401417
{
402418
_callbackExceptionWrapper.Invoke(this, args);
@@ -730,13 +746,7 @@ protected void HandleConnectionClose(in IncomingCommand cmd)
730746

731747
protected void HandleConnectionSecure(in IncomingCommand cmd)
732748
{
733-
var challenge = new ConnectionSecure(cmd.MethodBytes.Span)._challenge;
734-
cmd.ReturnMethodBuffer();
735-
var k = (ConnectionStartRpcContinuation)_continuationQueue.Next();
736-
k.m_result = new ConnectionSecureOrTune
737-
{
738-
m_challenge = challenge
739-
};
749+
var k = (ConnectionSecureOrTuneContinuation)_continuationQueue.Next();
740750
k.HandleCommand(IncomingCommand.Empty); // release the continuation.
741751
}
742752

@@ -758,25 +768,14 @@ protected void HandleConnectionStart(in IncomingCommand cmd)
758768
m_mechanisms = method._mechanisms,
759769
m_locales = method._locales
760770
};
761-
m_connectionStartCell.ContinueWithValue(details);
771+
m_connectionStartCell?.SetResult(details);
762772
m_connectionStartCell = null;
763773
}
764774

765775
protected void HandleConnectionTune(in IncomingCommand cmd)
766776
{
767-
var connectionTune = new ConnectionTune(cmd.MethodBytes.Span);
768-
cmd.ReturnMethodBuffer();
769-
var k = (ConnectionStartRpcContinuation)_continuationQueue.Next();
770-
k.m_result = new ConnectionSecureOrTune
771-
{
772-
m_tuneDetails =
773-
{
774-
m_channelMax = connectionTune._channelMax,
775-
m_frameMax = connectionTune._frameMax,
776-
m_heartbeatInSeconds = connectionTune._heartbeat
777-
}
778-
};
779-
k.HandleCommand(IncomingCommand.Empty); // release the continuation.
777+
var k = (ConnectionSecureOrTuneContinuation)_continuationQueue.Next();
778+
k.HandleCommand(cmd); // release the continuation.
780779
}
781780

782781
protected void HandleConnectionUnblocked()
@@ -815,6 +814,8 @@ protected void HandleQueueDeclareOk(in IncomingCommand cmd)
815814

816815
public abstract void _Private_ConnectionOpen(string virtualHost);
817816

817+
public abstract ValueTask _Private_ConnectionOpenAsync(string virtualHost);
818+
818819
public abstract void _Private_ConnectionSecureOk(byte[] response);
819820

820821
public abstract void _Private_ConnectionStartOk(IDictionary<string, object> clientProperties, string mechanism, byte[] response, string locale);
@@ -930,6 +931,36 @@ public void BasicPublish<TProperties>(CachedString exchange, CachedString routin
930931
ChannelSend(in cmd, in basicProperties, body);
931932
}
932933

934+
public ValueTask BasicPublishAsync<TProperties>(string exchange, string routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body, bool mandatory)
935+
where TProperties : IReadOnlyBasicProperties, IAmqpHeader
936+
{
937+
if (NextPublishSeqNo > 0)
938+
{
939+
lock (_confirmLock)
940+
{
941+
_pendingDeliveryTags.AddLast(NextPublishSeqNo++);
942+
}
943+
}
944+
945+
var cmd = new BasicPublish(exchange, routingKey, mandatory, default);
946+
return ModelSendAsync(in cmd, in basicProperties, body);
947+
}
948+
949+
public ValueTask BasicPublishAsync<TProperties>(CachedString exchange, CachedString routingKey, in TProperties basicProperties, ReadOnlyMemory<byte> body, bool mandatory)
950+
where TProperties : IReadOnlyBasicProperties, IAmqpHeader
951+
{
952+
if (NextPublishSeqNo > 0)
953+
{
954+
lock (_confirmLock)
955+
{
956+
_pendingDeliveryTags.AddLast(NextPublishSeqNo++);
957+
}
958+
}
959+
960+
var cmd = new BasicPublishMemory(exchange.Bytes, routingKey.Bytes, mandatory, default);
961+
return ModelSendAsync(in cmd, in basicProperties, body);
962+
}
963+
933964
public void UpdateSecret(string newSecret, string reason)
934965
{
935966
if (newSecret is null)

0 commit comments

Comments
 (0)