Merge pull request #61 from lukeabsent/AMQNET-637
AMQNET-637 More async methods implementations, refactors and increase AMQPNetLite version
diff --git a/src/NMS.AMQP/Apache-NMS-AMQP.csproj b/src/NMS.AMQP/Apache-NMS-AMQP.csproj
index 9fb8ebd..b710bc1 100644
--- a/src/NMS.AMQP/Apache-NMS-AMQP.csproj
+++ b/src/NMS.AMQP/Apache-NMS-AMQP.csproj
@@ -94,7 +94,7 @@
<ItemGroup>
<!-- AMQPNetLite.Core is .NET Standard 1.3 package -->
- <PackageReference Include="AMQPNetLite.Core" Version="2.4.0" />
+ <PackageReference Include="AMQPNetLite.Core" Version="2.4.1" />
<PackageReference Include="Apache.NMS" Version="2.0.0" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.9.0" />
</ItemGroup>
diff --git a/src/NMS.AMQP/INmsTransactionContext.cs b/src/NMS.AMQP/INmsTransactionContext.cs
index 6b8f6b1..7bcfaf9 100644
--- a/src/NMS.AMQP/INmsTransactionContext.cs
+++ b/src/NMS.AMQP/INmsTransactionContext.cs
@@ -19,7 +19,6 @@
using Apache.NMS.AMQP.Message;
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider;
-using Apache.NMS.AMQP.Util;
namespace Apache.NMS.AMQP
{
diff --git a/src/NMS.AMQP/Message/NmsMessage.cs b/src/NMS.AMQP/Message/NmsMessage.cs
index 1ca0f14..c56854c 100644
--- a/src/NMS.AMQP/Message/NmsMessage.cs
+++ b/src/NMS.AMQP/Message/NmsMessage.cs
@@ -16,7 +16,9 @@
*/
using System;
+using System.Threading.Tasks;
using Apache.NMS.AMQP.Message.Facade;
+using Apache.NMS.AMQP.Util.Synchronization;
using Apache.NMS.Util;
namespace Apache.NMS.AMQP.Message
@@ -150,11 +152,16 @@
public void Acknowledge()
{
+ AcknowledgeAsync().GetAsyncResult();
+ }
+
+ public async Task AcknowledgeAsync()
+ {
if (NmsAcknowledgeCallback != null)
{
try
{
- NmsAcknowledgeCallback.Acknowledge();
+ await NmsAcknowledgeCallback.Acknowledge().Await();
NmsAcknowledgeCallback = null;
}
catch (Exception e)
diff --git a/src/NMS.AMQP/NmsAcknowledgeCallback.cs b/src/NMS.AMQP/NmsAcknowledgeCallback.cs
index 40364c6..fefb32d 100644
--- a/src/NMS.AMQP/NmsAcknowledgeCallback.cs
+++ b/src/NMS.AMQP/NmsAcknowledgeCallback.cs
@@ -15,8 +15,10 @@
* limitations under the License.
*/
+using System.Threading.Tasks;
using Apache.NMS.AMQP.Message;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP
{
@@ -32,7 +34,7 @@
this.envelope = envelope;
}
- public void Acknowledge()
+ public async Task Acknowledge()
{
if (session.IsClosed)
{
@@ -41,11 +43,11 @@
if (envelope == null)
{
- session.Acknowledge(AcknowledgementType);
+ await session.AcknowledgeAsync(AcknowledgementType).Await();
}
else
{
- session.AcknowledgeIndividual(AcknowledgementType, envelope);
+ await session.AcknowledgeIndividualAsync(AcknowledgementType, envelope).Await();
}
}
diff --git a/src/NMS.AMQP/NmsConnection.cs b/src/NMS.AMQP/NmsConnection.cs
index 9ecffa9..e0a98d8 100644
--- a/src/NMS.AMQP/NmsConnection.cs
+++ b/src/NMS.AMQP/NmsConnection.cs
@@ -25,6 +25,7 @@
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
using Apache.NMS.Util;
namespace Apache.NMS.AMQP
@@ -43,7 +44,7 @@
private readonly AtomicLong temporaryQueueIdGenerator = new AtomicLong();
private readonly AtomicLong transactionIdGenerator = new AtomicLong();
private Exception failureCause;
- private readonly object syncRoot = new object();
+ private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
public NmsConnection(NmsConnectionInfo connectionInfo, IProvider provider)
{
@@ -89,12 +90,18 @@
DoStop(true);
}
+ public Task StopAsync()
+ {
+ Stop();
+ return Task.CompletedTask;
+ }
+
private void DoStop(bool checkClosed)
{
if (checkClosed)
CheckClosedOrFailed();
- CheckIsOnDeliveryThread();
+ CheckIsOnDeliveryExecutionFlow();
if (started.CompareAndSet(true, false))
{
@@ -112,13 +119,23 @@
public ISession CreateSession(AcknowledgementMode acknowledgementMode)
{
+ return CreateSessionAsync(acknowledgementMode).GetAsyncResult();
+ }
+
+ public Task<ISession> CreateSessionAsync()
+ {
+ return CreateSessionAsync(AcknowledgementMode);
+ }
+
+ public async Task<ISession> CreateSessionAsync(AcknowledgementMode acknowledgementMode)
+ {
CheckClosedOrFailed();
- CreateNmsConnection();
+ await CreateNmsConnectionAsync().Await();
NmsSession session = new NmsSession(this, GetNextSessionId(), acknowledgementMode);
try
{
- session.Begin().ConfigureAwait(false).GetAwaiter().GetResult();
+ await session.Begin().Await();
sessions.TryAdd(session.SessionInfo.Id, session);
if (started)
{
@@ -139,18 +156,23 @@
public void Close()
{
- CheckIsOnDeliveryThread();
+ CloseAsync().GetAsyncResult();
+ }
+
+ public async Task CloseAsync()
+ {
+ CheckIsOnDeliveryExecutionFlow();
if (closed.CompareAndSet(false, true))
{
DoStop(false);
foreach (NmsSession session in sessions.Values)
- session.Shutdown(null);
+ await session.ShutdownAsync(null).Await();;
try
{
- provider.Close();
+ await provider.CloseAsync().Await();;
}
catch (Exception)
{
@@ -170,7 +192,12 @@
public void Start()
{
- CreateNmsConnection();
+ StartAsync().GetAsyncResult();
+ }
+
+ public async Task StartAsync()
+ {
+ await CreateNmsConnectionAsync().Await();;
if (started.CompareAndSet(false, true))
{
@@ -288,12 +315,12 @@
{
foreach (NmsTemporaryDestination tempDestination in tempDestinations.Values)
{
- await provider.CreateResource(tempDestination);
+ await provider.CreateResource(tempDestination).Await();;
}
foreach (NmsSession session in sessions.Values)
{
- await session.OnConnectionRecovery(provider).ConfigureAwait(false);
+ await session.OnConnectionRecovery(provider).Await();
}
}
@@ -314,7 +341,7 @@
foreach (NmsSession session in sessions.Values)
{
- await session.OnConnectionRecovered(provider).ConfigureAwait(false);
+ await session.OnConnectionRecovered(provider).Await();
}
}
@@ -403,38 +430,56 @@
return provider.Send(envelope);
}
- private void CheckIsOnDeliveryThread()
+ private void CheckIsOnDeliveryExecutionFlow()
{
foreach (NmsSession session in sessions.Values)
{
- session.CheckIsOnDeliveryThread();
+ session.CheckIsOnDeliveryExecutionFlow();
}
}
private void CreateNmsConnection()
{
+ CreateNmsConnectionInternal(true).GetAsyncResult();
+ }
+
+ private Task CreateNmsConnectionAsync()
+ {
+ return CreateNmsConnectionInternal(false);
+ }
+
+ private async Task CreateNmsConnectionInternal(bool sync = true)
+ {
if (connected || closed)
{
return;
}
- lock (syncRoot)
+ var syncLock = sync ? syncRoot.Lock() : await syncRoot.LockAsync();
+ using(syncLock)
{
if (closed || connected)
{
return;
}
-
+
try
{
- provider.Connect(ConnectionInfo).ConfigureAwait(false).GetAwaiter().GetResult();
+ var configureTask = provider.Connect(ConnectionInfo).Await();
+ if (sync)
+ configureTask.GetAwaiter().GetResult();
+ else
+ await configureTask;
connected.Set(true);
}
catch (Exception e)
{
try
{
- provider.Close();
+ if (sync)
+ provider.Close();
+ else
+ await provider.CloseAsync().Await();
}
catch
{
@@ -444,6 +489,7 @@
throw NMSExceptionSupport.Create(e);
}
}
+
}
internal void OnAsyncException(Exception error)
@@ -488,23 +534,33 @@
public ITemporaryQueue CreateTemporaryQueue()
{
+ return CreateTemporaryQueueAsync().GetAsyncResult();
+ }
+
+ public async Task<ITemporaryQueue> CreateTemporaryQueueAsync()
+ {
var destinationName = $"{Id}:{temporaryQueueIdGenerator.IncrementAndGet().ToString()}";
var queue = new NmsTemporaryQueue(destinationName);
- InitializeTemporaryDestination(queue);
+ await InitializeTemporaryDestinationAsync(queue).Await();;
return queue;
}
public ITemporaryTopic CreateTemporaryTopic()
{
+ return CreateTemporaryTopicAsync().GetAsyncResult();
+ }
+
+ public async Task<ITemporaryTopic> CreateTemporaryTopicAsync()
+ {
var destinationName = $"{Id}:{temporaryTopicIdGenerator.IncrementAndGet().ToString()}";
NmsTemporaryTopic topic = new NmsTemporaryTopic(destinationName);
- InitializeTemporaryDestination(topic);
+ await InitializeTemporaryDestinationAsync(topic).Await();;
return topic;
}
- private void InitializeTemporaryDestination(NmsTemporaryDestination temporaryDestination)
+ private async Task InitializeTemporaryDestinationAsync(NmsTemporaryDestination temporaryDestination)
{
- CreateResource(temporaryDestination).ConfigureAwait(false).GetAwaiter().GetResult();
+ await CreateResource(temporaryDestination).Await();
tempDestinations.TryAdd(temporaryDestination, temporaryDestination);
temporaryDestination.Connection = this;
}
@@ -515,7 +571,7 @@
throw new InvalidDestinationException("Can't consume from a temporary destination created using another connection");
}
- public void DeleteTemporaryDestination(NmsTemporaryDestination destination)
+ public async Task DeleteTemporaryDestinationAsync(NmsTemporaryDestination destination)
{
CheckClosedOrFailed();
@@ -531,7 +587,7 @@
tempDestinations.TryRemove(destination, out _);
- DestroyResource(destination).ConfigureAwait(false).GetAwaiter().GetResult();
+ await DestroyResource(destination).Await();
}
catch (Exception e)
{
@@ -541,11 +597,16 @@
public void Unsubscribe(string subscriptionName)
{
- CheckClosedOrFailed();
-
- provider.Unsubscribe(subscriptionName).ConfigureAwait(false).GetAwaiter().GetResult();
+ UnsubscribeAsync(subscriptionName).GetAsyncResult();
}
+ public async Task UnsubscribeAsync(string subscriptionName)
+ {
+ CheckClosedOrFailed();
+
+ await provider.Unsubscribe(subscriptionName).Await();
+ }
+
public Task Rollback(NmsTransactionInfo transactionInfo, NmsTransactionInfo nextTransactionInfo)
{
return provider.Rollback(transactionInfo, nextTransactionInfo);
diff --git a/src/NMS.AMQP/NmsConnectionFactory.cs b/src/NMS.AMQP/NmsConnectionFactory.cs
index 88101bb..66543df 100644
--- a/src/NMS.AMQP/NmsConnectionFactory.cs
+++ b/src/NMS.AMQP/NmsConnectionFactory.cs
@@ -17,9 +17,11 @@
using System;
using System.Collections.Specialized;
+using System.Threading.Tasks;
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
using Apache.NMS.Util;
using URISupport = Apache.NMS.AMQP.Util.URISupport;
@@ -166,7 +168,17 @@
{
return CreateConnection(UserName, Password);
}
+
+ public Task<IConnection> CreateConnectionAsync()
+ {
+ return CreateConnectionAsync(UserName, Password);
+ }
+ public Task<IConnection> CreateConnectionAsync(string userName, string password)
+ {
+ return Task.FromResult(CreateConnection(userName, password));
+ }
+
public IConnection CreateConnection(string userName, string password)
{
try
@@ -201,6 +213,26 @@
return new NmsContext((NmsConnection)CreateConnection(userName, password), acknowledgementMode);
}
+ public async Task<INMSContext> CreateContextAsync()
+ {
+ return new NmsContext((NmsConnection)await CreateConnectionAsync().Await(), AcknowledgementMode.AutoAcknowledge);
+ }
+
+ public async Task<INMSContext> CreateContextAsync(AcknowledgementMode acknowledgementMode)
+ {
+ return new NmsContext((NmsConnection)await CreateConnectionAsync().Await(), acknowledgementMode);
+ }
+
+ public async Task<INMSContext> CreateContextAsync(string userName, string password)
+ {
+ return new NmsContext((NmsConnection)await CreateConnectionAsync(userName, password).Await(), AcknowledgementMode.AutoAcknowledge);
+ }
+
+ public async Task<INMSContext> CreateContextAsync(string userName, string password, AcknowledgementMode acknowledgementMode)
+ {
+ return new NmsContext((NmsConnection)await CreateConnectionAsync(userName, password).Await(), acknowledgementMode);
+ }
+
public Uri BrokerUri
{
get => brokerUri;
diff --git a/src/NMS.AMQP/NmsConsumer.cs b/src/NMS.AMQP/NmsConsumer.cs
index 9a2d40d..d6d905b 100644
--- a/src/NMS.AMQP/NmsConsumer.cs
+++ b/src/NMS.AMQP/NmsConsumer.cs
@@ -16,6 +16,7 @@
*/
using System;
+using System.Threading.Tasks;
namespace Apache.NMS.AMQP
{
@@ -40,11 +41,21 @@
return consumer.Receive();
}
+ public Task<IMessage> ReceiveAsync()
+ {
+ return consumer.ReceiveAsync();
+ }
+
public IMessage Receive(TimeSpan timeout)
{
return consumer.Receive(timeout);
}
+ public Task<IMessage> ReceiveAsync(TimeSpan timeout)
+ {
+ return consumer.ReceiveAsync(timeout);
+ }
+
public IMessage ReceiveNoWait()
{
return consumer.ReceiveNoWait();
@@ -55,11 +66,21 @@
return consumer.ReceiveBody<T>();
}
+ public Task<T> ReceiveBodyAsync<T>()
+ {
+ return consumer.ReceiveBodyAsync<T>();
+ }
+
public T ReceiveBody<T>(TimeSpan timeout)
{
return consumer.ReceiveBody<T>(timeout);
}
+ public Task<T> ReceiveBodyAsync<T>(TimeSpan timeout)
+ {
+ return consumer.ReceiveBodyAsync<T>(timeout);
+ }
+
public T ReceiveBodyNoWait<T>()
{
return consumer.ReceiveBodyNoWait<T>();
@@ -70,6 +91,11 @@
consumer.Close();
}
+ public Task CloseAsync()
+ {
+ return consumer.CloseAsync();
+ }
+
public string MessageSelector => consumer.MessageSelector;
public ConsumerTransformerDelegate ConsumerTransformer
diff --git a/src/NMS.AMQP/NmsContext.cs b/src/NMS.AMQP/NmsContext.cs
index 260647e..d8b905e 100644
--- a/src/NMS.AMQP/NmsContext.cs
+++ b/src/NMS.AMQP/NmsContext.cs
@@ -16,13 +16,15 @@
*/
using System;
+using System.Threading.Tasks;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP
{
public class NmsContext : INMSContext
{
- private readonly object syncRoot = new object();
+ private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
private readonly NmsConnection connection;
private readonly AtomicLong connectionRefCount;
@@ -57,6 +59,11 @@
connection.Start();
}
+ public Task StartAsync()
+ {
+ return connection.StartAsync();
+ }
+
public bool IsStarted { get => connection.IsStarted; }
public void Stop()
@@ -64,6 +71,11 @@
connection.Stop();
}
+ public Task StopAsync()
+ {
+ return connection.StopAsync();
+ }
+
public INMSContext CreateContext(AcknowledgementMode acknowledgementMode)
{
if (connectionRefCount.Get() == 0) {
@@ -76,9 +88,12 @@
public INMSProducer CreateProducer()
{
- if (sharedProducer == null) {
- lock (syncRoot) {
- if (sharedProducer == null) {
+ if (sharedProducer == null)
+ {
+ using(syncRoot.Lock())
+ {
+ if (sharedProducer == null)
+ {
sharedProducer = (NmsMessageProducer) GetSession().CreateProducer();
}
}
@@ -86,6 +101,21 @@
return new NmsProducer(GetSession(), sharedProducer);
}
+ public async Task<INMSProducer> CreateProducerAsync()
+ {
+ if (sharedProducer == null)
+ {
+ using (await syncRoot.LockAsync().Await())
+ {
+ if (sharedProducer == null)
+ {
+ sharedProducer = (NmsMessageProducer) await (await GetSessionAsync().Await()).CreateProducerAsync().Await();
+ }
+ }
+ }
+ return new NmsProducer(await GetSessionAsync(), sharedProducer);
+ }
+
public INMSConsumer CreateConsumer(IDestination destination)
{
@@ -137,96 +167,238 @@
return StartIfNeeded(new NmsConsumer(GetSession(), (NmsMessageConsumer) GetSession().CreateSharedDurableConsumer(destination, subscriptionName, selector)));
}
+ public async Task<INMSConsumer> CreateConsumerAsync(IDestination destination)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync().Await(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateConsumerAsync(destination)));
+ }
+
+ public async Task<INMSConsumer> CreateConsumerAsync(IDestination destination, string selector)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync().Await(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateConsumerAsync(destination, selector)));
+ }
+
+ public async Task<INMSConsumer> CreateConsumerAsync(IDestination destination, string selector, bool noLocal)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateConsumerAsync(destination, selector, noLocal)));
+ }
+
+ public async Task<INMSConsumer> CreateDurableConsumerAsync(ITopic destination, string subscriptionName)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateDurableConsumerAsync(destination, subscriptionName)));
+ }
+
+ public async Task<INMSConsumer> CreateDurableConsumerAsync(ITopic destination, string subscriptionName, string selector)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateDurableConsumerAsync(destination, subscriptionName, selector)));
+ }
+
+ public async Task<INMSConsumer> CreateDurableConsumerAsync(ITopic destination, string subscriptionName, string selector, bool noLocal)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateDurableConsumerAsync(destination, subscriptionName, selector, noLocal)));
+ }
+
+ public async Task<INMSConsumer> CreateSharedConsumerAsync(ITopic destination, string subscriptionName)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateSharedConsumerAsync(destination, subscriptionName)));
+ }
+
+ public async Task<INMSConsumer> CreateSharedConsumerAsync(ITopic destination, string subscriptionName, string selector)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateSharedConsumerAsync(destination, subscriptionName, selector)));
+ }
+
+ public async Task<INMSConsumer> CreateSharedDurableConsumerAsync(ITopic destination, string subscriptionName)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateSharedDurableConsumerAsync(destination, subscriptionName)));
+ }
+
+ public async Task<INMSConsumer> CreateSharedDurableConsumerAsync(ITopic destination, string subscriptionName, string selector)
+ {
+ return await StartIfNeededAsync(new NmsConsumer(await GetSessionAsync(), (NmsMessageConsumer) await (await GetSessionAsync()).CreateSharedDurableConsumerAsync(destination, subscriptionName, selector)));
+ }
+
public void Unsubscribe(string name)
{
GetSession().Unsubscribe(name);
}
+ public async Task UnsubscribeAsync(string name)
+ {
+ await (await GetSessionAsync().Await()).UnsubscribeAsync(name).Await();
+ }
+
public IQueueBrowser CreateBrowser(IQueue queue)
{
return GetSession().CreateBrowser(queue);
}
+ public async Task<IQueueBrowser> CreateBrowserAsync(IQueue queue)
+ {
+ return await (await GetSessionAsync().Await()).CreateBrowserAsync(queue).Await();
+ }
+
public IQueueBrowser CreateBrowser(IQueue queue, string selector)
{
return GetSession().CreateBrowser(queue, selector);
}
+ public async Task<IQueueBrowser> CreateBrowserAsync(IQueue queue, string selector)
+ {
+ return await (await GetSessionAsync().Await()).CreateBrowserAsync(queue, selector).Await();
+ }
+
public IQueue GetQueue(string name)
{
return GetSession().GetQueue(name);
}
+ public async Task<IQueue> GetQueueAsync(string name)
+ {
+ return await (await GetSessionAsync().Await()).GetQueueAsync(name).Await();
+ }
+
public ITopic GetTopic(string name)
{
return GetSession().GetTopic(name);
}
+ public async Task<ITopic> GetTopicAsync(string name)
+ {
+ return await (await GetSessionAsync().Await()).GetTopicAsync(name).Await();
+ }
+
public ITemporaryQueue CreateTemporaryQueue()
{
return GetSession().CreateTemporaryQueue();
}
+ public async Task<ITemporaryQueue> CreateTemporaryQueueAsync()
+ {
+ return await (await GetSessionAsync().Await()).CreateTemporaryQueueAsync().Await();
+ }
+
public ITemporaryTopic CreateTemporaryTopic()
{
return GetSession().CreateTemporaryTopic();
}
+ public async Task<ITemporaryTopic> CreateTemporaryTopicAsync()
+ {
+ return await (await GetSessionAsync().Await()).CreateTemporaryTopicAsync().Await();
+ }
+
public IMessage CreateMessage()
{
return GetSession().CreateMessage();
}
+ public async Task<IMessage> CreateMessageAsync()
+ {
+ return await (await GetSessionAsync().Await()).CreateMessageAsync().Await();
+ }
+
public ITextMessage CreateTextMessage()
{
return GetSession().CreateTextMessage();
}
+ public async Task<ITextMessage> CreateTextMessageAsync()
+ {
+ return await (await GetSessionAsync().Await()).CreateTextMessageAsync().Await();
+ }
+
public ITextMessage CreateTextMessage(string text)
{
return GetSession().CreateTextMessage(text);
}
+ public async Task<ITextMessage> CreateTextMessageAsync(string text)
+ {
+ return await (await GetSessionAsync().Await()).CreateTextMessageAsync(text).Await();
+ }
+
public IMapMessage CreateMapMessage()
{
return GetSession().CreateMapMessage();
}
+ public async Task<IMapMessage> CreateMapMessageAsync()
+ {
+ return await (await GetSessionAsync().Await()).CreateMapMessageAsync().Await();
+ }
+
public IObjectMessage CreateObjectMessage(object body)
{
return GetSession().CreateObjectMessage(body);
}
+ public async Task<IObjectMessage> CreateObjectMessageAsync(object body)
+ {
+ return await (await GetSessionAsync().Await()).CreateObjectMessageAsync(body).Await();
+ }
+
public IBytesMessage CreateBytesMessage()
{
return GetSession().CreateBytesMessage();
}
+ public async Task<IBytesMessage> CreateBytesMessageAsync()
+ {
+ return await (await GetSessionAsync().Await()).CreateBytesMessageAsync().Await();
+ }
+
public IBytesMessage CreateBytesMessage(byte[] body)
{
return GetSession().CreateBytesMessage(body);
}
+ public async Task<IBytesMessage> CreateBytesMessageAsync(byte[] body)
+ {
+ return await (await GetSessionAsync().Await()).CreateBytesMessageAsync(body).Await();
+ }
+
public IStreamMessage CreateStreamMessage()
{
return GetSession().CreateStreamMessage();
}
+ public async Task<IStreamMessage> CreateStreamMessageAsync()
+ {
+ return await (await GetSessionAsync().Await()).CreateStreamMessageAsync().Await();
+ }
+
public void Close()
{
+ CloseInternal(true).GetAsyncResult();
+ }
+
+ public Task CloseAsync()
+ {
+ return CloseInternal(false);
+ }
+
+ public async Task CloseInternal(bool sync)
+ {
NMSException failure = null;
try
{
- session?.Close();
+ if (sync)
+ session?.Close();
+ else
+ await (session?.CloseAsync() ?? Task.CompletedTask).Await();
} catch (NMSException jmse)
{
failure = jmse;
}
if (connectionRefCount.DecrementAndGet() == 0) {
- try {
- connection.Close();
+ try
+ {
+ if (sync)
+ connection.Close();
+ else
+ await connection.CloseAsync().Await();
} catch (NMSException jmse) {
if (failure == null)
{
@@ -239,27 +411,48 @@
throw failure;
}
}
-
+
+
public void Recover()
{
GetSession().Recover();
}
+ public async Task RecoverAsync()
+ {
+ await (await GetSessionAsync().Await()).RecoverAsync().Await();
+ }
+
public void Acknowledge()
{
GetSession().Acknowledge();
}
+ public async Task AcknowledgeAsync()
+ {
+ await (await GetSessionAsync().Await()).AcknowledgeAsync().Await();
+ }
+
public void Commit()
{
GetSession().Commit();
}
+ public async Task CommitAsync()
+ {
+ await (await GetSessionAsync().Await()).CommitAsync().Await();
+ }
+
public void Rollback()
{
GetSession().Rollback();
}
+ public async Task RollbackAsync()
+ {
+ await (await GetSessionAsync().Await()).RollbackAsync().Await();
+ }
+
public void PurgeTempDestinations()
{
connection.PurgeTempDestinations();
@@ -267,8 +460,10 @@
private NmsSession GetSession() {
- if (session == null) {
- lock (syncRoot) {
+ if (session == null)
+ {
+ using( syncRoot.Lock())
+ {
if (session == null)
{
session = (NmsSession) connection.CreateSession(AcknowledgementMode);
@@ -277,6 +472,22 @@
}
return session;
}
+
+ private async Task<NmsSession> GetSessionAsync()
+ {
+ if (session == null)
+ {
+ using(await syncRoot.LockAsync().Await())
+ {
+ if (session == null)
+ {
+ session = (NmsSession) await connection.CreateSessionAsync(AcknowledgementMode).Await();
+ }
+ }
+ }
+
+ return session;
+ }
private NmsConsumer StartIfNeeded(NmsConsumer consumer) {
if (autoStart) {
@@ -285,6 +496,13 @@
return consumer;
}
+ private async Task<NmsConsumer> StartIfNeededAsync(NmsConsumer consumer) {
+ if (autoStart) {
+ await connection.StartAsync().Await();
+ }
+ return consumer;
+ }
+
public ConsumerTransformerDelegate ConsumerTransformer { get => session.ConsumerTransformer; set => session.ConsumerTransformer = value; }
diff --git a/src/NMS.AMQP/NmsLocalTransactionContext.cs b/src/NMS.AMQP/NmsLocalTransactionContext.cs
index caae980..58995f8 100644
--- a/src/NMS.AMQP/NmsLocalTransactionContext.cs
+++ b/src/NMS.AMQP/NmsLocalTransactionContext.cs
@@ -23,6 +23,7 @@
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
using Apache.NMS.Util;
namespace Apache.NMS.AMQP
@@ -47,7 +48,7 @@
{
if (!IsInDoubt())
{
- await this.connection.Send(envelope);
+ await this.connection.Send(envelope).Await();
this.participants.Add(envelope.ProducerId);
}
}
@@ -59,7 +60,7 @@
{
try
{
- await this.connection.Acknowledge(envelope, ackType).ConfigureAwait(false);
+ await this.connection.Acknowledge(envelope, ackType).Await();
this.participants.Add(envelope.ConsumerId);
Tracer.Debug($"TX:{this.transactionInfo.Id} has performed an acknowledge.");
}
@@ -72,7 +73,7 @@
}
else
{
- await this.connection.Acknowledge(envelope, ackType).ConfigureAwait(false);
+ await this.connection.Acknowledge(envelope, ackType).Await();
}
}
@@ -83,7 +84,7 @@
try
{
Reset();
- await this.session.Connection.CreateResource(this.transactionInfo);
+ await this.session.Connection.CreateResource(this.transactionInfo).Await();
OnTransactionStarted();
Tracer.Debug($"Begin: {this.transactionInfo.Id}");
}
@@ -120,7 +121,7 @@
{
this.transactionInfo = GetNextTransactionInfo();
Tracer.Debug($"Transaction recovery creating new TX:{this.transactionInfo.Id} after failover.");
- await provider.CreateResource(this.transactionInfo).ConfigureAwait(false);
+ await provider.CreateResource(this.transactionInfo).Await();
}
}
@@ -141,7 +142,7 @@
{
try
{
- await Rollback();
+ await Rollback().Await();
}
catch (Exception e)
{
@@ -157,7 +158,7 @@
try
{
- await this.connection.Commit(this.transactionInfo, nextTx).ConfigureAwait(false);
+ await this.connection.Commit(this.transactionInfo, nextTx).Await();
OnTransactionCommitted();
Reset();
this.transactionInfo = nextTx;
@@ -180,7 +181,7 @@
// one to recover our state.
if (nextTx.ProviderTxId == null)
{
- await Begin().ConfigureAwait(false);
+ await Begin().Await();
}
}
catch (Exception e)
@@ -202,7 +203,7 @@
try
{
- await this.connection.Rollback(this.transactionInfo, nextTx);
+ await this.connection.Rollback(this.transactionInfo, nextTx).Await();
OnTransactionRolledBack();
Reset();
this.transactionInfo = nextTx;
@@ -223,7 +224,7 @@
// one to recover our state.
if (startNewTransaction && nextTx.ProviderTxId == null)
{
- await Begin();
+ await Begin().Await();
}
}
catch (Exception e)
diff --git a/src/NMS.AMQP/NmsMessageConsumer.cs b/src/NMS.AMQP/NmsMessageConsumer.cs
index 36abe8f..d64632d 100644
--- a/src/NMS.AMQP/NmsMessageConsumer.cs
+++ b/src/NMS.AMQP/NmsMessageConsumer.cs
@@ -21,13 +21,14 @@
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
using Apache.NMS.Util;
namespace Apache.NMS.AMQP
{
public class NmsMessageConsumer : IMessageConsumer
{
- private readonly object syncRoot = new object();
+ private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
private readonly AcknowledgementMode acknowledgementMode;
private readonly AtomicBool closed = new AtomicBool();
private readonly MessageDeliveryTask deliveryTask;
@@ -64,13 +65,6 @@
};
deliveryTask = new MessageDeliveryTask(this);
-
- Session.Connection.CreateResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
-
- Session.Add(this);
-
- if (Session.IsStarted)
- Start();
}
public NmsSession Session { get; }
@@ -97,13 +91,18 @@
public void Close()
{
+ CloseAsync().GetAsyncResult();
+ }
+
+ public async Task CloseAsync()
+ {
if (closed)
return;
- lock (syncRoot)
+ using(await syncRoot.LockAsync().Await())
{
Shutdown(null);
- Session.Connection.DestroyResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+ await Session.Connection.DestroyResource(Info).Await();
}
}
@@ -116,7 +115,7 @@
add
{
CheckClosed();
- lock (syncRoot)
+ using(syncRoot.Lock())
{
Listener += value;
DrainMessageQueueToListener();
@@ -124,7 +123,7 @@
}
remove
{
- lock (syncRoot)
+ using(syncRoot.Lock())
{
Listener -= value;
}
@@ -140,12 +139,12 @@
{
if (started)
{
- return ReceiveInternal(-1);
+ return ReceiveInternalAsync(-1).GetAsyncResult();
}
}
}
-
- public T ReceiveBody<T>()
+
+ public async Task<IMessage> ReceiveAsync()
{
CheckClosed();
CheckMessageListener();
@@ -154,7 +153,26 @@
{
if (started)
{
- return ReceiveBodyInternal<T>(-1);
+ return await ReceiveInternalAsync(-1).Await();
+ }
+ }
+ }
+
+ public T ReceiveBody<T>()
+ {
+ return ReceiveBodyAsync<T>().GetAsyncResult();
+ }
+
+ public Task<T> ReceiveBodyAsync<T>()
+ {
+ CheckClosed();
+ CheckMessageListener();
+
+ while (true)
+ {
+ if (started)
+ {
+ return ReceiveBodyInternalAsync<T>(-1);
}
}
}
@@ -165,7 +183,7 @@
CheckClosed();
CheckMessageListener();
- return started ? ReceiveInternal(0) : null;
+ return started ? ReceiveInternalAsync(0).GetAsyncResult() : null;
}
public T ReceiveBodyNoWait<T>()
@@ -173,19 +191,24 @@
CheckClosed();
CheckMessageListener();
- return started ? ReceiveBodyInternal<T>(0) : default;
+ return started ? ReceiveBodyInternalAsync<T>(0).GetAsyncResult() : default;
}
public IMessage Receive(TimeSpan timeout)
{
+ return ReceiveAsync(timeout).GetAsyncResult();
+ }
+
+ public async Task<IMessage> ReceiveAsync(TimeSpan timeout)
+ {
CheckClosed();
CheckMessageListener();
-
+
int timeoutInMilliseconds = (int) timeout.TotalMilliseconds;
if (started)
{
- return ReceiveInternal(timeoutInMilliseconds);
+ return await ReceiveInternalAsync(timeoutInMilliseconds).Await();
}
long deadline = GetDeadline(timeoutInMilliseconds);
@@ -200,13 +223,18 @@
if (started)
{
- return ReceiveInternal(timeoutInMilliseconds);
+ return await ReceiveInternalAsync(timeoutInMilliseconds).Await();
}
}
}
public T ReceiveBody<T>(TimeSpan timeout)
{
+ return ReceiveBodyAsync<T>(timeout).GetAsyncResult();
+ }
+
+ public async Task<T> ReceiveBodyAsync<T>(TimeSpan timeout)
+ {
CheckClosed();
CheckMessageListener();
@@ -214,7 +242,7 @@
if (started)
{
- return ReceiveBodyInternal<T>(timeoutInMilliseconds);
+ return await ReceiveBodyInternalAsync<T>(timeoutInMilliseconds).Await();
}
long deadline = GetDeadline(timeoutInMilliseconds);
@@ -229,7 +257,7 @@
if (started)
{
- return ReceiveBodyInternal<T>(timeoutInMilliseconds);
+ return await ReceiveBodyInternalAsync<T>(timeoutInMilliseconds).Await();
}
}
}
@@ -255,9 +283,16 @@
private event MessageListener Listener;
- public Task Init()
+ public async Task Init()
{
- return Session.Connection.StartResource(Info);
+ await Session.Connection.CreateResource(Info).Await();
+
+ Session.Add(this);
+
+ if (Session.IsStarted)
+ Start();
+
+ await Session.Connection.StartResource(Info).Await();
}
public void OnInboundMessage(InboundMessageDispatch envelope)
@@ -276,11 +311,14 @@
if (Session.IsStarted && Listener != null)
{
- Session.EnqueueForDispatch(deliveryTask);
+ using (syncRoot.Exclude()) // Exclude lock for a time of dispatching, so it does not pass along to actionblock
+ {
+ Session.EnqueueForDispatch(deliveryTask);
+ }
}
}
- private void DeliverNextPending()
+ private async Task DeliverNextPendingAsync()
{
if (Tracer.IsDebugEnabled)
{
@@ -289,7 +327,7 @@
if (Session.IsStarted && started && Listener != null)
{
- lock (syncRoot)
+ using(await syncRoot.LockAsync())
{
try
{
@@ -313,7 +351,7 @@
Tracer.Debug($"{Info.Id} filtered expired message: {envelope.Message.NMSMessageId}");
}
- DoAckExpired(envelope);
+ await DoAckExpiredAsync(envelope).Await();
}
else if (IsRedeliveryExceeded(envelope))
{
@@ -323,7 +361,7 @@
}
// TODO: Apply redelivery policy
- DoAckExpired(envelope);
+ await DoAckExpiredAsync(envelope).Await();
}
else
{
@@ -331,9 +369,9 @@
bool autoAckOrDupsOk = acknowledgementMode == AcknowledgementMode.AutoAcknowledge || acknowledgementMode == AcknowledgementMode.DupsOkAcknowledge;
if (autoAckOrDupsOk)
- DoAckDelivered(envelope);
+ await DoAckDeliveredAsync(envelope).Await();
else
- AckFromReceive(envelope);
+ await AckFromReceiveAsync(envelope).Await();
try
{
@@ -347,9 +385,9 @@
if (autoAckOrDupsOk)
{
if (!deliveryFailed)
- DoAckConsumed(envelope);
+ await DoAckConsumedAsync(envelope).Await();
else
- DoAckReleased(envelope);
+ await DoAckReleasedAsync(envelope).Await();
}
}
}
@@ -386,29 +424,31 @@
return false;
}
- private void DoAckReleased(InboundMessageDispatch envelope)
+ private Task DoAckReleasedAsync(InboundMessageDispatch envelope)
{
- Session.AcknowledgeIndividual(AckType.RELEASED, envelope);
+ return Session.AcknowledgeIndividualAsync(AckType.RELEASED, envelope);
}
- private IMessage ReceiveInternal(int timeout)
+
+ private Task<IMessage> ReceiveInternalAsync(int timeout)
{
- return ReceiveInternal(timeout, envelope =>
+ return ReceiveInternalBaseAsync(timeout, async envelope =>
{
IMessage message = envelope.Message.Copy();
- AckFromReceive(envelope);
+ await AckFromReceiveAsync(envelope);
return message;
});
}
- private T ReceiveBodyInternal<T>(int timeout)
+
+ private Task<T> ReceiveBodyInternalAsync<T>(int timeout)
{
- return ReceiveInternal<T>(timeout, envelope =>
+ return ReceiveInternalBaseAsync<T>(timeout, async envelope =>
{
try
{
T body = envelope.Message.Body<T>();
- AckFromReceive(envelope);
+ await AckFromReceiveAsync(envelope);
return body;
}
catch (MessageFormatException mfe)
@@ -427,7 +467,7 @@
}
- private T ReceiveInternal<T>(int timeout, Func<InboundMessageDispatch, T> func)
+ private async Task<T> ReceiveInternalBaseAsync<T>(int timeout, Func<InboundMessageDispatch, Task<T>> func)
{
try
{
@@ -444,7 +484,7 @@
Tracer.Debug("Trying to dequeue next message.");
}
- InboundMessageDispatch envelope = messageQueue.Dequeue(timeout);
+ InboundMessageDispatch envelope = await messageQueue.DequeueAsync(timeout).Await();
if (failureCause != null)
throw NMSExceptionSupport.Create(failureCause);
@@ -459,7 +499,7 @@
Tracer.Debug($"{Info.Id} filtered expired message: {envelope.Message.NMSMessageId}");
}
- DoAckExpired(envelope);
+ await DoAckExpiredAsync(envelope).Await();
if (timeout > 0)
timeout = (int) Math.Max(deadline - DateTime.UtcNow.Ticks / 10_000L, 0);
@@ -472,7 +512,7 @@
}
// TODO: Apply redelivery policy
- DoAckExpired(envelope);
+ await DoAckExpiredAsync(envelope).Await();
}
else
{
@@ -481,7 +521,7 @@
Tracer.Debug($"{Info.Id} received message {envelope.Message.NMSMessageId}.");
}
- return func.Invoke(envelope);
+ return await func.Invoke(envelope);
}
}
}
@@ -501,35 +541,35 @@
return DateTime.UtcNow.Ticks / 10_000L + timeout;
}
- private void AckFromReceive(InboundMessageDispatch envelope)
+ private async Task AckFromReceiveAsync(InboundMessageDispatch envelope)
{
if (envelope?.Message != null)
{
NmsMessage message = envelope.Message;
if (message.NmsAcknowledgeCallback != null)
{
- DoAckDelivered(envelope);
+ await DoAckDeliveredAsync(envelope).Await();
}
else
{
- DoAckConsumed(envelope);
+ await DoAckConsumedAsync(envelope).Await();
}
}
}
- private void DoAckDelivered(InboundMessageDispatch envelope)
+ private Task DoAckDeliveredAsync(InboundMessageDispatch envelope)
{
- Session.Acknowledge(AckType.DELIVERED, envelope);
+ return Session.AcknowledgeAsync(AckType.DELIVERED, envelope);
}
- private void DoAckConsumed(InboundMessageDispatch envelope)
+ private Task DoAckConsumedAsync(InboundMessageDispatch envelope)
{
- Session.Acknowledge(AckType.ACCEPTED, envelope);
+ return Session.AcknowledgeAsync(AckType.ACCEPTED, envelope);
}
- private void DoAckExpired(InboundMessageDispatch envelope)
+ private Task DoAckExpiredAsync(InboundMessageDispatch envelope)
{
- Session.Acknowledge(AckType.MODIFIED_FAILED_UNDELIVERABLE, envelope);
+ return Session.AcknowledgeAsync(AckType.MODIFIED_FAILED_UNDELIVERABLE, envelope);
}
private void SetAcknowledgeCallback(InboundMessageDispatch envelope)
@@ -587,7 +627,10 @@
int size = messageQueue.Count;
for (int i = 0; i < size; i++)
{
- Session.EnqueueForDispatch(deliveryTask);
+ using (syncRoot.Exclude()) // Exclude lock for a time of dispatching, so it does not pass along to actionblock
+ {
+ Session.EnqueueForDispatch(deliveryTask);
+ }
}
}
}
@@ -599,13 +642,13 @@
public async Task OnConnectionRecovered(IProvider provider)
{
- await provider.StartResource(Info).ConfigureAwait(false);
+ await provider.StartResource(Info).Await();
DrainMessageQueueToListener();
}
public void Stop()
{
- lock (syncRoot)
+ using(syncRoot.Lock())
{
started.Set(false);
}
@@ -621,13 +664,13 @@
messageQueue.Clear();
}
- public void SuspendForRollback()
+ public async Task SuspendForRollbackAsync()
{
Stop();
try
{
- Session.Connection.StopResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+ await Session.Connection.StopResource(Info).Await();
}
finally
{
@@ -638,17 +681,17 @@
}
}
- public void ResumeAfterRollback()
+ public async Task ResumeAfterRollbackAsync()
{
Start();
- StartConsumerResource();
+ await StartConsumerResourceAsync().Await();
}
- private void StartConsumerResource()
+ private async Task StartConsumerResourceAsync()
{
try
{
- Session.Connection.StartResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+ await Session.Connection.StartResource(Info).Await();
}
catch (NMSException)
{
@@ -666,9 +709,9 @@
this.consumer = consumer;
}
- public void DeliverNextPending()
+ public Task DeliverNextPending()
{
- consumer.DeliverNextPending();
+ return consumer.DeliverNextPendingAsync();
}
}
}
diff --git a/src/NMS.AMQP/NmsMessageProducer.cs b/src/NMS.AMQP/NmsMessageProducer.cs
index 3f7e3db..e215861 100644
--- a/src/NMS.AMQP/NmsMessageProducer.cs
+++ b/src/NMS.AMQP/NmsMessageProducer.cs
@@ -21,6 +21,7 @@
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP
{
@@ -39,19 +40,22 @@
private bool disableMessageId;
private bool disableMessageTimestamp;
- public NmsMessageProducer(NmsProducerId producerId, NmsSession session, IDestination destination)
+ internal NmsMessageProducer(NmsProducerId producerId, NmsSession session, IDestination destination)
{
this.session = session;
Info = new NmsProducerInfo(producerId)
{
Destination = destination
};
+ }
- session.Connection.CreateResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+ internal async Task Init()
+ {
+ await session.Connection.CreateResource(Info).Await();
session.Add(this);
}
-
+
public NmsProducerId ProducerId => Info.Id;
public NmsProducerInfo Info { get; }
public INmsMessageIdBuilder MessageIdBuilder { get; } = new DefaultMessageIdBuilder();
@@ -113,11 +117,16 @@
public void Close()
{
+ CloseAsync().GetAsyncResult();
+ }
+
+ public async Task CloseAsync()
+ {
if (closed)
return;
Shutdown();
- session.Connection.DestroyResource(Info).ConfigureAwait(false).GetAwaiter().GetResult();
+ await session.Connection.DestroyResource(Info).Await();
}
public IMessage CreateMessage()
diff --git a/src/NMS.AMQP/NmsNoTxTransactionContext.cs b/src/NMS.AMQP/NmsNoTxTransactionContext.cs
index a8e1607..f9fc384 100644
--- a/src/NMS.AMQP/NmsNoTxTransactionContext.cs
+++ b/src/NMS.AMQP/NmsNoTxTransactionContext.cs
@@ -19,7 +19,6 @@
using Apache.NMS.AMQP.Message;
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider;
-using Apache.NMS.AMQP.Util;
namespace Apache.NMS.AMQP
{
diff --git a/src/NMS.AMQP/NmsProducer.cs b/src/NMS.AMQP/NmsProducer.cs
index 974a185..fad1a2c 100644
--- a/src/NMS.AMQP/NmsProducer.cs
+++ b/src/NMS.AMQP/NmsProducer.cs
@@ -19,6 +19,7 @@
using System.Collections;
using System.Threading.Tasks;
using Apache.NMS.AMQP.Message;
+using Apache.NMS.AMQP.Util.Synchronization;
using Apache.NMS.Util;
namespace Apache.NMS.AMQP
@@ -122,7 +123,7 @@
message.NMSReplyTo = replyTo;
}
- await producer.SendAsync(destination, message);
+ await producer.SendAsync(destination, message).Await();
return this;
}
@@ -200,6 +201,11 @@
producer.Close();
}
+ public Task CloseAsync()
+ {
+ return producer.CloseAsync();
+ }
+
public string NMSCorrelationID
{
diff --git a/src/NMS.AMQP/NmsQueueBrowser.cs b/src/NMS.AMQP/NmsQueueBrowser.cs
index e1eb348..4f39260 100644
--- a/src/NMS.AMQP/NmsQueueBrowser.cs
+++ b/src/NMS.AMQP/NmsQueueBrowser.cs
@@ -16,14 +16,16 @@
*/
using System.Collections;
+using System.Threading.Tasks;
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP
{
public class NmsQueueBrowser : IQueueBrowser, IEnumerator
{
- private readonly object syncRoot = new object();
+ private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
private readonly NmsSession session;
private readonly IQueue destination;
@@ -103,8 +105,13 @@
public void Close()
{
+ CloseAsync().GetAsyncResult();
+ }
+
+ public async Task CloseAsync()
+ {
if (closed.CompareAndSet(false, true)) {
- DestroyConsumer();
+ await DestroyConsumerAsync().Await();
}
}
@@ -121,28 +128,33 @@
private void CreateConsumer()
{
- lock (syncRoot)
+ using(syncRoot.Lock())
{
if (consumer == null)
{
NmsMessageConsumer messageConsumer = new NmsQueueBrowserMessageConsumer(session.GetNextConsumerId(), session,
destination, selector, false);
- messageConsumer.Init().ConfigureAwait(false).GetAwaiter().GetResult();
+ messageConsumer.Init().GetAsyncResult();
// Assign only after fully created and initialized.
consumer = messageConsumer;
}
}
}
-
+
private void DestroyConsumer()
{
- lock (syncRoot)
+ DestroyConsumerAsync().GetAsyncResult();
+ }
+
+ private async Task DestroyConsumerAsync()
+ {
+ using(await syncRoot.LockAsync().Await())
{
try
{
- consumer?.Close();
+ await (consumer != null ? consumer.CloseAsync() : Task.CompletedTask).Await();
}
catch (NMSException e)
{
diff --git a/src/NMS.AMQP/NmsSession.cs b/src/NMS.AMQP/NmsSession.cs
index e1bb898..0eb697c 100644
--- a/src/NMS.AMQP/NmsSession.cs
+++ b/src/NMS.AMQP/NmsSession.cs
@@ -20,11 +20,11 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Amqp;
using Apache.NMS.AMQP.Message;
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP
{
@@ -64,19 +64,19 @@
internal async Task Begin()
{
- await Connection.CreateResource(SessionInfo).ConfigureAwait(false);
+ await Connection.CreateResource(SessionInfo).Await();
try
{
// We always keep an open TX if transacted so start now.
- await TransactionContext.Begin().ConfigureAwait(false);
+ await TransactionContext.Begin().Await();
}
catch (Exception)
{
// failed, close the AMQP session before we throw
try
{
- await Connection.DestroyResource(SessionInfo).ConfigureAwait(false);
+ await Connection.DestroyResource(SessionInfo).Await();
}
catch (Exception)
{
@@ -88,12 +88,23 @@
public void Close()
{
- CheckIsOnDeliveryThread();
+ CheckIsOnDeliveryExecutionFlow();
if (!closed)
{
Shutdown();
- Connection.DestroyResource(SessionInfo);
+ Connection.DestroyResource(SessionInfo).GetAsyncResult();
+ }
+ }
+
+ public async Task CloseAsync()
+ {
+ CheckIsOnDeliveryExecutionFlow();
+
+ if (!closed)
+ {
+ await ShutdownAsync().Await();
+ await Connection.DestroyResource(SessionInfo).Await();
}
}
@@ -114,9 +125,21 @@
return CreateProducer(null);
}
+ public Task<IMessageProducer> CreateProducerAsync()
+ {
+ return CreateProducerAsync(null);
+ }
+
public IMessageProducer CreateProducer(IDestination destination)
{
- return new NmsMessageProducer(GetNextProducerId(), this, destination);
+ return CreateProducerAsync(destination).GetAsyncResult();
+ }
+
+ public async Task<IMessageProducer> CreateProducerAsync(IDestination destination)
+ {
+ var producer = new NmsMessageProducer(GetNextProducerId(), this, destination);
+ await producer.Init().Await();
+ return producer;
}
private NmsProducerId GetNextProducerId()
@@ -129,18 +152,33 @@
return CreateConsumer(destination, null);
}
+ public Task<IMessageConsumer> CreateConsumerAsync(IDestination destination)
+ {
+ return CreateConsumerAsync(destination, null);
+ }
+
public IMessageConsumer CreateConsumer(IDestination destination, string selector)
{
return CreateConsumer(destination, selector, false);
}
+ public Task<IMessageConsumer> CreateConsumerAsync(IDestination destination, string selector)
+ {
+ return CreateConsumerAsync(destination, selector, false);
+ }
+
public IMessageConsumer CreateConsumer(IDestination destination, string selector, bool noLocal)
{
+ return CreateConsumerAsync(destination, selector, noLocal).GetAsyncResult();
+ }
+
+ public async Task<IMessageConsumer> CreateConsumerAsync(IDestination destination, string selector, bool noLocal)
+ {
CheckClosed();
NmsMessageConsumer messageConsumer = new NmsMessageConsumer(GetNextConsumerId(), this, destination, selector, noLocal);
- messageConsumer.Init().ConfigureAwait(false).GetAwaiter().GetResult();
-
+ await messageConsumer.Init().Await();
+
return messageConsumer;
}
@@ -149,17 +187,32 @@
return CreateDurableConsumer(destination, name, null, false);
}
+ public Task<IMessageConsumer> CreateDurableConsumerAsync(ITopic destination, string name)
+ {
+ return CreateDurableConsumerAsync(destination, name, null, false);
+ }
+
public IMessageConsumer CreateDurableConsumer(ITopic destination, string name, string selector)
{
return CreateDurableConsumer(destination, name, selector, false);
}
+ public Task<IMessageConsumer> CreateDurableConsumerAsync(ITopic destination, string name, string selector)
+ {
+ return CreateDurableConsumerAsync(destination, name, selector, false);
+ }
+
public IMessageConsumer CreateDurableConsumer(ITopic destination, string name, string selector, bool noLocal)
{
+ return CreateDurableConsumerAsync(destination, name, selector, noLocal).GetAsyncResult();
+ }
+
+ public async Task<IMessageConsumer> CreateDurableConsumerAsync(ITopic destination, string name, string selector, bool noLocal)
+ {
CheckClosed();
NmsMessageConsumer messageConsumer = new NmsDurableMessageConsumer(GetNextConsumerId(), this, destination, name, selector, noLocal);
- messageConsumer.Init().ConfigureAwait(false).GetAwaiter().GetResult();
+ await messageConsumer.Init().Await();
return messageConsumer;
}
@@ -169,12 +222,22 @@
return CreateSharedConsumer(destination, name, null);
}
+ public Task<IMessageConsumer> CreateSharedConsumerAsync(ITopic destination, string name)
+ {
+ return CreateSharedConsumerAsync(destination, name, null);
+ }
+
public IMessageConsumer CreateSharedConsumer(ITopic destination, string name, string selector)
{
+ return CreateSharedConsumerAsync(destination, name, selector).GetAsyncResult();
+ }
+
+ public async Task<IMessageConsumer> CreateSharedConsumerAsync(ITopic destination, string name, string selector)
+ {
CheckClosed();
NmsMessageConsumer messageConsumer = new NmsSharedMessageConsumer(GetNextConsumerId(), this, destination, name, selector, false);
- messageConsumer.Init().ConfigureAwait(false).GetAwaiter().GetResult();
+ await messageConsumer.Init().Await();
return messageConsumer;
}
@@ -184,12 +247,22 @@
return CreateSharedDurableConsumer(destination, name, null);
}
+ public Task<IMessageConsumer> CreateSharedDurableConsumerAsync(ITopic destination, string name)
+ {
+ return CreateSharedDurableConsumerAsync(destination, name, null);
+ }
+
public IMessageConsumer CreateSharedDurableConsumer(ITopic destination, string name, string selector)
{
+ return CreateSharedDurableConsumerAsync(destination, name, selector).GetAsyncResult();
+ }
+
+ public async Task<IMessageConsumer> CreateSharedDurableConsumerAsync(ITopic destination, string name, string selector)
+ {
CheckClosed();
NmsMessageConsumer messageConsumer = new NmsSharedDurableMessageConsumer(GetNextConsumerId(), this, destination, name, selector, false);
- messageConsumer.Init().ConfigureAwait(false).GetAwaiter().GetResult();
+ await messageConsumer.Init().Await();//.GetAwaiter().GetResult();
return messageConsumer;
}
@@ -206,16 +279,32 @@
public void Unsubscribe(string name)
{
- CheckClosed();
-
- Connection.Unsubscribe(name);
+ UnsubscribeAsync(name).GetAsyncResult();
}
+ public async Task UnsubscribeAsync(string name)
+ {
+ CheckClosed();
+
+ await Connection.UnsubscribeAsync(name).Await();
+ }
+
+ public Task<IQueueBrowser> CreateBrowserAsync(IQueue queue)
+ {
+ return Task.FromResult(CreateBrowser(queue));
+ }
+
+ public Task<IQueueBrowser> CreateBrowserAsync(IQueue queue, string selector)
+ {
+ return Task.FromResult(CreateBrowser(queue, selector));
+ }
+
public IQueueBrowser CreateBrowser(IQueue queue)
{
return CreateBrowser(queue, null);
}
+
public IQueueBrowser CreateBrowser(IQueue queue, string selector)
{
CheckClosed();
@@ -230,6 +319,11 @@
return new NmsQueue(name);
}
+ public Task<IQueue> GetQueueAsync(string name)
+ {
+ return Task.FromResult(GetQueue(name));
+ }
+
public ITopic GetTopic(string name)
{
CheckClosed();
@@ -237,31 +331,51 @@
return new NmsTopic(name);
}
+ public Task<ITopic> GetTopicAsync(string name)
+ {
+ return Task.FromResult(GetTopic(name));
+ }
+
public ITemporaryQueue CreateTemporaryQueue()
{
+ return CreateTemporaryQueueAsync().GetAsyncResult();
+ }
+
+ public async Task<ITemporaryQueue> CreateTemporaryQueueAsync()
+ {
CheckClosed();
- return Connection.CreateTemporaryQueue();
+ return await Connection.CreateTemporaryQueueAsync().Await();
}
public ITemporaryTopic CreateTemporaryTopic()
{
+ return CreateTemporaryTopicAsync().GetAsyncResult();
+ }
+
+ public async Task<ITemporaryTopic> CreateTemporaryTopicAsync()
+ {
CheckClosed();
- return Connection.CreateTemporaryTopic();
+ return await Connection.CreateTemporaryTopicAsync().Await();
}
public void DeleteDestination(IDestination destination)
{
+ DeleteDestinationAsync(destination).GetAsyncResult();
+ }
+
+ public async Task DeleteDestinationAsync(IDestination destination)
+ {
CheckClosed();
if (destination == null)
return;
if (destination is ITemporaryQueue temporaryQueue)
- temporaryQueue.Delete();
+ await temporaryQueue.DeleteAsync().Await();
else if (destination is ITemporaryTopic temporaryTopic)
- temporaryTopic.Delete();
+ await temporaryTopic.DeleteAsync().Await();
else
throw new NotSupportedException("AMQP can not delete a Queue or Topic destination.");
}
@@ -273,6 +387,11 @@
return Connection.MessageFactory.CreateMessage();
}
+ public Task<IMessage> CreateMessageAsync()
+ {
+ return Task.FromResult(CreateMessage());
+ }
+
public ITextMessage CreateTextMessage()
{
CheckClosed();
@@ -280,6 +399,11 @@
return Connection.MessageFactory.CreateTextMessage();
}
+ public Task<ITextMessage> CreateTextMessageAsync()
+ {
+ return Task.FromResult(CreateTextMessage());
+ }
+
public ITextMessage CreateTextMessage(string text)
{
CheckClosed();
@@ -287,6 +411,11 @@
return Connection.MessageFactory.CreateTextMessage(text);
}
+ public Task<ITextMessage> CreateTextMessageAsync(string text)
+ {
+ return Task.FromResult(CreateTextMessage(text));
+ }
+
public IMapMessage CreateMapMessage()
{
CheckClosed();
@@ -294,6 +423,11 @@
return Connection.MessageFactory.CreateMapMessage();
}
+ public Task<IMapMessage> CreateMapMessageAsync()
+ {
+ return Task.FromResult(CreateMapMessage());
+ }
+
public IObjectMessage CreateObjectMessage(object body)
{
CheckClosed();
@@ -301,6 +435,11 @@
return Connection.MessageFactory.CreateObjectMessage(body);
}
+ public Task<IObjectMessage> CreateObjectMessageAsync(object body)
+ {
+ return Task.FromResult(CreateObjectMessage(body));
+ }
+
public IBytesMessage CreateBytesMessage()
{
CheckClosed();
@@ -308,6 +447,11 @@
return Connection.MessageFactory.CreateBytesMessage();
}
+ public Task<IBytesMessage> CreateBytesMessageAsync()
+ {
+ return Task.FromResult(CreateBytesMessage());
+ }
+
public IBytesMessage CreateBytesMessage(byte[] body)
{
CheckClosed();
@@ -315,21 +459,36 @@
return Connection.MessageFactory.CreateBytesMessage(body);
}
+ public Task<IBytesMessage> CreateBytesMessageAsync(byte[] body)
+ {
+ return Task.FromResult(CreateBytesMessage(body));
+ }
+
public IStreamMessage CreateStreamMessage()
{
CheckClosed();
return Connection.MessageFactory.CreateStreamMessage();
}
+
+ public Task<IStreamMessage> CreateStreamMessageAsync()
+ {
+ return Task.FromResult(CreateStreamMessage());
+ }
public void Recover()
{
+ RecoverAsync().GetAsyncResult();
+ }
+
+ public async Task RecoverAsync()
+ {
CheckClosed();
bool wasStarted = IsStarted;
Stop();
- Connection.Recover(SessionInfo.Id).ConfigureAwait(false).GetAwaiter().GetResult();
+ await Connection.Recover(SessionInfo.Id).Await();
if (wasStarted)
Start();
@@ -337,8 +496,13 @@
public void Acknowledge()
{
+ AcknowledgeAsync().GetAsyncResult();
+ }
+
+ public async Task AcknowledgeAsync()
+ {
if (acknowledgementMode == AcknowledgementMode.ClientAcknowledge) {
- Acknowledge(AckType.ACCEPTED);
+ await AcknowledgeAsync(AckType.ACCEPTED).Await();
}
}
@@ -346,11 +510,23 @@
{
CheckClosed();
- TransactionContext.Commit().ConfigureAwait(false).GetAwaiter().GetResult();
+ TransactionContext.Commit().GetAsyncResult();
+ }
+
+ public async Task CommitAsync()
+ {
+ CheckClosed();
+
+ await TransactionContext.Commit().Await();
}
public void Rollback()
{
+ RollbackAsync().GetAsyncResult();
+ }
+
+ public async Task RollbackAsync()
+ {
CheckClosed();
// Stop processing any new messages that arrive
@@ -358,19 +534,19 @@
{
foreach (NmsMessageConsumer consumer in consumers.Values)
{
- consumer.SuspendForRollback();
+ await consumer.SuspendForRollbackAsync().Await();
}
}
finally
{
- TransactionContext.Rollback().ConfigureAwait(false).GetAwaiter().GetResult();
+ await TransactionContext.Rollback().Await(); //.GetAsyncResult();
}
// Currently some consumers won't get suspended and some won't restart
// after a failed rollback.
foreach (NmsMessageConsumer consumer in consumers.Values)
{
- consumer.ResumeAfterRollback();
+ await consumer.ResumeAfterRollbackAsync().Await();
}
}
@@ -430,24 +606,24 @@
}
}
- public void Acknowledge(AckType ackType)
+ public Task AcknowledgeAsync(AckType ackType)
{
- Connection.Acknowledge(SessionInfo.Id, ackType).ConfigureAwait(false).GetAwaiter().GetResult();
+ return Connection.Acknowledge(SessionInfo.Id, ackType);
}
- public void Acknowledge(AckType ackType, InboundMessageDispatch envelope)
+ public Task AcknowledgeAsync(AckType ackType, InboundMessageDispatch envelope)
{
- TransactionContext.Acknowledge(envelope, ackType).ConfigureAwait(false).GetAwaiter().GetResult();
+ return TransactionContext.Acknowledge(envelope, ackType);
}
- public void AcknowledgeIndividual(AckType ackType, InboundMessageDispatch envelope)
+ public Task AcknowledgeIndividualAsync(AckType ackType, InboundMessageDispatch envelope)
{
if (Transacted)
{
throw new IllegalStateException("Message acknowledge called inside a transacted Session");
}
- Connection.Acknowledge(envelope, ackType).ConfigureAwait(false).GetAwaiter().GetResult();
+ return Connection.Acknowledge(envelope, ackType); //.GetAsyncResult();
}
public void Send(NmsMessageProducer producer, IDestination destination, IMessage original,
@@ -456,7 +632,7 @@
{
SendAsync(producer, destination, original, deliveryMode, priority, timeToLive, disableMessageId,
- disableMessageTimestamp, deliveryDelay).ConfigureAwait(false).GetAwaiter().GetResult();
+ disableMessageTimestamp, deliveryDelay).GetAsyncResult();
}
@@ -617,6 +793,11 @@
public void Shutdown(NMSException exception = null)
{
+ ShutdownAsync(exception).GetAsyncResult();
+ }
+
+ public async Task ShutdownAsync(NMSException exception = null)
+ {
if (closed.CompareAndSet(false, true))
{
failureCause = exception;
@@ -630,7 +811,7 @@
foreach (NmsMessageProducer producer in producers.Values.ToArray())
producer.Shutdown(exception);
- TransactionContext.Shutdown().ConfigureAwait(false).GetAwaiter().GetResult();
+ await TransactionContext.Shutdown().Await();
}
finally
{
@@ -638,7 +819,7 @@
}
}
}
-
+
public void Start()
{
if (started.CompareAndSet(false, true))
@@ -652,9 +833,9 @@
}
}
- internal void CheckIsOnDeliveryThread()
+ internal void CheckIsOnDeliveryExecutionFlow()
{
- if (dispatcher != null && dispatcher.IsOnDeliveryThread())
+ if (dispatcher != null && dispatcher.IsOnDeliveryExecutionFlow())
{
throw new IllegalStateException("Illegal invocation from MessageListener callback");
}
@@ -662,16 +843,16 @@
public async Task OnConnectionRecovery(IProvider provider)
{
- await provider.CreateResource(SessionInfo).ConfigureAwait(false);
+ await provider.CreateResource(SessionInfo).Await();
foreach (NmsMessageConsumer consumer in consumers.Values)
{
- await consumer.OnConnectionRecovery(provider).ConfigureAwait(false);
+ await consumer.OnConnectionRecovery(provider).Await();
}
foreach (NmsMessageProducer producer in producers.Values)
{
- await producer.OnConnectionRecovery(provider).ConfigureAwait(false);
+ await producer.OnConnectionRecovery(provider).Await();
}
}
@@ -679,7 +860,7 @@
{
foreach (NmsMessageConsumer consumer in consumers.Values)
{
- await consumer.OnConnectionRecovered(provider).ConfigureAwait(false);
+ await consumer.OnConnectionRecovered(provider).Await();
}
}
diff --git a/src/NMS.AMQP/NmsTemporaryDestination.cs b/src/NMS.AMQP/NmsTemporaryDestination.cs
index a2876d4..1f1d092 100644
--- a/src/NMS.AMQP/NmsTemporaryDestination.cs
+++ b/src/NMS.AMQP/NmsTemporaryDestination.cs
@@ -15,8 +15,9 @@
* limitations under the License.
*/
+using System.Threading.Tasks;
using Apache.NMS.AMQP.Meta;
-using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP
{
@@ -36,9 +37,14 @@
public void Dispose()
{
+ DeleteAsync().GetAsyncResult();
+ }
+
+ public async Task DeleteAsync()
+ {
if (Connection != null)
{
- Connection.DeleteTemporaryDestination(this);
+ await Connection.DeleteTemporaryDestinationAsync(this).Await();
Connection = null;
}
}
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpConnection.cs b/src/NMS.AMQP/Provider/Amqp/AmqpConnection.cs
index 6b40430..0e30ad6 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpConnection.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpConnection.cs
@@ -27,6 +27,7 @@
using Apache.NMS.AMQP.Provider.Amqp.Message;
using Apache.NMS.AMQP.Transport;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Amqp
{
@@ -73,11 +74,11 @@
{
Address address = UriUtil.ToAddress(remoteUri, Info.UserName, Info.Password);
this.tsc = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
- underlyingConnection = await transport.CreateAsync(address, new AmqpHandler(this)).ConfigureAwait(false);
+ underlyingConnection = await transport.CreateAsync(address, new AmqpHandler(this)).AwaitRunContinuationAsync();
underlyingConnection.AddClosedCallback(OnClosed);
// Wait for connection to be opened
- await this.tsc.Task.ConfigureAwait(false);
+ await this.tsc.Task.Await();
// Create a Session for this connection that is used for Temporary Destinations
// and perhaps later on management and advisory monitoring.
@@ -85,7 +86,7 @@
sessionInfo.AcknowledgementMode = AcknowledgementMode.AutoAcknowledge;
connectionSession = new AmqpConnectionSession(this, sessionInfo);
- await connectionSession.Start().ConfigureAwait(false);
+ await connectionSession.Start().Await();
}
private void OnClosed(IAmqpObject sender, Error error)
@@ -174,7 +175,7 @@
public async Task CreateSession(NmsSessionInfo sessionInfo)
{
var amqpSession = new AmqpSession(this, sessionInfo);
- await amqpSession.Start().ConfigureAwait(false);
+ await amqpSession.Start().Await();
sessions.TryAdd(sessionInfo.Id, amqpSession);
}
@@ -192,6 +193,20 @@
}
}
+ public async Task CloseAsync()
+ {
+ try
+ {
+ if (UnderlyingConnection != null) await UnderlyingConnection.CloseAsync().AwaitRunContinuationAsync();
+ }
+ catch (Exception ex)
+ {
+ // log network errors
+ NMSException nmse = ExceptionSupport.Wrap(ex, "Amqp Connection close failure for NMS Connection {0}", this.Info.Id);
+ Tracer.DebugFormat("Caught Exception while closing Amqp Connection {0}. Exception {1}", this.Info.Id, nmse);
+ }
+ }
+
public AmqpSession GetSession(NmsSessionId sessionId)
{
if (sessions.TryGetValue(sessionId, out AmqpSession session))
@@ -209,7 +224,7 @@
public async Task CreateTemporaryDestination(NmsTemporaryDestination destination)
{
AmqpTemporaryDestination amqpTemporaryDestination = new AmqpTemporaryDestination(connectionSession, destination);
- await amqpTemporaryDestination.Attach();
+ await amqpTemporaryDestination.Attach().Await();
temporaryDestinations.TryAdd(destination, amqpTemporaryDestination);
}
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpConnectionSession.cs b/src/NMS.AMQP/Provider/Amqp/AmqpConnectionSession.cs
index c89133a..ba9399e 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpConnectionSession.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpConnectionSession.cs
@@ -22,6 +22,7 @@
using Amqp.Types;
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Amqp
{
@@ -57,9 +58,9 @@
tcs.TrySetException(exception);
});
- await tcs.Task;
+ await tcs.Task.Await();
- receiverLink.Close(TimeSpan.FromMilliseconds(Connection.Provider.CloseTimeout));
+ await receiverLink.CloseAsync(TimeSpan.FromMilliseconds(Connection.Provider.CloseTimeout)).AwaitRunContinuationAsync();
}
private Attach CreateAttach(string subscriptionName)
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs b/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs
index d4ceeac..fc3a5cf 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpConsumer.cs
@@ -27,6 +27,7 @@
using Apache.NMS.AMQP.Provider.Amqp.Filters;
using Apache.NMS.AMQP.Provider.Amqp.Message;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Amqp
{
@@ -391,15 +392,15 @@
}
}
- public void Close()
+ public async Task CloseAsync()
{
if (info.IsDurable)
{
- receiverLink?.Detach();
+ if (receiverLink != null) await receiverLink.DetachAsync().AwaitRunContinuationAsync();
}
else
{
- receiverLink?.Close();
+ if (receiverLink != null) await receiverLink.CloseAsync().AwaitRunContinuationAsync();
}
}
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpProducer.cs b/src/NMS.AMQP/Provider/Amqp/AmqpProducer.cs
index d52a278..5da3e31 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpProducer.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpProducer.cs
@@ -24,6 +24,7 @@
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Provider.Amqp.Message;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Amqp
{
@@ -55,7 +56,7 @@
};
string linkName = info.Id + ":" + target.Address;
- var taskCompletionSource = new TaskCompletionSource<bool>();
+ var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
senderLink = new SenderLink(session.UnderlyingSession, linkName, frame, HandleOpened(taskCompletionSource));
senderLink.AddClosedCallback((sender, error) =>
@@ -75,7 +76,7 @@
return taskCompletionSource.Task;
}
-
+
private OnAttached HandleOpened(TaskCompletionSource<bool> tsc) => (link, attach) =>
{
if (IsClosePending(attach))
@@ -141,7 +142,7 @@
SendSync(message, transactionalState);
return;
}
- await SendAsync(message, transactionalState).ConfigureAwait(false);
+ await SendAsync(message, transactionalState).Await();
}
catch (AmqpException amqpEx)
{
@@ -178,7 +179,7 @@
try
{
senderLink.Send(message, deliveryState, _onOutcome, tcs);
- await tcs.Task.ConfigureAwait(false);
+ await tcs.Task.Await();
}
finally
{
@@ -210,12 +211,12 @@
}
}
- public void Close()
+ public async Task CloseAsync()
{
try
{
var closeTimeout = session.Connection.Provider.CloseTimeout;
- senderLink.Close(TimeSpan.FromMilliseconds(closeTimeout));
+ await senderLink.CloseAsync(TimeSpan.FromMilliseconds(closeTimeout)).AwaitRunContinuationAsync();
}
catch (NMSException)
{
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpProvider.cs b/src/NMS.AMQP/Provider/Amqp/AmqpProvider.cs
index c31bdeb..0720820 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpProvider.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpProvider.cs
@@ -18,11 +18,10 @@
using System;
using System.Threading.Tasks;
using Amqp;
-using Amqp.Framing;
using Apache.NMS.AMQP.Message;
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Transport;
-using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Amqp
{
@@ -135,6 +134,11 @@
connection?.Close();
}
+ public Task CloseAsync()
+ {
+ return connection?.CloseAsync();
+ }
+
public void SetProviderListener(IProviderListener providerListener)
{
Listener = providerListener;
@@ -166,49 +170,49 @@
}
}
- public Task DestroyResource(INmsResource resourceInfo)
+ public async Task DestroyResource(INmsResource resourceInfo)
{
switch (resourceInfo)
{
case NmsSessionInfo sessionInfo:
{
AmqpSession session = connection.GetSession(sessionInfo.Id);
- session.Close();
- return Task.CompletedTask;
+ await session.CloseAsync().Await();
+ return;
}
case NmsConsumerInfo consumerInfo:
{
AmqpSession session = connection.GetSession(consumerInfo.SessionId);
AmqpConsumer consumer = session.GetConsumer(consumerInfo.Id);
- consumer.Close();
+ await consumer.CloseAsync().Await();;
session.RemoveConsumer(consumerInfo.Id);
- return Task.CompletedTask;
+ return;
}
case NmsProducerInfo producerInfo:
{
AmqpSession session = connection.GetSession(producerInfo.SessionId);
AmqpProducer producer = session.GetProducer(producerInfo.Id);
- producer.Close();
+ await producer.CloseAsync().Await();;
session.RemoveProducer(producerInfo.Id);
- return Task.CompletedTask;
+ return;
}
case NmsTemporaryDestination temporaryDestination:
{
AmqpTemporaryDestination amqpTemporaryDestination = connection.GetTemporaryDestination(temporaryDestination);
if (amqpTemporaryDestination != null)
{
- amqpTemporaryDestination.Close();
+ await amqpTemporaryDestination.CloseAsync().Await();;
connection.RemoveTemporaryDestination(temporaryDestination);
}
else
Tracer.Debug($"Could not find temporary destination {temporaryDestination} to delete.");
- return Task.CompletedTask;
+ return;
}
default:
throw new ArgumentOutOfRangeException(nameof(resourceInfo), "Not supported resource type.");
}
- }
+ }
public Task StartResource(INmsResource resourceInfo)
{
@@ -270,7 +274,7 @@
{
AmqpSession session = connection.GetSession(envelope.ProducerInfo.SessionId);
AmqpProducer producer = session.GetProducer(envelope.ProducerId);
- await producer.Send(envelope).ConfigureAwait(false);
+ await producer.Send(envelope).Await();
envelope.Message.IsReadOnly = false;
}
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpSendTask.cs b/src/NMS.AMQP/Provider/Amqp/AmqpSendTask.cs
index d53c0fc..5676780 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpSendTask.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpSendTask.cs
@@ -29,7 +29,8 @@
{
private readonly Timer timer;
- public AmqpSendTask(SenderLink link, global::Amqp.Message message, DeliveryState deliveryState, long timeoutMillis)
+ public AmqpSendTask(SenderLink link, global::Amqp.Message message, DeliveryState deliveryState, long timeoutMillis)
+ : base(TaskCreationOptions.RunContinuationsAsynchronously)
{
if (timeoutMillis != NmsConnectionInfo.INFINITE)
{
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpSession.cs b/src/NMS.AMQP/Provider/Amqp/AmqpSession.cs
index d2c5c81..2be170c 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpSession.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpSession.cs
@@ -25,6 +25,7 @@
using Amqp.Framing;
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Amqp
{
@@ -83,14 +84,14 @@
return tcs.Task;
}
- public void Close()
+ public async Task CloseAsync()
{
long closeTimeout = Connection.Provider.CloseTimeout;
TimeSpan timeout = TimeSpan.FromMilliseconds(closeTimeout);
- UnderlyingSession.Close(timeout);
+ await UnderlyingSession.CloseAsync(timeout).AwaitRunContinuationAsync();
Connection.RemoveSession(SessionInfo.Id);
}
-
+
public Task BeginTransaction(NmsTransactionInfo transactionInfo)
{
if (!SessionInfo.IsTransacted)
@@ -115,14 +116,14 @@
public async Task CreateConsumer(NmsConsumerInfo consumerInfo)
{
AmqpConsumer amqpConsumer = new AmqpConsumer(this, consumerInfo);
- await amqpConsumer.Attach();
+ await amqpConsumer.Attach().Await();;
consumers.TryAdd(consumerInfo.Id, amqpConsumer);
}
public async Task CreateProducer(NmsProducerInfo producerInfo)
{
var amqpProducer = new AmqpProducer(this, producerInfo);
- await amqpProducer.Attach();
+ await amqpProducer.Attach().Await();;
producers.TryAdd(producerInfo.Id, amqpProducer);
}
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpTemporaryDestination.cs b/src/NMS.AMQP/Provider/Amqp/AmqpTemporaryDestination.cs
index 6a76632..263e117 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpTemporaryDestination.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpTemporaryDestination.cs
@@ -21,6 +21,7 @@
using Amqp.Framing;
using Amqp.Types;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Amqp
{
@@ -94,12 +95,12 @@
return result;
}
-
- public void Close()
+
+ public async Task CloseAsync()
{
try
{
- senderLink.Close();
+ await senderLink.CloseAsync().AwaitRunContinuationAsync();
}
catch (NMSException)
{
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpTransactionContext.cs b/src/NMS.AMQP/Provider/Amqp/AmqpTransactionContext.cs
index 0a77f4a..2e46699 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpTransactionContext.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpTransactionContext.cs
@@ -21,7 +21,7 @@
using Amqp.Framing;
using Amqp.Transactions;
using Apache.NMS.AMQP.Meta;
-using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Amqp
{
@@ -66,14 +66,14 @@
Tracer.Debug($"TX Context{this} rolling back current TX[{this.current}]");
this.current = null;
- await this.coordinator.DischargeAsync(this.txnId, true).ConfigureAwait(false);
+ await this.coordinator.DischargeAsync(this.txnId, true).Await();
PostRollback();
if (nextTransactionInfo != null)
{
- await Begin(nextTransactionInfo).ConfigureAwait(false);
+ await Begin(nextTransactionInfo).Await();
}
}
@@ -101,11 +101,11 @@
Tracer.Debug($"TX Context{this} committing back current TX[{this.current}]");
this.current = null;
- await this.coordinator.DischargeAsync(this.txnId, false).ConfigureAwait(false);
+ await this.coordinator.DischargeAsync(this.txnId, false).Await();
PostCommit();
- await Begin(nextTransactionInfo).ConfigureAwait(false);
+ await Begin(nextTransactionInfo).Await();
}
private void PostCommit()
@@ -123,7 +123,7 @@
this.coordinator = new AmqpTransactionCoordinator(this.session);
}
- this.txnId = await this.coordinator.DeclareAsync().ConfigureAwait(false);
+ this.txnId = await this.coordinator.DeclareAsync().Await();
this.current = transactionInfo.Id;
transactionInfo.ProviderTxId = this.txnId;
this.cachedTransactedState = new TransactionalState { TxnId = this.txnId };
diff --git a/src/NMS.AMQP/Provider/Amqp/AmqpTransactionCoordinator.cs b/src/NMS.AMQP/Provider/Amqp/AmqpTransactionCoordinator.cs
index 3fab595..0281b5c 100644
--- a/src/NMS.AMQP/Provider/Amqp/AmqpTransactionCoordinator.cs
+++ b/src/NMS.AMQP/Provider/Amqp/AmqpTransactionCoordinator.cs
@@ -21,6 +21,7 @@
using Amqp.Framing;
using Amqp.Transactions;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Amqp
{
@@ -51,7 +52,7 @@
public async Task<byte[]> DeclareAsync()
{
- var outcome = await this.SendAsync(DeclareMessage, null, this.session.Connection.Provider.RequestTimeout).ConfigureAwait(false);
+ var outcome = await this.SendAsync(DeclareMessage, null, this.session.Connection.Provider.RequestTimeout).Await();
if (outcome.Descriptor.Code == MessageSupport.DECLARED_INSTANCE.Descriptor.Code)
{
return ((Declared) outcome).TxnId;
@@ -71,7 +72,7 @@
public async Task DischargeAsync(byte[] txnId, bool fail)
{
var message = new global::Amqp.Message(new Discharge { TxnId = txnId, Fail = fail });
- var outcome = await this.SendAsync(message, null, this.session.Connection.Provider.RequestTimeout).ConfigureAwait(false);
+ var outcome = await this.SendAsync(message, null, this.session.Connection.Provider.RequestTimeout).Await();
if (outcome.Descriptor.Code == MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code)
{
diff --git a/src/NMS.AMQP/Provider/Failover/FailoverProvider.cs b/src/NMS.AMQP/Provider/Failover/FailoverProvider.cs
index c567687..80d4b89 100644
--- a/src/NMS.AMQP/Provider/Failover/FailoverProvider.cs
+++ b/src/NMS.AMQP/Provider/Failover/FailoverProvider.cs
@@ -22,6 +22,7 @@
using Apache.NMS.AMQP.Message;
using Apache.NMS.AMQP.Meta;
using Apache.NMS.AMQP.Util;
+using Apache.NMS.AMQP.Util.Synchronization;
using Apache.NMS.Util;
namespace Apache.NMS.AMQP.Provider.Failover
@@ -128,8 +129,8 @@
{
Tracer.Debug($"Connection attempt:[{reconnectAttempts}] to: {target.Scheme}://{target.Host}:{target.Port} in-progress");
provider = ProviderFactory.Create(target);
- await provider.Connect(connectionInfo).ConfigureAwait(false);
- await InitializeNewConnection(provider).ConfigureAwait(false);
+ await provider.Connect(connectionInfo).Await();
+ await InitializeNewConnection(provider).Await();
return;
}
catch (Exception e)
@@ -173,7 +174,7 @@
}
else
{
- await reconnectControl.ScheduleReconnect(Reconnect).ConfigureAwait(false);
+ await reconnectControl.ScheduleReconnect(Reconnect).Await();
}
}
}
@@ -220,21 +221,21 @@
Tracer.Debug($"Signalling connection recovery: {provider}");
// Allow listener to recover its resources
- await listener.OnConnectionRecovery(provider).ConfigureAwait(false);
+ await listener.OnConnectionRecovery(provider).Await();
// Restart consumers, send pull commands, etc.
- await listener.OnConnectionRecovered(provider).ConfigureAwait(false);
+ await listener.OnConnectionRecovered(provider).Await();
// Let the client know that connection has restored.
listener.OnConnectionRestored(connectedUri);
// If we try to run pending requests right after the connection is reestablished
// it will result in timeout on the first send request
- await Task.Delay(50).ConfigureAwait(false);
+ await Task.Delay(50).Await();
foreach (FailoverRequest request in GetPendingRequests())
{
- await request.Run().ConfigureAwait(false);
+ await request.Run().Await();
}
reconnectControl.ConnectionEstablished();
@@ -266,6 +267,21 @@
}
}
+ public async Task CloseAsync()
+ {
+ if (closed.CompareAndSet(false, true))
+ {
+ try
+ {
+ if (provider != null) await provider.CloseAsync().Await();
+ }
+ catch (Exception e)
+ {
+ Tracer.Warn("Error caught while closing Provider: " + e.Message);
+ }
+ }
+ }
+
public void SetProviderListener(IProviderListener providerListener)
{
CheckClosed();
@@ -599,20 +615,20 @@
if (ReconnectAttempts == 0)
{
Tracer.Debug("Initial connect attempt will be performed immediately");
- await action();
+ await action().Await();;
}
else if (ReconnectAttempts == 1 && failoverProvider.InitialReconnectDelay > 0)
{
Tracer.Debug($"Delayed initial reconnect attempt will be in {failoverProvider.InitialReconnectDelay} milliseconds");
- await Task.Delay(TimeSpan.FromMilliseconds(failoverProvider.InitialReconnectDelay));
- await action();
+ await Task.Delay(TimeSpan.FromMilliseconds(failoverProvider.InitialReconnectDelay)).Await();;
+ await action().Await();;
}
else
{
double delay = NextReconnectDelay();
Tracer.Debug($"Next reconnect attempt will be in {delay} milliseconds");
- await Task.Delay(TimeSpan.FromMilliseconds(delay));
- await action();
+ await Task.Delay(TimeSpan.FromMilliseconds(delay)).Await();;
+ await action().Await();;
}
}
else if (ReconnectAttempts == 0)
@@ -620,21 +636,21 @@
if (failoverProvider.InitialReconnectDelay > 0)
{
Tracer.Debug($"Delayed initial reconnect attempt will be in {failoverProvider.InitialReconnectDelay} milliseconds");
- await Task.Delay(TimeSpan.FromMilliseconds(failoverProvider.InitialReconnectDelay));
- await action();
+ await Task.Delay(TimeSpan.FromMilliseconds(failoverProvider.InitialReconnectDelay)).Await();
+ await action().Await();;
}
else
{
Tracer.Debug("Initial Reconnect attempt will be performed immediately");
- await action();
+ await action().Await();;
}
}
else
{
double delay = NextReconnectDelay();
Tracer.Debug($"Next reconnect attempt will be in {delay} milliseconds");
- await Task.Delay(TimeSpan.FromMilliseconds(delay));
- await action();
+ await Task.Delay(TimeSpan.FromMilliseconds(delay)).Await();
+ await action().Await();;
}
}
diff --git a/src/NMS.AMQP/Provider/Failover/FailoverRequest.cs b/src/NMS.AMQP/Provider/Failover/FailoverRequest.cs
index 6c2ef6f..84f5822 100644
--- a/src/NMS.AMQP/Provider/Failover/FailoverRequest.cs
+++ b/src/NMS.AMQP/Provider/Failover/FailoverRequest.cs
@@ -20,6 +20,7 @@
using System.Threading;
using System.Threading.Tasks;
using Apache.NMS.AMQP.Meta;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Provider.Failover
{
@@ -61,7 +62,7 @@
{
try
{
- await this.DoTask(activeProvider).ConfigureAwait(false);
+ await this.DoTask(activeProvider).Await();
this.taskCompletionSource.TrySetResult(true);
this.failoverProvider.RemoveFailoverRequest(this);
this.cancellationTokenSource?.Dispose();
diff --git a/src/NMS.AMQP/Provider/IProvider.cs b/src/NMS.AMQP/Provider/IProvider.cs
index afe3402..b92b829 100644
--- a/src/NMS.AMQP/Provider/IProvider.cs
+++ b/src/NMS.AMQP/Provider/IProvider.cs
@@ -30,6 +30,7 @@
void Start();
Task Connect(NmsConnectionInfo connectionInfo);
void Close();
+ Task CloseAsync();
void SetProviderListener(IProviderListener providerListener);
Task CreateResource(INmsResource resourceInfo);
Task DestroyResource(INmsResource resourceInfo);
diff --git a/src/NMS.AMQP/SessionDispatcher.cs b/src/NMS.AMQP/SessionDispatcher.cs
index fed3327..70d26e4 100644
--- a/src/NMS.AMQP/SessionDispatcher.cs
+++ b/src/NMS.AMQP/SessionDispatcher.cs
@@ -18,13 +18,14 @@
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP
{
internal class SessionDispatcher
{
private readonly ActionBlock<NmsMessageConsumer.MessageDeliveryTask> actionBlock;
- private int dispatchThreadId;
+ private readonly AsyncLocal<bool> isOnDispatcherFlow = new AsyncLocal<bool>();
private readonly CancellationTokenSource cts;
public SessionDispatcher()
@@ -40,18 +41,18 @@
public void Post(NmsMessageConsumer.MessageDeliveryTask task) => actionBlock.Post(task);
- public bool IsOnDeliveryThread() => dispatchThreadId == Thread.CurrentThread.ManagedThreadId;
+ public bool IsOnDeliveryExecutionFlow() => isOnDispatcherFlow.Value;
- private void HandleTask(NmsMessageConsumer.MessageDeliveryTask messageDeliveryTask)
+ private async Task HandleTask(NmsMessageConsumer.MessageDeliveryTask messageDeliveryTask)
{
try
{
- dispatchThreadId = Thread.CurrentThread.ManagedThreadId;
- messageDeliveryTask.DeliverNextPending();
+ isOnDispatcherFlow.Value = true;
+ await messageDeliveryTask.DeliverNextPending().Await();
}
finally
{
- dispatchThreadId = -1;
+ isOnDispatcherFlow.Value = false;
}
}
diff --git a/src/NMS.AMQP/Util/PriorityMessageQueue.cs b/src/NMS.AMQP/Util/PriorityMessageQueue.cs
index 3b11e1d..9d49618 100644
--- a/src/NMS.AMQP/Util/PriorityMessageQueue.cs
+++ b/src/NMS.AMQP/Util/PriorityMessageQueue.cs
@@ -17,8 +17,9 @@
using System;
using System.Collections.Generic;
-using System.Threading;
+using System.Threading.Tasks;
using Apache.NMS.AMQP.Message;
+using Apache.NMS.AMQP.Util.Synchronization;
namespace Apache.NMS.AMQP.Util
{
@@ -26,8 +27,8 @@
{
private readonly LinkedList<InboundMessageDispatch>[] lists;
- private readonly object syncRoot = new object();
-
+ private readonly NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
+
private bool disposed;
private int count;
@@ -46,8 +47,10 @@
{
get
{
- lock (syncRoot)
+ using(syncRoot.Lock())
+ {
return count;
+ }
}
}
@@ -78,22 +81,26 @@
public void Enqueue(InboundMessageDispatch envelope)
{
- lock (syncRoot)
+ using(syncRoot.Lock())
{
GetList(envelope).AddLast(envelope);
this.count++;
- Monitor.Pulse(syncRoot);
+
+ syncRoot.Pulse();
}
+
+
}
public void EnqueueFirst(InboundMessageDispatch envelope)
{
- lock (syncRoot)
+ using(syncRoot.Lock())
{
lists[(int) MsgPriority.Highest].AddFirst(envelope);
count++;
- Monitor.Pulse(syncRoot);
- }
+ syncRoot.Pulse();
+ }
+
}
private LinkedList<InboundMessageDispatch> GetList(InboundMessageDispatch envelope)
@@ -102,20 +109,20 @@
return lists[(int) priority];
}
- public InboundMessageDispatch Dequeue(int timeout)
+ public async Task<InboundMessageDispatch> DequeueAsync(int timeout)
{
- lock (syncRoot)
+ using(await syncRoot.LockAsync())
{
while (timeout != 0 && IsEmpty && !disposed)
{
if (timeout == -1)
{
- Monitor.Wait(syncRoot);
+ await syncRoot.WaitAsync();
}
else
{
long start = DateTime.UtcNow.Ticks / 10_000L;
- Monitor.Wait(syncRoot, timeout);
+ await syncRoot.WaitAsync(timeout);
timeout = Math.Max(timeout + (int) (start - DateTime.UtcNow.Ticks / 10_000L), 0);
}
}
@@ -127,27 +134,61 @@
return RemoveFirst();
}
+
+ }
+
+
+ public InboundMessageDispatch Dequeue(int timeout)
+ {
+ using(syncRoot.Lock())
+ {
+ while (timeout != 0 && IsEmpty && !disposed)
+ {
+ if (timeout == -1)
+ {
+ syncRoot.Wait();
+ }
+ else
+ {
+ long start = DateTime.UtcNow.Ticks / 10_000L;
+ syncRoot.Wait(timeout);
+ timeout = Math.Max(timeout + (int) (start - DateTime.UtcNow.Ticks / 10_000L), 0);
+ }
+ }
+
+ if (IsEmpty || disposed)
+ {
+ return null;
+ }
+
+ return RemoveFirst();
+ }
+
}
public void Clear()
{
- lock (syncRoot)
- {
+ using(syncRoot.Lock())
+ {
for (int i = (int) MsgPriority.Highest; i >= 0; i--)
{
lists[i].Clear();
}
+
count = 0;
}
+
}
public void Dispose()
{
- lock (syncRoot)
+
+ using(syncRoot.Lock())
{
disposed = true;
- Monitor.PulseAll(syncRoot);
+ syncRoot.PulseAll();
}
+
}
}
}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Util/SymbolUtil.cs b/src/NMS.AMQP/Util/SymbolUtil.cs
index e3fe2c3..5b23b57 100644
--- a/src/NMS.AMQP/Util/SymbolUtil.cs
+++ b/src/NMS.AMQP/Util/SymbolUtil.cs
@@ -14,13 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+
using Amqp.Types;
-using Apache.NMS;
namespace Apache.NMS.AMQP.Util
{
diff --git a/src/NMS.AMQP/Util/Synchronization/NmsSynchronizationMonitor.cs b/src/NMS.AMQP/Util/Synchronization/NmsSynchronizationMonitor.cs
new file mode 100644
index 0000000..98a13f9
--- /dev/null
+++ b/src/NMS.AMQP/Util/Synchronization/NmsSynchronizationMonitor.cs
@@ -0,0 +1,280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Apache.NMS.AMQP.Util.Synchronization
+{
+ /// <summary>
+ /// Goal of this is to replace lock(syncRoot) for sync and async methods, and also have Wait and Pulse(All) capabilities
+ /// Relies on AsyncLocal construct, and should be valid along the flow of executioncontext
+ /// </summary>
+ public class NmsSynchronizationMonitor
+ {
+ // Main locking mechanism
+ private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
+
+ // Lists of executions sleeping in Wait
+ private readonly List<SemaphoreSlim> waitingLocks = new List<SemaphoreSlim>();
+
+ // SyncRoot used in locking related to Wait/Pulse
+ private readonly object waitSyncRoot = new object();
+
+ // Holds RALock in current flow of execution, should be like ThreadStatic but for async flow
+ private readonly AsyncLocal<NmsLock> asyncLocal;
+
+ public NmsSynchronizationMonitor()
+ {
+ asyncLocal = new AsyncLocal<NmsLock>();
+ }
+
+ /// <summary>
+ /// Synchronous Wait operation
+ /// </summary>
+ /// <param name="timeout"></param>
+ public void Wait(int? timeout = null)
+ {
+ var raLock = GetCurrentLock();
+
+ if (raLock == null)
+ {
+ throw new IllegalStateException("Wait called without acquiring Lock first");
+ }
+
+ // In one synchronized context we will Release monitor and sign ourself on list of sleeping locks
+ SemaphoreSlim waitSemaphore = new SemaphoreSlim(0, 1);
+ lock (waitSyncRoot)
+ {
+ ReleaseMonitor();
+ waitingLocks.Add(waitSemaphore);
+ // raLock.WaitSemaphore = new SemaphoreSlim(0, 1);
+ }
+
+ try
+ {
+ // Now wait, if our lock was pulsed just before, we will not really sleep, but instead continue ...
+ waitSemaphore.Wait(timeout ?? -1);
+
+ lock (waitSyncRoot)
+ {
+ waitingLocks.Remove(waitSemaphore);
+ waitSemaphore.Dispose();
+ }
+ }
+ finally
+ {
+ // Enter again, but we need to use the same raLock as before
+ EnterMonitor();
+ }
+ }
+
+
+ public async Task WaitAsync(int? timeout = null)
+ {
+ var raLock = GetCurrentLock();
+
+ if (raLock == null)
+ {
+ throw new IllegalStateException("Wait called without acquiring Lock first");
+ }
+
+ SemaphoreSlim waitSemaphore = new SemaphoreSlim(0, 1);
+
+ lock (waitSyncRoot)
+ {
+ ReleaseMonitor();
+ waitingLocks.Add(waitSemaphore);
+ }
+
+ try
+ {
+ // Here between lock and waiting is a problematic thing, two pulses can release the same thing
+ await waitSemaphore.WaitAsync(timeout ?? -1).Await();
+
+ lock (waitSyncRoot)
+ {
+ waitingLocks.Remove(waitSemaphore);
+ waitSemaphore.Dispose();
+ waitSemaphore.Dispose();
+ }
+ }
+ finally
+ {
+ // Enter again, but we need to use the same raLock as before, and also asyncLocal a
+ await EnterMonitorAsync().Await();
+ }
+ }
+
+ public void Pulse()
+ {
+ lock (waitSyncRoot)
+ {
+ var firstWaiting = waitingLocks.FirstOrDefault();
+ if (firstWaiting != null)
+ {
+ firstWaiting.Release();
+ waitingLocks.Remove(firstWaiting);
+ }
+ }
+ }
+
+ public void PulseAll()
+ {
+ lock (waitSyncRoot)
+ {
+ waitingLocks.ForEach(a => { a.Release(); });
+ waitingLocks.Clear();
+ }
+ }
+
+
+ /// <summary>
+ /// Allows to create a sub context where asyncLocal will be removed and thus not passed for example to something that could carry it and thus has wrong locks acquired
+ /// </summary>
+ /// <returns></returns>
+ public IDisposable Exclude()
+ {
+ return new ExcludeLock(this);
+ }
+
+
+ public IDisposable Lock()
+ {
+ NmsLock nmsLock = GetOrCreateCurrentLock();
+ nmsLock.Enter();
+ return nmsLock;
+ }
+
+ public Task<IDisposable>
+ LockAsync() // This should not be async method, cause setting asyncLocal inside GetOrCreateCurrentLock may be only limited to this method in such case
+ {
+ NmsLock nmsLock = GetOrCreateCurrentLock();
+ return nmsLock.EnterAsync();
+ }
+
+
+ private NmsLock GetOrCreateCurrentLock()
+ {
+ if (asyncLocal.Value == null)
+ {
+ asyncLocal.Value = new NmsLock(this);
+ }
+
+ return asyncLocal.Value;
+ }
+
+ private NmsLock GetCurrentLock()
+ {
+ var context = asyncLocal.Value;
+ return context;
+ }
+
+ private void SetCurrentLock(NmsLock nmsLock)
+ {
+ asyncLocal.Value = nmsLock;
+ }
+
+ private void EnterMonitor()
+ {
+ semaphoreSlim.Wait();
+ }
+
+ private Task EnterMonitorAsync()
+ {
+ return semaphoreSlim.WaitAsync();
+ }
+
+ private void ReleaseMonitor()
+ {
+ semaphoreSlim.Release();
+ }
+
+ private class NmsLock : IDisposable
+ {
+ private int NestCounter { get; set; }
+
+ public Guid Id = Guid.NewGuid();
+
+ private readonly NmsSynchronizationMonitor parent;
+
+ public NmsLock(NmsSynchronizationMonitor parent)
+ {
+ this.parent = parent;
+ }
+
+ public void Enter()
+ {
+ if (NestCounter == 0)
+ {
+ parent.EnterMonitor();
+ }
+
+ NestCounter++;
+ }
+
+ public async Task<IDisposable> EnterAsync()
+ {
+ if (NestCounter == 0)
+ {
+ await parent.EnterMonitorAsync();
+ }
+
+ NestCounter++;
+ return this;
+ }
+
+ private void Leave()
+ {
+ NestCounter--;
+ if (NestCounter <= 0)
+ {
+ parent.ReleaseMonitor();
+ parent.SetCurrentLock(null);
+ }
+ }
+
+ public void Dispose()
+ {
+ Leave();
+ }
+ }
+
+ private class ExcludeLock : IDisposable
+ {
+ private readonly NmsSynchronizationMonitor parent;
+
+ private readonly NmsLock currentLock;
+
+ public ExcludeLock(NmsSynchronizationMonitor parent)
+ {
+ this.parent = parent;
+
+ currentLock = parent.GetCurrentLock();
+ parent.SetCurrentLock(null);
+ }
+
+ public void Dispose()
+ {
+ parent.SetCurrentLock(this.currentLock);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Util/Synchronization/TaskExtensions.cs b/src/NMS.AMQP/Util/Synchronization/TaskExtensions.cs
new file mode 100644
index 0000000..e5e4072
--- /dev/null
+++ b/src/NMS.AMQP/Util/Synchronization/TaskExtensions.cs
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+
+namespace Apache.NMS.AMQP.Util.Synchronization
+{
+ public static class TaskExtensions
+ {
+
+ public static T GetAsyncResult<T>(this Task<T> task)
+ {
+ return task.Await().GetAwaiter().GetResult();
+ }
+
+ public static void GetAsyncResult(this Task task)
+ {
+ task.Await().GetAwaiter().GetResult();
+ }
+
+ public static async Task AwaitRunContinuationAsync(this Task task)
+ {
+ await task.Await();
+ if (TaskSynchronizationSettings.TryToRunCertainContunuationsAsynchronously)
+ {
+ await Task.Yield();
+ }
+ }
+
+ public static async Task<T> AwaitRunContinuationAsync<T>(this Task<T> task)
+ {
+ T t = await task.Await();
+ if (TaskSynchronizationSettings.TryToRunCertainContunuationsAsynchronously)
+ {
+ await Task.Yield();
+ }
+ return t;
+ }
+
+ public static ConfiguredTaskAwaitable Await(this Task task)
+ {
+ return task.ConfigureAwait(TaskSynchronizationSettings.ContinueOnCapturedContext);
+ }
+
+ public static ConfiguredTaskAwaitable<T> Await<T>(this Task<T> task)
+ {
+ return task.ConfigureAwait(TaskSynchronizationSettings.ContinueOnCapturedContext);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Util/Synchronization/TaskSynchronizationSettings.cs b/src/NMS.AMQP/Util/Synchronization/TaskSynchronizationSettings.cs
new file mode 100644
index 0000000..fa2e3b4
--- /dev/null
+++ b/src/NMS.AMQP/Util/Synchronization/TaskSynchronizationSettings.cs
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.NMS.AMQP.Util.Synchronization
+{
+
+ public static class TaskSynchronizationSettings
+ {
+ /// <summary>
+ /// General settings for awaits, whether to force continuation on captured context
+ /// </summary>
+ public static bool ContinueOnCapturedContext { get; set; } = false;
+
+ /// <summary>
+ /// In AMQP library there is a async pump, that is calling methods, and in turn continuation may be inlined, if this happens
+ /// and continuation is waiting synchronously on something, then it stops processing in that pump, and it may lead to deadlocks
+ /// if continuation is waiting on something (some message) that async pump was supposed to deliver-but now async pump stopped processing
+ /// It seems it was also discussed here https://github.com/Azure/amqpnetlite/issues/237
+ /// So by default there is a try to force continuation from different thread (by using) yield, on those tasks that it seems that their
+ /// continuation may be from async pump
+ /// </summary>
+ public static bool TryToRunCertainContunuationsAsynchronously { get; set; } = true;
+ }
+}
\ No newline at end of file
diff --git a/src/NMS.AMQP/Util/UriUtil.cs b/src/NMS.AMQP/Util/UriUtil.cs
index acfbef8..ad06886 100644
--- a/src/NMS.AMQP/Util/UriUtil.cs
+++ b/src/NMS.AMQP/Util/UriUtil.cs
@@ -15,12 +15,7 @@
* limitations under the License.
*/
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Amqp;
-using Apache.NMS;
namespace Apache.NMS.AMQP.Util
{
diff --git a/test/Apache-NMS-AMQP-Interop-Test/Transactions/NmsTransactedConsumerTest.cs b/test/Apache-NMS-AMQP-Interop-Test/Transactions/NmsTransactedConsumerTest.cs
index 37dbd29..55c4eee 100644
--- a/test/Apache-NMS-AMQP-Interop-Test/Transactions/NmsTransactedConsumerTest.cs
+++ b/test/Apache-NMS-AMQP-Interop-Test/Transactions/NmsTransactedConsumerTest.cs
@@ -46,6 +46,8 @@
[Test, Timeout(60_000)]
public void TestConsumedInTxAreAcked()
{
+ PurgeQueue(TimeSpan.FromSeconds(2));
+
Connection = CreateAmqpConnection();
Connection.Start();
diff --git a/test/Apache-NMS-AMQP-Test/Integration/AmqpAcknowledgmentsIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/AmqpAcknowledgmentsIntegrationTest.cs
index ba09d9c..2609400 100644
--- a/test/Apache-NMS-AMQP-Test/Integration/AmqpAcknowledgmentsIntegrationTest.cs
+++ b/test/Apache-NMS-AMQP-Test/Integration/AmqpAcknowledgmentsIntegrationTest.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
+using System.Threading.Tasks;
using Amqp.Framing;
using Apache.NMS;
using Apache.NMS.AMQP.Util;
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/AmqpAcknowledgmentsIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/AmqpAcknowledgmentsIntegrationTest.cs
new file mode 100644
index 0000000..b419ad4
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/AmqpAcknowledgmentsIntegrationTest.cs
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Apache.NMS;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class AmqpAcknowledgmentsIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestAcknowledgeFailsAfterSessionIsClosed()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: 1);
+ testPeer.ExpectEnd();
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ IMessage receivedMessage = await consumer.ReceiveAsync(TimeSpan.FromSeconds(6));
+ Assert.NotNull(receivedMessage, "Message was not received");
+
+ await session.CloseAsync();
+
+ Assert.CatchAsync<NMSException>(async () => await receivedMessage.AcknowledgeAsync(), "Should not be able to acknowledge the message after session closed");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestClientAcknowledgeMessages()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ int msgCount = 3;
+
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ IMessage lastReceivedMessage = null;
+ for (int i = 0; i < msgCount; i++)
+ {
+ lastReceivedMessage = await consumer.ReceiveAsync();
+ Assert.NotNull(lastReceivedMessage, "Message " + i + " was not received");
+ }
+
+ for (int i = 0; i < msgCount; i++)
+ {
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+ }
+
+ await lastReceivedMessage.AcknowledgeAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestClientAcknowledgeMessagesAsync()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ int msgCount = 3;
+
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ CountdownEvent latch = new CountdownEvent(3);
+
+ IMessage lastReceivedMessage = null;
+ consumer.Listener += message =>
+ {
+ lastReceivedMessage = message;
+ latch.Signal();
+ };
+
+ Assert.True(latch.Wait(2000));
+
+ for (int i = 0; i < msgCount; i++)
+ {
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+ }
+
+ await lastReceivedMessage.AcknowledgeAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestAcknowledgeIndividualMessages()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ int msgCount = 6;
+
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.IndividualAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(
+ message: CreateMessageWithNullContent(),
+ count: msgCount,
+ drain: false,
+ nextIncomingId: 1,
+ addMessageNumberProperty: true,
+ sendDrainFlowResponse: false,
+ sendSettled: false,
+ creditMatcher: credit => Assert.Greater(credit, msgCount));
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ var messages = new List<IMessage>();
+ for (int i = 0; i < msgCount; i++)
+ {
+ IMessage message = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+ Assert.NotNull(message, "Message " + i + " was not received");
+ messages.Add(message);
+
+ Assert.AreEqual(i, message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER), "unexpected message number property");
+ }
+
+ Action<DeliveryState> dispositionMatcher = state => { Assert.AreEqual(state.Descriptor.Code, MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code); };
+
+ // Acknowledge the messages in a random order and verify the individual dispositions have expected delivery state.
+ Random random = new Random();
+ for (int i = 0; i < msgCount; i++)
+ {
+ var message = messages[random.Next(msgCount - i)];
+ messages.Remove(message);
+
+ uint deliveryNumber = (uint) message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER) + 1;
+
+ testPeer.ExpectDisposition(settled: true, stateMatcher: dispositionMatcher, firstDeliveryId: deliveryNumber, lastDeliveryId: deliveryNumber);
+
+ await message.AcknowledgeAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestAcknowledgeIndividualMessagesAsync()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ int msgCount = 6;
+
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.IndividualAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(
+ message: CreateMessageWithNullContent(),
+ count: msgCount,
+ drain: false,
+ nextIncomingId: 1,
+ addMessageNumberProperty: true,
+ sendDrainFlowResponse: false,
+ sendSettled: false,
+ creditMatcher: credit => Assert.Greater(credit, msgCount));
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ CountdownEvent latch = new CountdownEvent(msgCount);
+ List<ITextMessage> messages = new List<ITextMessage>();
+ consumer.Listener += message =>
+ {
+ messages.Add((ITextMessage) message);
+ latch.Signal();
+ };
+
+ Assert.True(latch.Wait(TimeSpan.FromMilliseconds(1000)), $"Should receive: {msgCount}, but received: {messages.Count}");
+
+ Action<DeliveryState> dispositionMatcher = state => { Assert.AreEqual(state.Descriptor.Code, MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code); };
+
+ // Acknowledge the messages in a random order and verify the individual dispositions have expected delivery state.
+ Random random = new Random();
+ for (int i = 0; i < msgCount; i++)
+ {
+ var message = messages[random.Next(msgCount - i)];
+ messages.Remove(message);
+
+ uint deliveryNumber = (uint) message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER) + 1;
+
+ testPeer.ExpectDisposition(settled: true, stateMatcher: dispositionMatcher, firstDeliveryId: deliveryNumber, lastDeliveryId: deliveryNumber);
+
+ await message.AcknowledgeAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestAutoAcknowledgeMessages()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ int msgCount = 6;
+
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ for (int i = 0; i < msgCount; i++)
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ for (int i = 0; i < msgCount; i++)
+ Assert.NotNull(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000)), $"Message {i} not received within given timeout.");
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestAutoAcknowledgeMessagesAsync()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ int msgCount = 6;
+
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ for (int i = 0; i < msgCount; i++)
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ consumer.Listener += (message) => { };
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/ConnectionIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/ConnectionIntegrationTest.cs
new file mode 100644
index 0000000..03e2b29
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/ConnectionIntegrationTest.cs
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Apache.NMS;
+using Apache.NMS.AMQP;
+using NMS.AMQP.Test.TestAmqp;
+using NMS.AMQP.Test.TestAmqp.BasicTypes;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class ConnectionIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestCreateAndCloseConnection()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestExplicitConnectionCloseListenerIsNotInvoked()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent exceptionFired = new ManualResetEvent(false);
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ connection.ExceptionListener += exception => { exceptionFired.Set(); };
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ Assert.IsFalse(exceptionFired.WaitOne(TimeSpan.FromMilliseconds(100)));
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateAutoAckSession()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ Assert.NotNull(session, "Session should not be null");
+ testPeer.ExpectClose();
+ Assert.AreEqual(AcknowledgementMode.AutoAcknowledge, session.AcknowledgementMode);
+ await connection.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateAutoAckSessionByDefault()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync();
+ Assert.NotNull(session, "Session should not be null");
+ Assert.AreEqual(AcknowledgementMode.AutoAcknowledge, session.AcknowledgementMode);
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRemotelyCloseConnectionDuringSessionCreation()
+ {
+ string errorMessage = "buba";
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ // Expect the begin, then explicitly close the connection with an error
+ testPeer.ExpectBegin(sendResponse: false);
+ testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: AmqpError.NOT_ALLOWED, errorMessage: errorMessage);
+
+ try
+ {
+ await connection.CreateSessionAsync();
+ Assert.Fail("Expected exception to be thrown");
+ }
+ catch (NMSException e)
+ {
+ Assert.True(e.Message.Contains(errorMessage));
+ }
+
+ await connection.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRemotelyEndConnectionListenerInvoked()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent done = new ManualResetEvent(false);
+
+ // Don't set a ClientId, so that the underlying AMQP connection isn't established yet
+ IConnection connection = await EstablishConnectionAsync(testPeer: testPeer, setClientId: false);
+
+ // Tell the test peer to close the connection when executing its last handler
+ testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: ConnectionError.CONNECTION_FORCED, errorMessage: "buba");
+
+ connection.ExceptionListener += exception => done.Set();
+
+ // Trigger the underlying AMQP connection
+ await connection.StartAsync();
+
+ Assert.IsTrue(done.WaitOne(TimeSpan.FromSeconds(5)), "Connection should report failure");
+
+ await connection.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRemotelyEndConnectionWithSessionWithConsumer()
+ {
+ string errorMessage = "buba";
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ // Create a consumer, then remotely end the connection afterwards.
+ testPeer.ExpectReceiverAttach();
+
+ testPeer.ExpectLinkFlow();
+ testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: AmqpError.RESOURCE_LIMIT_EXCEEDED, errorMessage: errorMessage);
+
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ Assert.That(() => ((NmsConnection) connection).IsConnected, Is.False.After(10_000, 100), "Connection never closes.");
+
+ try
+ {
+ await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ Assert.Fail("Expected ISE to be thrown due to being closed");
+ }
+ catch (NMSConnectionException e)
+ {
+ Assert.True(e.ToString().Contains(AmqpError.RESOURCE_LIMIT_EXCEEDED));
+ Assert.True(e.ToString().Contains(errorMessage));
+ }
+
+ // Verify the session is now marked closed
+ try
+ {
+ var _ = session.AcknowledgementMode;
+ Assert.Fail("Expected ISE to be thrown due to being closed");
+ }
+ catch (IllegalStateException e)
+ {
+ Assert.True(e.ToString().Contains(AmqpError.RESOURCE_LIMIT_EXCEEDED));
+ Assert.True(e.ToString().Contains(errorMessage));
+ }
+
+ // Verify the consumer is now marked closed
+ try
+ {
+ consumer.Listener += message => { };
+ }
+ catch (IllegalStateException e)
+ {
+ Assert.True(e.ToString().Contains(AmqpError.RESOURCE_LIMIT_EXCEEDED));
+ Assert.True(e.ToString().Contains(errorMessage));
+ }
+
+ // Try closing them explicitly, should effectively no-op in client.
+ // The test peer will throw during close if it sends anything.
+ await consumer.CloseAsync();
+ await session.CloseAsync();
+ await connection.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConnectionStartStop()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ int msgCount = 10;
+
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: msgCount);
+
+ var consumer = await session.CreateConsumerAsync(queue);
+
+ for (int i = 0; i < 5; i++)
+ {
+ IMessage message = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000));
+ Assert.IsNotNull(message);
+ }
+
+ // stop the connection, consumers shouldn't receive any more messages
+ await connection.StopAsync();
+
+ // No messages should arrive to consumer as connection has been stopped
+ Assert.IsNull(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(100)), "Message arrived despite the fact, that the connection was stopped.");
+
+ // restart the connection
+ await connection.StartAsync();
+
+ // The second batch of messages should be delivered
+ for (int i = 0; i < 5; i++)
+ {
+ IMessage message = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000));
+ Assert.IsNotNull(message);
+ }
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/ConsumerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/ConsumerIntegrationTest.cs
new file mode 100644
index 0000000..b4f9885
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/ConsumerIntegrationTest.cs
@@ -0,0 +1,974 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Apache.NMS;
+using Apache.NMS.AMQP;
+using Apache.NMS.AMQP.Message;
+using Apache.NMS.AMQP.Util;
+using Moq;
+using NMS.AMQP.Test.TestAmqp;
+using NMS.AMQP.Test.TestAmqp.BasicTypes;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class ConsumerIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestCloseConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRemotelyCloseConsumer()
+ {
+ Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+ string errorMessage = "buba";
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent consumerClosed = new ManualResetEvent(false);
+ ManualResetEvent exceptionFired = new ManualResetEvent(false);
+
+ mockConnectionListener
+ .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+ .Callback(() => consumerClosed.Set());
+
+ NmsConnection connection = (NmsConnection) await EstablishConnectionAsync(testPeer);
+ connection.AddConnectionListener(mockConnectionListener.Object);
+ connection.ExceptionListener += exception => { exceptionFired.Set(); };
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ // Create a consumer, then remotely end it afterwards.
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, errorMessage: errorMessage, delayBeforeSend: 400);
+
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ // Verify the consumer gets marked closed
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ Assert.True(consumerClosed.WaitOne(2000), "Consumer closed callback didn't trigger");
+ Assert.False(exceptionFired.WaitOne(20), "Exception listener shouldn't fire with no MessageListener");
+
+ // Try closing it explicitly, should effectively no-op in client.
+ // The test peer will throw during close if it sends anything.
+ await consumer.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRemotelyCloseConsumerWithMessageListenerFiresExceptionListener()
+ {
+ Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+ string errorMessage = "buba";
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent consumerClosed = new ManualResetEvent(false);
+ ManualResetEvent exceptionFired = new ManualResetEvent(false);
+
+ mockConnectionListener
+ .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+ .Callback(() => consumerClosed.Set());
+
+ NmsConnection connection = (NmsConnection) await EstablishConnectionAsync(testPeer);
+ connection.AddConnectionListener(mockConnectionListener.Object);
+ connection.ExceptionListener += exception => { exceptionFired.Set(); };
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ // Create a consumer, then remotely end it afterwards.
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, errorMessage: errorMessage, 10);
+
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ consumer.Listener += message => { };
+
+ // Verify the consumer gets marked closed
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ Assert.True(consumerClosed.WaitOne(2000), "Consumer closed callback didn't trigger");
+ Assert.True(exceptionFired.WaitOne(2000), "Exception listener should have fired with a MessageListener");
+
+ // Try closing it explicitly, should effectively no-op in client.
+ // The test peer will throw during close if it sends anything.
+ await consumer.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestReceiveMessageWithReceiveZeroTimeout()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = null } }, count: 1);
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+ IMessage message = await consumer.ReceiveAsync();
+ Assert.NotNull(message, "A message should have been received");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(10000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestExceptionInOnMessageReleasesInAutoAckMode()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = null } }, count: 1);
+ testPeer.ExpectDispositionThatIsReleasedAndSettled();
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+ consumer.Listener += message => throw new Exception();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(10000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCloseDurableTopicSubscriberDetachesWithCloseFalse()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string topicName = "myTopic";
+ string subscriptionName = "mySubscription";
+ ITopic topic = await session.GetTopicAsync(topicName);
+
+ testPeer.ExpectDurableSubscriberAttach(topicName, subscriptionName);
+ testPeer.ExpectLinkFlow();
+
+ IMessageConsumer durableConsumer = await session.CreateDurableConsumerAsync(topic, subscriptionName, null, false);
+
+ testPeer.ExpectDetach(expectClosed: false, sendResponse: true, replyClosed: false);
+ await durableConsumer.CloseAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerReceiveThrowsIfConnectionLost()
+ {
+ await DoTestConsumerReceiveThrowsIfConnectionLost(false);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerTimedReceiveThrowsIfConnectionLost()
+ {
+ await DoTestConsumerReceiveThrowsIfConnectionLost(true);
+ }
+
+ private async Task DoTestConsumerReceiveThrowsIfConnectionLost(bool useTimeout)
+ {
+ ManualResetEvent consumerReady = new ManualResetEvent(false);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("queue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.RunAfterLastHandler(() => { consumerReady.WaitOne(2000); });
+ testPeer.DropAfterLastMatcher(delay: 10);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+ consumerReady.Set();
+
+ try
+ {
+ if (useTimeout)
+ {
+ await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10_0000));
+ }
+ else
+ {
+ await consumer.ReceiveAsync();
+ }
+
+
+ Assert.Fail("An exception should have been thrown");
+ }
+ catch (NMSException)
+ {
+ // Expected
+ }
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerReceiveNoWaitThrowsIfConnectionLost()
+ {
+ ManualResetEvent disconnected = new ManualResetEvent(false);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ NmsConnection connection = (NmsConnection) await EstablishConnectionAsync(testPeer);
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionFailure(It.IsAny<NMSException>()))
+ .Callback(() => { disconnected.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("queue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: ConnectionError.CONNECTION_FORCED, errorMessage: "buba");
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ Assert.True(disconnected.WaitOne(), "Connection should be disconnected");
+
+ try
+ {
+ consumer.ReceiveNoWait();
+ Assert.Fail("An exception should have been thrown");
+ }
+ catch (NMSException)
+ {
+ // Expected
+ }
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSetMessageListenerAfterStartAndSend()
+ {
+ int messageCount = 4;
+ CountdownEvent latch = new CountdownEvent(messageCount);
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), messageCount);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ for (int i = 0; i < messageCount; i++)
+ {
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+ }
+
+ consumer.Listener += message => latch.Signal();
+
+ Assert.True(latch.Wait(4000), "Messages not received within given timeout. Count remaining: " + latch.CurrentCount);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ await consumer.CloseAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestNoReceivedMessagesWhenConnectionNotStarted()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 3);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ // Wait for a message to arrive then try and receive it, which should not happen
+ // since the connection is not started.
+ Assert.Null(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(100)));
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestNoReceivedNoWaitMessagesWhenConnectionNotStarted()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 3);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ // Wait for a message to arrive then try and receive it, which should not happen
+ // since the connection is not started.
+ Assert.Null(consumer.ReceiveNoWait());
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSyncReceiveFailsWhenListenerSet()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ consumer.Listener += message => { };
+
+ Assert.CatchAsync<NMSException>(async () => await consumer.ReceiveAsync(), "Should have thrown an exception.");
+ Assert.CatchAsync<NMSException>(async () => await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000)), "Should have thrown an exception.");
+ Assert.CatchAsync<NMSException>(async () => consumer.ReceiveNoWait(), "Should have thrown an exception.");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateProducerInOnMessage()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IQueue outbound = await session.GetQueueAsync("ForwardDest");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ testPeer.ExpectSenderAttach();
+ testPeer.ExpectTransfer(messageMatcher: Assert.NotNull);
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ consumer.Listener += message =>
+ {
+ IMessageProducer producer = session.CreateProducer(outbound);
+ producer.Send(message);
+ producer.Close();
+ };
+
+ testPeer.WaitForAllMatchersToComplete(10_000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestMessageListenerCallsConnectionCloseThrowsIllegalStateException()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ ManualResetEvent latch = new ManualResetEvent(false);
+ Exception exception = null;
+ consumer.Listener += message =>
+ {
+ try
+ {
+ connection.Close();
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+
+ latch.Set();
+ };
+
+ Assert.True(latch.WaitOne(4000), "Messages not received within given timeout.");
+ Assert.IsNotNull(exception);
+ Assert.IsInstanceOf<IllegalStateException>(exception);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestMessageListenerCallsConnectionStopThrowsIllegalStateException()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ ManualResetEvent latch = new ManualResetEvent(false);
+ Exception exception = null;
+ consumer.Listener += message =>
+ {
+ try
+ {
+ connection.Stop();
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+
+ latch.Set();
+ };
+
+ Assert.True(latch.WaitOne(3000), "Messages not received within given timeout.");
+ Assert.IsNotNull(exception);
+ Assert.IsInstanceOf<IllegalStateException>(exception);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestMessageListenerCallsSessionCloseThrowsIllegalStateException()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ ManualResetEvent latch = new ManualResetEvent(false);
+ Exception exception = null;
+ consumer.Listener += message =>
+ {
+ try
+ {
+ session.Close();
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+
+ latch.Set();
+ };
+
+ Assert.True(latch.WaitOne(3000), "Messages not received within given timeout.");
+ Assert.IsNotNull(exception);
+ Assert.IsInstanceOf<IllegalStateException>(exception);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ // TODO: To be fixed
+ [Test, Timeout(20_000), Ignore("Ignore")]
+ public async Task TestMessageListenerClosesItsConsumer()
+ {
+ var latch = new ManualResetEvent(false);
+ var exceptionListenerFired = new ManualResetEvent(false);
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ connection.ExceptionListener += _ => exceptionListenerFired.Set();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true, creditMatcher: credit => Assert.AreEqual(99, credit)); // Not sure if expected credit is right
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ Exception exception = null;
+ consumer.Listener += message =>
+ {
+ try
+ {
+ consumer.Close();
+ latch.Set();
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(1000)), "Process not completed within given timeout");
+ Assert.IsNull(exception, "No error expected during close");
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ Assert.False(exceptionListenerFired.WaitOne(20), "Exception listener shouldn't have fired");
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRecoverOrderingWithAsyncConsumer()
+ {
+ ManualResetEvent latch = new ManualResetEvent(false);
+ Exception asyncError = null;
+
+ int recoverCount = 5;
+ int messageCount = 8;
+ int testPayloadLength = 255;
+ string payload = Encoding.UTF8.GetString(new byte[testPayloadLength]);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(
+ message: new Amqp.Message() { BodySection = new AmqpValue() { Value = payload } },
+ count: messageCount,
+ drain: false,
+ nextIncomingId: 1,
+ addMessageNumberProperty: true,
+ sendDrainFlowResponse: false,
+ sendSettled: false,
+ creditMatcher: credit => Assert.Greater(credit, messageCount)
+ );
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ bool complete = false;
+ int messageSeen = 0;
+ int expectedIndex = 0;
+ consumer.Listener += message =>
+ {
+ if (complete)
+ {
+ return;
+ }
+
+ try
+ {
+ int actualIndex = message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER);
+ Assert.AreEqual(expectedIndex, actualIndex, "Received Message Out Of Order");
+
+ // don't ack the message until we receive it X times
+ if (messageSeen < recoverCount)
+ {
+ session.Recover();
+ messageSeen++;
+ }
+ else
+ {
+ messageSeen = 0;
+ expectedIndex++;
+
+ // Have the peer expect the accept the disposition (1-based, hence pre-incremented).
+ testPeer.ExpectDisposition(settled: true,
+ stateMatcher: state => Assert.AreEqual(state.Descriptor.Code, MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code
+ ));
+
+ message.Acknowledge();
+
+ if (expectedIndex == messageCount)
+ {
+ complete = true;
+ latch.Set();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ complete = true;
+ asyncError = e;
+ latch.Set();
+ }
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromSeconds(15)), "Messages not received within given timeout.");
+ Assert.IsNull(asyncError, "Unexpected exception");
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerCloseWaitsForAsyncDeliveryToComplete()
+ {
+ ManualResetEvent latch = new ManualResetEvent(false);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ consumer.Listener += _ =>
+ {
+ latch.Set();
+ Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSessionCloseWaitsForAsyncDeliveryToComplete()
+ {
+ ManualResetEvent latch = new ManualResetEvent(false);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ consumer.Listener += _ =>
+ {
+ latch.Set();
+ Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+ testPeer.ExpectEnd();
+ await session.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConnectionCloseWaitsForAsyncDeliveryToComplete()
+ {
+ ManualResetEvent latch = new ManualResetEvent(false);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ await connection.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ consumer.Listener += _ =>
+ {
+ latch.Set();
+ Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRecoveredMessageShouldNotBeMutated()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.ClientAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ string originalPayload = "testMessage";
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message { BodySection = new AmqpValue() { Value = originalPayload } }, count: 1);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(destination);
+ NmsTextMessage message = await consumer.ReceiveAsync() as NmsTextMessage;
+ Assert.NotNull(message);
+ message.IsReadOnlyBody = false;
+ message.Text = message.Text + "Received";
+ await session.RecoverAsync();
+
+ ITextMessage recoveredMessage = await consumer.ReceiveAsync() as ITextMessage;
+ Assert.IsNotNull(recoveredMessage);
+ Assert.AreNotEqual(message.Text, recoveredMessage.Text);
+ Assert.AreEqual(originalPayload, recoveredMessage.Text);
+ Assert.AreNotSame(message, recoveredMessage);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/FailoverIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/FailoverIntegrationTest.cs
new file mode 100644
index 0000000..74f66ad
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/FailoverIntegrationTest.cs
@@ -0,0 +1,1164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP;
+using Moq;
+using NLog;
+using NMS.AMQP.Test.TestAmqp;
+using NMS.AMQP.Test.TestAmqp.BasicTypes;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class FailoverIntegrationTestAsync : IntegrationTestFixture
+ {
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+ [Test, Timeout(20_000), Category("Windows")]
+ public async Task TestFailoverHandlesDropThenRejectionCloseAfterConnect()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ using (TestAmqpPeer rejectingPeer = new TestAmqpPeer())
+ using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent originalConnected = new ManualResetEvent(false);
+ ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+ // Create a peer to connect to, one to fail to reconnect to, and a final one to reconnect to
+ var originalUri = CreatePeerUri(originalPeer);
+ var rejectingUri = CreatePeerUri(rejectingPeer);
+ var finalUri = CreatePeerUri(finalPeer);
+
+ Logger.Info($"Original peer is at: {originalUri}");
+ Logger.Info($"Rejecting peer is at: {rejectingUri}");
+ Logger.Info($"Final peer is at: {finalUri}");
+
+ // Connect to the first
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+
+ long ird = 0;
+ long rd = 2000;
+
+ NmsConnection connection = await EstablishAnonymousConnection("failover.initialReconnectDelay=" + ird + "&failover.reconnectDelay=" + rd + "&failover.maxReconnectAttempts=10", originalPeer,
+ rejectingPeer, finalPeer);
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+ .Callback(() => { originalConnected.Set(); });
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+ .Callback(() => { finalConnected.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+ Assert.False(finalConnected.WaitOne(TimeSpan.FromMilliseconds(100)), "Should not yet have connected to final peer");
+
+ // Set expectations on rejecting and final peer
+ rejectingPeer.RejectConnect(AmqpError.NOT_FOUND, "Resource could not be located");
+
+ finalPeer.ExpectSaslAnonymous();
+ finalPeer.ExpectOpen();
+ finalPeer.ExpectBegin();
+
+ // Close the original peer and wait for things to shake out.
+ originalPeer.Close(sendClose: true);
+
+ rejectingPeer.WaitForAllMatchersToComplete(2000);
+
+ Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(10)), "Should connect to final peer");
+
+ finalPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ finalPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestFailoverHandlesDropWithModifiedInitialReconnectDelay()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent originalConnected = new ManualResetEvent(false);
+ ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+ // Create a peer to connect to, then one to reconnect to
+ var originalUri = CreatePeerUri(originalPeer);
+ var finalUri = CreatePeerUri(finalPeer);
+
+ // Connect to the first peer
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectBegin();
+ originalPeer.DropAfterLastMatcher();
+
+ NmsConnection connection = await EstablishAnonymousConnection("failover.initialReconnectDelay=1&failover.reconnectDelay=600&failover.maxReconnectAttempts=10", originalPeer, finalPeer);
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+ .Callback(() => { originalConnected.Set(); });
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+ .Callback(() => { finalConnected.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+ // Post Failover Expectations of FinalPeer
+ finalPeer.ExpectSaslAnonymous();
+ finalPeer.ExpectOpen();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectBegin();
+
+ await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+
+ // Shut it down
+ finalPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ finalPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestFailoverInitialReconnectDelayDoesNotApplyToInitialConnect()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ {
+ // Connect to the first peer
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+
+ int delay = 20000;
+ Stopwatch watch = new Stopwatch();
+ watch.Start();
+
+ NmsConnection connection = await EstablishAnonymousConnection("failover.initialReconnectDelay=" + delay + "&failover.maxReconnectAttempts=1", originalPeer);
+ await connection.StartAsync();
+
+ watch.Stop();
+
+ Assert.True(watch.ElapsedMilliseconds < delay,
+ "Initial connect should not have delayed for the specified initialReconnectDelay." + "Elapsed=" + watch.ElapsedMilliseconds + ", delay=" + delay);
+ Assert.True(watch.ElapsedMilliseconds < 5000, $"Connection took longer than reasonable: {watch.ElapsedMilliseconds}");
+
+ // Shut it down
+ originalPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ originalPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestFailoverHandlesDropAfterSessionCloseRequested()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent originalConnected = new ManualResetEvent(false);
+
+ // Create a peer to connect to
+ var originalUri = CreatePeerUri(originalPeer);
+
+ // Connect to the first peer
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+ .Callback(() => { originalConnected.Set(); });
+
+ NmsConnection connection = await EstablishAnonymousConnection(originalPeer);
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to peer");
+
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectEnd(sendResponse: false);
+ originalPeer.DropAfterLastMatcher();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ ManualResetEvent sessionCloseCompleted = new ManualResetEvent(false);
+ Exception sessionClosedThrew = null;
+
+ Task.Run(() =>
+ {
+ try
+ {
+ session.Close();
+ }
+ catch (Exception e)
+ {
+ sessionClosedThrew = e;
+ }
+ finally
+ {
+ sessionCloseCompleted.Set();
+ }
+ });
+
+ originalPeer.WaitForAllMatchersToComplete(2000);
+
+ Assert.IsTrue(sessionCloseCompleted.WaitOne(TimeSpan.FromSeconds(3)), "Session close should have completed by now");
+ Assert.IsNull(sessionClosedThrew, "Session close should have completed normally");
+
+ await connection.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateConsumerFailsWhenLinkRefused()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ testPeer.ExpectSaslAnonymous();
+ testPeer.ExpectOpen();
+ testPeer.ExpectBegin();
+
+ NmsConnection connection = await EstablishAnonymousConnection(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string topicName = "myTopic";
+ ITopic topic = await session.GetTopicAsync(topicName);
+
+ // Expect a link to a topic node, which we will then refuse
+ testPeer.ExpectReceiverAttach(sourceMatcher: source =>
+ {
+ Assert.AreEqual(topicName, source.Address);
+ Assert.IsFalse(source.Dynamic);
+ Assert.AreEqual((uint) TerminusDurability.NONE, source.Durable);
+ }, targetMatcher: Assert.NotNull, linkNameMatcher: Assert.NotNull, refuseLink: true);
+
+ //Expect the detach response to the test peer closing the consumer link after refusal.
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: false, replyClosed: false);
+
+ Assert.CatchAsync<NMSException>(async () => await session.CreateConsumerAsync(topic));
+
+ // Shut it down
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestFailoverEnforcesRequestTimeoutSession()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent connected = new ManualResetEvent(false);
+ ManualResetEvent disconnected = new ManualResetEvent(false);
+
+ // Connect to the test peer
+ testPeer.ExpectSaslAnonymous();
+ testPeer.ExpectOpen();
+ testPeer.ExpectBegin();
+ testPeer.DropAfterLastMatcher(delay: 10);
+
+ NmsConnection connection = await EstablishAnonymousConnection("nms.requestTimeout=1000&failover.reconnectDelay=2000&failover.maxReconnectAttempts=60", testPeer);
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionInterrupted(It.IsAny<Uri>()))
+ .Callback(() => { disconnected.Set(); });
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.IsAny<Uri>()))
+ .Callback(() => { connected.Set(); });
+
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(connected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to peer");
+ Assert.True(disconnected.WaitOne(TimeSpan.FromSeconds(5)), "Should lose connection to peer");
+
+ Assert.CatchAsync<NMSException>(async () => await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge));
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestFailoverEnforcesSendTimeout()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent connected = new ManualResetEvent(false);
+ ManualResetEvent disconnected = new ManualResetEvent(false);
+
+ // Connect to the test peer
+ testPeer.ExpectSaslAnonymous();
+ testPeer.ExpectOpen();
+ testPeer.ExpectBegin();
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+ testPeer.DropAfterLastMatcher();
+
+ NmsConnection connection = await EstablishAnonymousConnection("nms.sendTimeout=1000&failover.reconnectDelay=2000&failover.maxReconnectAttempts=60", testPeer);
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.IsAny<Uri>()))
+ .Callback(() => { connected.Set(); });
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionInterrupted(It.IsAny<Uri>()))
+ .Callback(() => { disconnected.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(connected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to peer");
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ Assert.True(disconnected.WaitOne(TimeSpan.FromSeconds(5)), "Should lose connection to peer");
+
+ Assert.CatchAsync<NMSException>(async () => await producer.SendAsync(producer.CreateTextMessage("test")));
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestFailoverPassthroughOfCompletedSyncSend()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ NmsConnection connection = await EstablishAnonymousConnection((testPeer));
+
+ testPeer.ExpectSaslAnonymous();
+ testPeer.ExpectOpen();
+ testPeer.ExpectBegin();
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Do a warm up
+ string messageContent1 = "myMessage1";
+ testPeer.ExpectTransfer(messageMatcher: m => { Assert.AreEqual(messageContent1, (m.BodySection as AmqpValue).Value); });
+
+ ITextMessage message1 = await session.CreateTextMessageAsync(messageContent1);
+ await producer.SendAsync(message1);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ // Create and send a new message, which is accepted
+ String messageContent2 = "myMessage2";
+ int delay = 15;
+ testPeer.ExpectTransfer(messageMatcher: m => Assert.AreEqual(messageContent2, (m.BodySection as AmqpValue).Value),
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: new Accepted(),
+ responseSettled: true,
+ stateMatcher: Assert.IsNull,
+ dispositionDelay: delay);
+ testPeer.ExpectClose();
+
+ ITextMessage message2 = await session.CreateTextMessageAsync(messageContent2);
+
+ DateTime start = DateTime.UtcNow;
+ await producer.SendAsync(message2);
+
+ TimeSpan elapsed = DateTime.UtcNow - start;
+ Assert.That(elapsed.TotalMilliseconds, Is.GreaterThanOrEqualTo(delay));
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+
+ [Test, Timeout(20_000)]
+ public async Task TestFailoverPassthroughOfRejectedSyncSend()
+ {
+ await DoFailoverPassthroughOfFailingSyncSendTestImpl(new Rejected());
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestFailoverPassthroughOfReleasedSyncSend()
+ {
+ await DoFailoverPassthroughOfFailingSyncSendTestImpl(new Released());
+ }
+
+ [Test, Timeout(20_000), Ignore("TODO: It should be fixed.")]
+ public async Task TestFailoverPassthroughOfModifiedFailedSyncSend()
+ {
+ var modified = new Modified()
+ {
+ DeliveryFailed = true
+ };
+ await DoFailoverPassthroughOfFailingSyncSendTestImpl(modified);
+ }
+
+ private async Task DoFailoverPassthroughOfFailingSyncSendTestImpl(Outcome failingState)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ NmsConnection connection = await EstablishAnonymousConnection((testPeer));
+
+ testPeer.ExpectSaslAnonymous();
+ testPeer.ExpectOpen();
+ testPeer.ExpectBegin();
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Do a warm up that succeeds
+ string messageContent1 = "myMessage1";
+ testPeer.ExpectTransfer(messageMatcher: m => { Assert.AreEqual(messageContent1, (m.BodySection as AmqpValue).Value); });
+
+ ITextMessage message1 = await session.CreateTextMessageAsync(messageContent1);
+ await producer.SendAsync(message1);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ // Create and send a new message, which fails as it is not accepted
+ Assert.False(failingState is Accepted);
+
+ String messageContent2 = "myMessage2";
+ int delay = 15;
+ testPeer.ExpectTransfer(messageMatcher: m => Assert.AreEqual(messageContent2, (m.BodySection as AmqpValue).Value),
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: failingState,
+ responseSettled: true,
+ stateMatcher: Assert.IsNull,
+ dispositionDelay: delay);
+
+ ITextMessage message2 = await session.CreateTextMessageAsync(messageContent2);
+
+ DateTime start = DateTime.UtcNow;
+ Assert.Catch(() => producer.Send(message2), "Expected an exception for this send.");
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ //Do a final send that succeeds
+ string messageContent3 = "myMessage3";
+ testPeer.ExpectTransfer(messageMatcher: m => { Assert.AreEqual(messageContent3, (m.BodySection as AmqpValue).Value); });
+
+ ITextMessage message3 = await session.CreateTextMessageAsync(messageContent3);
+ await producer.SendAsync(message3);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateSessionAfterConnectionDrops()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent originalConnected = new ManualResetEvent(false);
+ ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+ // Create a peer to connect to, then one to reconnect to
+ var originalUri = CreatePeerUri(originalPeer);
+ var finalUri = CreatePeerUri(finalPeer);
+
+ // Connect to the first peer
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectBegin(sendResponse: false);
+ originalPeer.DropAfterLastMatcher();
+
+ NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+ .Callback(() => { originalConnected.Set(); });
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+ .Callback(() => { finalConnected.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+ // Post Failover Expectations of FinalPeer
+
+ finalPeer.ExpectSaslAnonymous();
+ finalPeer.ExpectOpen();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectEnd();
+ finalPeer.ExpectClose();
+
+ ISession session = await connection.CreateSessionAsync();
+
+ Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+
+ await session.CloseAsync();
+ await connection.CloseAsync();
+
+ finalPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateConsumerAfterConnectionDrops()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent originalConnected = new ManualResetEvent(false);
+ ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+ // Create a peer to connect to, then one to reconnect to
+ var originalUri = CreatePeerUri(originalPeer);
+ var finalUri = CreatePeerUri(finalPeer);
+
+ // Connect to the first peer
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectBegin();
+ originalPeer.DropAfterLastMatcher();
+
+ NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+ .Callback(() => { originalConnected.Set(); });
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+ .Callback(() => { finalConnected.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+ // Post Failover Expectations of FinalPeer
+ finalPeer.ExpectSaslAnonymous();
+ finalPeer.ExpectOpen();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectReceiverAttach();
+ finalPeer.ExpectLinkFlow(drain: false, sendDrainFlowResponse: false, creditMatcher: credit => Assert.AreEqual(credit, 200));
+ finalPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ finalPeer.ExpectClose();
+
+ ISession session = await connection.CreateSessionAsync();
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ Assert.IsNull(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(500)));
+
+ Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+
+ await consumer.CloseAsync();
+
+ // Shut it down
+ await connection.CloseAsync();
+
+ finalPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateProducerAfterConnectionDrops()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent originalConnected = new ManualResetEvent(false);
+ ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+ // Create a peer to connect to, then one to reconnect to
+ var originalUri = CreatePeerUri(originalPeer);
+ var finalUri = CreatePeerUri(finalPeer);
+
+ // Connect to the first peer
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectBegin();
+ originalPeer.DropAfterLastMatcher();
+
+ NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+ .Callback(() => { originalConnected.Set(); });
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+ .Callback(() => { finalConnected.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+ // Post Failover Expectations of FinalPeer
+ finalPeer.ExpectSaslAnonymous();
+ finalPeer.ExpectOpen();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectSenderAttach();
+ finalPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ finalPeer.ExpectClose();
+
+ ISession session = await connection.CreateSessionAsync();
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+
+ await producer.CloseAsync();
+
+ await connection.CloseAsync();
+
+ finalPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000), Ignore("TODO: Fix")]
+ public async Task TestStartMaxReconnectAttemptsTriggeredWhenRemotesAreRejecting()
+ {
+ using (TestAmqpPeer firstPeer = new TestAmqpPeer())
+ using (TestAmqpPeer secondPeer = new TestAmqpPeer())
+ using (TestAmqpPeer thirdPeer = new TestAmqpPeer())
+ using (TestAmqpPeer fourthPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent failedConnection = new ManualResetEvent(false);
+
+ firstPeer.RejectConnect(AmqpError.NOT_FOUND, "Resource could not be located");
+ secondPeer.RejectConnect(AmqpError.NOT_FOUND, "Resource could not be located");
+ thirdPeer.RejectConnect(AmqpError.NOT_FOUND, "Resource could not be located");
+
+ // This shouldn't get hit, but if it does accept the connect so we don't pass the failed
+ // to connect assertion.
+ fourthPeer.ExpectSaslAnonymous();
+ fourthPeer.ExpectOpen();
+ fourthPeer.ExpectBegin();
+ fourthPeer.ExpectClose();
+
+ NmsConnection connection = await EstablishAnonymousConnection("failover.startupMaxReconnectAttempts=3&failover.reconnectDelay=15&failover.useReconnectBackOff=false",
+ firstPeer, secondPeer, thirdPeer, fourthPeer);
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionFailure(It.IsAny<NMSException>()))
+ .Callback(() => { failedConnection.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ Assert.CatchAsync<NMSException>(async () => await connection.StartAsync(), "Should not be able to connect");
+
+ Assert.True(failedConnection.WaitOne(TimeSpan.FromSeconds(5)));
+
+ try
+ {
+ await connection.CloseAsync();
+ }
+ catch (NMSException e)
+ {
+ }
+
+ firstPeer.WaitForAllMatchersToComplete(2000);
+ secondPeer.WaitForAllMatchersToComplete(2000);
+ thirdPeer.WaitForAllMatchersToComplete(2000);
+
+ // Shut down last peer and verify no connection made to it
+ fourthPeer.PurgeExpectations();
+ fourthPeer.Close();
+ Assert.NotNull(firstPeer.ClientSocket, "Peer 1 should have accepted a TCP connection");
+ Assert.NotNull(secondPeer.ClientSocket, "Peer 2 should have accepted a TCP connection");
+ Assert.NotNull(thirdPeer.ClientSocket, "Peer 3 should have accepted a TCP connection");
+ Assert.IsNull(fourthPeer.ClientSocket, "Peer 4 should not have accepted any TCP connection");
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListener()
+ {
+ Symbol errorCondition = AmqpError.RESOURCE_DELETED;
+ string errorDescription = nameof(TestRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListener);
+
+ await DoRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListenerTestImpl(errorCondition, errorDescription);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRemotelyCloseConsumerWithMessageListenerWithoutErrorFiresNMSExceptionListener()
+ {
+ await DoRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListenerTestImpl(null, null);
+ }
+
+ private async Task DoRemotelyCloseConsumerWithMessageListenerFiresNMSExceptionListenerTestImpl(Symbol errorType, string errorMessage)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent consumerClosed = new ManualResetEvent(false);
+ ManualResetEvent exceptionListenerFired = new ManualResetEvent(false);
+
+ testPeer.ExpectSaslAnonymous();
+ testPeer.ExpectOpen();
+ testPeer.ExpectBegin();
+
+ NmsConnection connection = await EstablishAnonymousConnection("failover.maxReconnectAttempts=1", testPeer);
+
+ connection.ExceptionListener += exception => { exceptionListenerFired.Set(); };
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+ .Callback(() => { consumerClosed.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectBegin();
+
+ ISession session1 = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ ISession session2 = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session2.GetQueueAsync("myQueue");
+
+ // Create a consumer, then remotely end it afterwards.
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectEnd();
+ testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: errorType, errorMessage: errorMessage, delayBeforeSend: 10);
+
+ IMessageConsumer consumer = await session2.CreateConsumerAsync(queue);
+ consumer.Listener += message => { };
+
+ // Close first session to allow the receiver remote close timing to be deterministic
+ await session1.CloseAsync();
+
+ // Verify the consumer gets marked closed
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ Assert.True(consumerClosed.WaitOne(TimeSpan.FromMilliseconds(2000)), "Consumer closed callback didn't trigger");
+ Assert.True(exceptionListenerFired.WaitOne(TimeSpan.FromMilliseconds(2000)), "NMS Exception listener should have fired with a MessageListener");
+
+ // Try closing it explicitly, should effectively no-op in client.
+ // The test peer will throw during close if it sends anything.
+ await consumer.CloseAsync();
+
+ // Shut the connection down
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestFailoverDoesNotFailPendingSend()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+ {
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectBegin();
+
+ // Ensure our send blocks in the provider waiting for credit so that on failover
+ // the message will actually get sent from the Failover bits once we grant some
+ // credit for the recovered sender.
+ originalPeer.ExpectSenderAttachWithoutGrantingCredit();
+ originalPeer.DropAfterLastMatcher(delay: 10); // Wait for sender to get into wait state
+
+ // Post Failover Expectations of sender
+ finalPeer.ExpectSaslAnonymous();
+ finalPeer.ExpectOpen();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectSenderAttach();
+ finalPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull);
+ finalPeer.ExpectClose();
+
+ NmsConnection connection = await EstablishAnonymousConnection("failover.initialReconnectDelay=25", originalPeer, finalPeer);
+ ISession session = await connection.CreateSessionAsync();
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Create and transfer a new message
+ string text = "myMessage";
+ ITextMessage message = await session.CreateTextMessageAsync(text);
+
+ Assert.DoesNotThrow(() =>
+ {
+ producer.Send(message);
+ });
+
+ await connection.CloseAsync();
+
+ finalPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestTempDestinationRecreatedAfterConnectionFailsOver()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent originalConnected = new ManualResetEvent(false);
+ ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+ // Create a peer to connect to, then one to reconnect to
+ var originalUri = CreatePeerUri(originalPeer);
+ var finalUri = CreatePeerUri(finalPeer);
+
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectBegin();
+ string dynamicAddress1 = "myTempTopicAddress";
+ originalPeer.ExpectTempTopicCreationAttach(dynamicAddress1);
+ originalPeer.DropAfterLastMatcher();
+
+ NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.Is<Uri>(uri => originalUri == uri.ToString())))
+ .Callback(() => { originalConnected.Set(); });
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionRestored(It.Is<Uri>(uri => finalUri == uri.ToString())))
+ .Callback(() => { finalConnected.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+ // Post Failover Expectations of FinalPeer
+ finalPeer.ExpectSaslAnonymous();
+ finalPeer.ExpectOpen();
+ finalPeer.ExpectBegin();
+ String dynamicAddress2 = "myTempTopicAddress2";
+ finalPeer.ExpectTempTopicCreationAttach(dynamicAddress2);
+
+ // Session is recreated after previous temporary destinations are recreated on failover.
+ finalPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ ITemporaryTopic temporaryTopic = await session.CreateTemporaryTopicAsync();
+
+ Assert.True(finalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to final peer");
+
+ // Delete the temporary Topic and close the session.
+ finalPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ finalPeer.ExpectEnd();
+
+ await temporaryTopic.DeleteAsync();
+
+ await session.CloseAsync();
+
+ // Shut it down
+ finalPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ originalPeer.WaitForAllMatchersToComplete(2000);
+ finalPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerCanReceivesMessagesWhenConnectionLostDuringAutoAck()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent consumerReady = new ManualResetEvent(false);
+ ManualResetEvent originalConnected = new ManualResetEvent(false);
+ ManualResetEvent finalConnected = new ManualResetEvent(false);
+
+ // Connect to the first peer
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectBegin();
+
+ NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+ Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionEstablished(It.IsAny<Uri>()))
+ .Callback(() => { originalConnected.Set(); });
+
+ connectionListener
+ .Setup(listener => listener.OnConnectionRestored(It.IsAny<Uri>()))
+ .Callback(() => { finalConnected.Set(); });
+
+ connection.AddConnectionListener(connectionListener.Object);
+
+ await connection.StartAsync();
+
+ Assert.True(originalConnected.WaitOne(TimeSpan.FromSeconds(5)), "Should connect to original peer");
+
+ originalPeer.ExpectReceiverAttach();
+ originalPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+ originalPeer.RunAfterLastHandler(() => consumerReady.WaitOne(TimeSpan.FromSeconds(2)));
+ originalPeer.DropAfterLastMatcher();
+
+ // Post Failover Expectations of FinalPeer
+ finalPeer.ExpectSaslAnonymous();
+ finalPeer.ExpectOpen();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectReceiverAttach();
+ finalPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+ finalPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageConsumer messageConsumer = await session.CreateConsumerAsync(queue);
+ CountdownEvent msgReceivedLatch = new CountdownEvent(2);
+ messageConsumer.Listener += message =>
+ {
+ if (msgReceivedLatch.CurrentCount == 2)
+ {
+ consumerReady.Set();
+ finalConnected.WaitOne(2000);
+ }
+
+ msgReceivedLatch.Signal();
+ };
+
+ finalPeer.WaitForAllMatchersToComplete(5000);
+
+ Assert.IsTrue(msgReceivedLatch.Wait(TimeSpan.FromSeconds(10)), $"Expected 2 messages, but got {2 - msgReceivedLatch.CurrentCount}");
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateProducerFailsWhenLinkRefused()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ testPeer.ExpectSaslAnonymous();
+ testPeer.ExpectOpen();
+ testPeer.ExpectBegin();
+
+ NmsConnection connection = await EstablishAnonymousConnection(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string topicName = "myTopic";
+ ITopic topic = await session.GetTopicAsync(topicName);
+
+ // Expect a link to a topic node, which we will then refuse
+ testPeer.ExpectSenderAttach(targetMatcher: x =>
+ {
+ Target target = (Target) x;
+
+ Assert.AreEqual(topicName, target.Address);
+ Assert.IsFalse(target.Dynamic);
+ Assert.AreEqual((uint) TerminusDurability.NONE, target.Durable);
+ }, sourceMatcher: Assert.NotNull, refuseLink: true);
+
+ //Expect the detach response to the test peer closing the producer link after refusal.
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: false, replyClosed: false);
+
+ Assert.CatchAsync<NMSException>(async () => await session.CreateProducerAsync(topic));
+
+ // Shut it down
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000), Category("Windows")]
+ public async Task TestConnectionInterruptedInvokedWhenConnectionToBrokerLost()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent connectionInterruptedInvoked = new ManualResetEvent(false);
+
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectBegin();
+
+ NmsConnection connection = await EstablishAnonymousConnection(originalPeer);
+
+ connection.ConnectionInterruptedListener += () => connectionInterruptedInvoked.Set();
+
+ await connection.StartAsync();
+
+ originalPeer.Close();
+
+ Assert.IsTrue(connectionInterruptedInvoked.WaitOne(TimeSpan.FromSeconds(10)));
+ }
+ }
+
+ [Test, Timeout(20_000), Category("Windows")]
+ public async Task TestConnectionResumedInvokedWhenConnectionToBrokerLost()
+ {
+ using (TestAmqpPeer originalPeer = new TestAmqpPeer())
+ using (TestAmqpPeer finalPeer = new TestAmqpPeer())
+ {
+ ManualResetEvent connectionResumedInvoked = new ManualResetEvent(false);
+
+ originalPeer.ExpectSaslAnonymous();
+ originalPeer.ExpectOpen();
+ originalPeer.ExpectBegin();
+ originalPeer.ExpectBegin();
+
+ finalPeer.ExpectSaslAnonymous();
+ finalPeer.ExpectOpen();
+ finalPeer.ExpectBegin();
+ finalPeer.ExpectBegin();
+
+ NmsConnection connection = await EstablishAnonymousConnection(originalPeer, finalPeer);
+
+ connection.ConnectionResumedListener += () => connectionResumedInvoked.Set();
+
+ await connection.StartAsync();
+
+ originalPeer.Close();
+ Assert.IsTrue(connectionResumedInvoked.WaitOne(TimeSpan.FromSeconds(10)));
+ }
+ }
+
+ private Task<NmsConnection> EstablishAnonymousConnection(params TestAmqpPeer[] peers)
+ {
+ return EstablishAnonymousConnection(null, null, peers);
+ }
+
+ private Task<NmsConnection> EstablishAnonymousConnection(string failoverParams, params TestAmqpPeer[] peers)
+ {
+ return EstablishAnonymousConnection(null, failoverParams, peers);
+ }
+
+ private async Task<NmsConnection> EstablishAnonymousConnection(string connectionParams, string failoverParams, params TestAmqpPeer[] peers)
+ {
+ if (peers.Length == 0)
+ {
+ throw new ArgumentException("No test peers were given, at least 1 required");
+ }
+
+ string remoteUri = "failover:(";
+ bool first = true;
+ foreach (TestAmqpPeer peer in peers)
+ {
+ if (!first)
+ {
+ remoteUri += ",";
+ }
+
+ remoteUri += CreatePeerUri(peer, connectionParams);
+ first = false;
+ }
+
+ if (failoverParams == null)
+ {
+ remoteUri += ")?failover.maxReconnectAttempts=10";
+ }
+ else
+ {
+ remoteUri += ")?" + failoverParams;
+ }
+
+ NmsConnectionFactory factory = new NmsConnectionFactory(remoteUri);
+ return (NmsConnection) await factory.CreateConnectionAsync();
+ }
+
+ private string CreatePeerUri(TestAmqpPeer peer, string parameters = null)
+ {
+ return $"amqp://127.0.0.1:{peer.ServerPort}/{(parameters != null ? "?" + parameters : "")}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/MessageDeliveryTimeTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/MessageDeliveryTimeTest.cs
new file mode 100644
index 0000000..157b159
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/MessageDeliveryTimeTest.cs
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class MessageDeliveryTimeTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20000)]
+ public async Task TestReceiveMessageWithoutDeliveryTimeSet()
+ {
+ await DoReceiveMessageDeliveryTime(null, null);
+ }
+
+ [Test, Timeout(20000)]
+ public async Task TestDeliveryTimeIsDateTime()
+ {
+ DateTime deliveryTime = DateTimeOffset.FromUnixTimeMilliseconds(CurrentTimeInMillis() + 12345).DateTime.ToUniversalTime();
+ await DoReceiveMessageDeliveryTime(deliveryTime, deliveryTime);
+ }
+
+ [Test, Timeout(20000)]
+ public async Task TestDeliveryTimeIsULong()
+ {
+ ulong deliveryTime = (ulong) (CurrentTimeInMillis() + 12345);
+ await DoReceiveMessageDeliveryTime(deliveryTime, DateTimeOffset.FromUnixTimeMilliseconds((long) deliveryTime).DateTime);
+ }
+
+ [Test, Timeout(20000)]
+ public async Task TestDeliveryTimeIsLong()
+ {
+ long deliveryTime = (CurrentTimeInMillis() + 12345);
+ await DoReceiveMessageDeliveryTime(deliveryTime, DateTimeOffset.FromUnixTimeMilliseconds(deliveryTime).DateTime);
+ }
+
+ [Test, Timeout(20000)]
+ public async Task TestDeliveryTimeIsInt()
+ {
+ int deliveryTime = (int) (CurrentTimeInMillis() + 12345);
+ await DoReceiveMessageDeliveryTime(deliveryTime, DateTimeOffset.FromUnixTimeMilliseconds(deliveryTime).DateTime);
+ }
+
+ [Test, Timeout(20000)]
+ public async Task TestDeliveryTimeIsUInt()
+ {
+ uint deliveryTime = (uint) (CurrentTimeInMillis() + 12345);
+ await DoReceiveMessageDeliveryTime(deliveryTime, DateTimeOffset.FromUnixTimeMilliseconds(deliveryTime).DateTime);
+ }
+
+ private long CurrentTimeInMillis()
+ {
+ return new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds();
+ }
+
+ private async Task DoReceiveMessageDeliveryTime(object setDeliveryTimeAnnotation, DateTime? expectedDeliveryTime)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+ testPeer.ExpectBegin();
+ var session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ var queue = await session.GetQueueAsync("myQueue");
+
+ var message = CreateMessageWithNullContent();
+ if (setDeliveryTimeAnnotation != null)
+ {
+ message.MessageAnnotations = message.MessageAnnotations ?? new MessageAnnotations();
+ message.MessageAnnotations[SymbolUtil.NMS_DELIVERY_TIME] = setDeliveryTimeAnnotation;
+ }
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message);
+ testPeer.ExpectDisposition(true, (deliveryState) => { });
+
+ DateTime startingTimeFrom = DateTime.UtcNow;
+ var messageConsumer = await session.CreateConsumerAsync(queue);
+ var receivedMessage = await messageConsumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+ DateTime receivingTime = DateTime.UtcNow;
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+
+ Assert.IsNotNull(receivedMessage);
+ if (expectedDeliveryTime != null)
+ {
+ Assert.AreEqual(receivedMessage.NMSDeliveryTime, expectedDeliveryTime.Value);
+ }
+ else
+ {
+ Assert.LessOrEqual(receivedMessage.NMSDeliveryTime, receivingTime);
+ Assert.GreaterOrEqual(receivedMessage.NMSDeliveryTime, startingTimeFrom);
+ }
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestDeliveryDelayNotSupportedThrowsException()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await base.EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+ Assert.Throws<NotSupportedException>(() => producer.DeliveryDelay = TimeSpan.FromMinutes(17));
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestDeliveryDelayHasItsReflectionInAmqpAnnotations()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ // Determine current time
+ TimeSpan deliveryDelay = TimeSpan.FromMinutes(17);
+ long currentUnixEpochTime = new DateTimeOffset(DateTime.UtcNow + deliveryDelay).ToUnixTimeMilliseconds();
+ long currentUnixEpochTime2 = new DateTimeOffset(DateTime.UtcNow + deliveryDelay + deliveryDelay).ToUnixTimeMilliseconds();
+
+ IConnection connection = await base.EstablishConnectionAsync(testPeer,
+ serverCapabilities: new Symbol[] {SymbolUtil.OPEN_CAPABILITY_DELAYED_DELIVERY, SymbolUtil.OPEN_CAPABILITY_SOLE_CONNECTION_FOR_CONTAINER});
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+ producer.DeliveryDelay = deliveryDelay;
+
+ // Create and transfer a new message
+ testPeer.ExpectTransfer(message =>
+ {
+ Assert.GreaterOrEqual((long) message.MessageAnnotations[SymbolUtil.NMS_DELIVERY_TIME], currentUnixEpochTime);
+ Assert.Less((long) message.MessageAnnotations[SymbolUtil.NMS_DELIVERY_TIME], currentUnixEpochTime2);
+
+ Assert.IsTrue(message.Header.Durable);
+ });
+ testPeer.ExpectClose();
+
+ ITextMessage textMessage = await session.CreateTextMessageAsync();
+
+ await producer.SendAsync(textMessage);
+ Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/MessageExpirationIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/MessageExpirationIntegrationTest.cs
new file mode 100644
index 0000000..427acdd
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/MessageExpirationIntegrationTest.cs
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class MessageExpirationIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestIncomingExpiredMessageGetsFiltered()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Expected the consumer to attach and send credit, then send it an
+ // already-expired message followed by a live message.
+ testPeer.ExpectReceiverAttach();
+ string expiredMsgContent = "already-expired";
+ Amqp.Message message = CreateExpiredMessage(expiredMsgContent);
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: message);
+
+ string liveMsgContent = "valid";
+ testPeer.SendTransferToLastOpenedLinkOnLastOpenedSession(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = liveMsgContent } }, nextIncomingId: 2);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ // Call receive, expect the first message to be filtered due to expiry,
+ // and the second message to be given to the test app and accepted.
+ Action<DeliveryState> modifiedMatcher = state =>
+ {
+ var modified = state as Modified;
+ Assert.IsNotNull(modified);
+ Assert.IsTrue(modified.DeliveryFailed);
+ Assert.IsTrue(modified.UndeliverableHere);
+ };
+ testPeer.ExpectDisposition(settled: true, stateMatcher: modifiedMatcher, firstDeliveryId: 1, lastDeliveryId: 1);
+ testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId: 2, lastDeliveryId: 2);
+
+ IMessage m = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+ Assert.NotNull(m, "Message should have been received");
+ Assert.IsInstanceOf<ITextMessage>(m);
+ Assert.AreEqual(liveMsgContent, (m as ITextMessage).Text, "Unexpected message content");
+
+ // Verify the other message is not there. Will drain to be sure there are no messages.
+ Assert.IsNull(await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10)), "Message should not have been received");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestIncomingExpiredMessageGetsConsumedWhenFilterDisabled()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, "?nms.localMessageExpiry=false");
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Expected the consumer to attach and send credit, then send it an
+ // already-expired message followed by a live message.
+ testPeer.ExpectReceiverAttach();
+
+ string expiredMsgContent = "already-expired";
+ Amqp.Message message = CreateExpiredMessage(expiredMsgContent);
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: message);
+
+ string liveMsgContent = "valid";
+ testPeer.SendTransferToLastOpenedLinkOnLastOpenedSession(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = liveMsgContent } }, nextIncomingId: 2);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ // Call receive, expect the expired message as we disabled local expiry.
+ testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId: 1, lastDeliveryId: 1);
+
+ IMessage m = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+ Assert.NotNull(m, "Message should have been received");
+ Assert.IsInstanceOf<ITextMessage>(m);
+ Assert.AreEqual(expiredMsgContent, ((ITextMessage) m).Text, "Unexpected message content");
+
+ // Verify the other message is there
+ testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId: 2, lastDeliveryId: 2);
+
+ m = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(3000));
+ Assert.NotNull(m, "Message should have been received");
+ Assert.IsInstanceOf<ITextMessage>(m);
+ Assert.AreEqual(liveMsgContent, ((ITextMessage) m).Text, "Unexpected message content");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestIncomingExpiredMessageGetsFilteredAsync()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Expected the consumer to attach and send credit, then send it an
+ // already-expired message followed by a live message.
+ testPeer.ExpectReceiverAttach();
+
+ string expiredMsgContent = "already-expired";
+ Amqp.Message message = CreateExpiredMessage(expiredMsgContent);
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: message);
+
+ string liveMsgContent = "valid";
+ testPeer.SendTransferToLastOpenedLinkOnLastOpenedSession(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = liveMsgContent } }, nextIncomingId: 2);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ // Add message listener, expect the first message to be filtered due to expiry,
+ // and the second message to be given to the test app and accepted.
+ Action<DeliveryState> modifiedMatcher = state =>
+ {
+ var modified = state as Modified;
+ Assert.IsNotNull(modified);
+ Assert.IsTrue(modified.DeliveryFailed);
+ Assert.IsTrue(modified.UndeliverableHere);
+ };
+ testPeer.ExpectDisposition(settled: true, stateMatcher: modifiedMatcher, firstDeliveryId: 1, lastDeliveryId: 1);
+ testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId: 2, lastDeliveryId: 2);
+
+
+ ManualResetEvent success = new ManualResetEvent(false);
+ ManualResetEvent listenerFailure = new ManualResetEvent(false);
+
+ consumer.Listener += m =>
+ {
+ if (liveMsgContent.Equals(((ITextMessage) m).Text))
+ success.Set();
+ else
+ listenerFailure.Set();
+ };
+
+ Assert.True(success.WaitOne(TimeSpan.FromSeconds(5)), "didn't get expected message");
+ Assert.False(listenerFailure.WaitOne(TimeSpan.FromMilliseconds(100)), "Received message when message should not have been received");
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestIncomingExpiredMessageGetsConsumedWhenFilterDisabledAsync()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, "?nms.localMessageExpiry=false");
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Expected the consumer to attach and send credit, then send it an
+ // already-expired message followed by a live message.
+ testPeer.ExpectReceiverAttach();
+
+ string expiredMsgContent = "already-expired";
+ Amqp.Message message = CreateExpiredMessage(expiredMsgContent);
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: message);
+
+ string liveMsgContent = "valid";
+ testPeer.SendTransferToLastOpenedLinkOnLastOpenedSession(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = liveMsgContent } }, nextIncomingId: 2);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ // Add message listener, expect both messages as the filter is disabled
+ testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId:1, lastDeliveryId:1);
+ testPeer.ExpectDisposition(settled: true, stateMatcher: Assert.IsInstanceOf<Accepted>, firstDeliveryId:2, lastDeliveryId:2);
+
+ CountdownEvent success = new CountdownEvent(2);
+
+ consumer.Listener += m =>
+ {
+ if (expiredMsgContent.Equals(((ITextMessage) m).Text) || liveMsgContent.Equals(((ITextMessage) m).Text))
+ success.Signal();
+ };
+
+ Assert.IsTrue(success.Wait(TimeSpan.FromSeconds(5)), "Didn't get expected messages");
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+
+ private static Amqp.Message CreateExpiredMessage(string value)
+ {
+ return new Amqp.Message
+ {
+ BodySection = new AmqpValue() { Value = value },
+ Properties = new Properties { AbsoluteExpiryTime = DateTime.UtcNow - TimeSpan.FromMilliseconds(100) }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/NMSConsumerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSConsumerIntegrationTest.cs
new file mode 100644
index 0000000..a27b268
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSConsumerIntegrationTest.cs
@@ -0,0 +1,965 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP.Message;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ // Adapted from ConsumerIntegrationTest to use NMSContext
+ [TestFixture]
+ public class NMSConsumerIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestCloseConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+ var consumer = await context.CreateConsumerAsync(queue);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ // TODO No connection Listener in NMSContext
+ // [Test, Timeout(20_000)]
+ // public async Task TestRemotelyCloseConsumer()
+ // {
+ // Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+ // string errorMessage = "buba";
+ //
+ // using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ // {
+ // ManualResetEvent consumerClosed = new ManualResetEvent(false);
+ // ManualResetEvent exceptionFired = new ManualResetEvent(false);
+ //
+ // mockConnectionListener
+ // .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+ // .Callback(() => consumerClosed.Set());
+ //
+ // var context = (NmsContext) EstablishNMSContext(testPeer);
+ // context.ConnectionInterruptedListener += () => { consumerClosed.Set(); };// AddConnectionListener(mockConnectionListener.Object);}
+ // // context.list ConnectionInterruptedListener += () => { consumerClosed.Set(); };// AddConnectionListener(mockConnectionListener.Object);}
+ // context.ExceptionListener += exception => { exceptionFired.Set(); };
+ //
+ // testPeer.ExpectBegin();
+ // // ISession session = context.CreateSession(AcknowledgementMode.AutoAcknowledge);
+ //
+ // // Create a consumer, then remotely end it afterwards.
+ // testPeer.ExpectReceiverAttach();
+ // testPeer.ExpectLinkFlow();
+ // testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, errorMessage: errorMessage, delayBeforeSend: 400);
+ //
+ // IQueue queue = context.GetQueue("myQueue");
+ // var consumer = context.CreateConsumer(queue);
+ //
+ //
+ // // Verify the consumer gets marked closed
+ // testPeer.WaitForAllMatchersToComplete(1000);
+ //
+ // Assert.True(consumerClosed.WaitOne(2000), "Consumer closed callback didn't trigger");
+ // Assert.False(exceptionFired.WaitOne(20), "Exception listener shouldn't fire with no MessageListener");
+ //
+ // // Try closing it explicitly, should effectively no-op in client.
+ // // The test peer will throw during close if it sends anything.
+ // consumer.Close();
+ // }
+ // }
+
+ // [Test, Timeout(20_000)]
+ // public async Task TestRemotelyCloseConsumerWithMessageListenerFiresExceptionListener()
+ // {
+ // Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+ // string errorMessage = "buba";
+ //
+ // using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ // {
+ // ManualResetEvent consumerClosed = new ManualResetEvent(false);
+ // ManualResetEvent exceptionFired = new ManualResetEvent(false);
+ //
+ // mockConnectionListener
+ // .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
+ // .Callback(() => consumerClosed.Set());
+ //
+ // NmsConnection connection = (NmsConnection) EstablishConnection(testPeer);
+ // connection.AddConnectionListener(mockConnectionListener.Object);
+ // connection.ExceptionListener += exception => { exceptionFired.Set(); };
+ //
+ // testPeer.ExpectBegin();
+ // ISession session = connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
+ //
+ // // Create a consumer, then remotely end it afterwards.
+ // testPeer.ExpectReceiverAttach();
+ // testPeer.ExpectLinkFlow();
+ // testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, errorMessage: errorMessage, 10);
+ //
+ // IQueue queue = session.GetQueue("myQueue");
+ // IMessageConsumer consumer = session.CreateConsumer(queue);
+ //
+ // consumer.Listener += message => { };
+ //
+ // // Verify the consumer gets marked closed
+ // testPeer.WaitForAllMatchersToComplete(1000);
+ //
+ // Assert.True(consumerClosed.WaitOne(2000), "Consumer closed callback didn't trigger");
+ // Assert.True(exceptionFired.WaitOne(2000), "Exception listener should have fired with a MessageListener");
+ //
+ // // Try closing it explicitly, should effectively no-op in client.
+ // // The test peer will throw during close if it sends anything.
+ // consumer.Close();
+ // }
+ // }
+
+ [Test, Timeout(20_000)]
+ public async Task TestReceiveMessageWithReceiveZeroTimeout()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = null } }, count: 1);
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ var consumer = await context.CreateConsumerAsync(queue);
+ IMessage message = await consumer.ReceiveAsync();
+ Assert.NotNull(message, "A message should have been received");
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(10000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestExceptionInOnMessageReleasesInAutoAckMode()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message() { BodySection = new AmqpValue() { Value = null } }, count: 1);
+ testPeer.ExpectDispositionThatIsReleasedAndSettled();
+
+ var consumer = await context.CreateConsumerAsync(queue);
+ consumer.Listener += message => throw new Exception();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(10000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCloseDurableTopicSubscriberDetachesWithCloseFalse()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ string topicName = "myTopic";
+ string subscriptionName = "mySubscription";
+ ITopic topic = await context.GetTopicAsync(topicName);
+
+ testPeer.ExpectDurableSubscriberAttach(topicName, subscriptionName);
+ testPeer.ExpectLinkFlow();
+
+ var durableConsumer = await context.CreateDurableConsumerAsync(topic, subscriptionName, null, false);
+
+ testPeer.ExpectDetach(expectClosed: false, sendResponse: true, replyClosed: false);
+ await durableConsumer.CloseAsync();
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerReceiveThrowsIfConnectionLost()
+ {
+ await DoTestConsumerReceiveThrowsIfConnectionLost(false);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerTimedReceiveThrowsIfConnectionLost()
+ {
+ await DoTestConsumerReceiveThrowsIfConnectionLost(true);
+ }
+
+ private async Task DoTestConsumerReceiveThrowsIfConnectionLost(bool useTimeout)
+ {
+ ManualResetEvent consumerReady = new ManualResetEvent(false);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+
+ testPeer.ExpectBegin();
+
+ IQueue queue = await context.GetQueueAsync("queue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.RunAfterLastHandler(() => { consumerReady.WaitOne(2000); });
+ testPeer.DropAfterLastMatcher(delay: 10);
+
+ var consumer = await context.CreateConsumerAsync(queue);
+ consumerReady.Set();
+
+ try
+ {
+ if (useTimeout)
+ {
+ await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(10_0000));
+ }
+ else
+ {
+ await consumer.ReceiveAsync();
+ }
+
+
+ Assert.Fail("An exception should have been thrown");
+ }
+ catch (NMSException)
+ {
+ // Expected
+ }
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ // TODO No connection Listener in context
+ // [Test, Timeout(20_000)]
+ // public async Task TestConsumerReceiveNoWaitThrowsIfConnectionLost()
+ // {
+ // ManualResetEvent disconnected = new ManualResetEvent(false);
+ //
+ // using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ // {
+ // NmsContext context = (NmsContext) EstablishNMSContext(testPeer);
+ // Mock<INmsConnectionListener> connectionListener = new Mock<INmsConnectionListener>();
+ //
+ // connectionListener
+ // .Setup(listener => listener.OnConnectionFailure(It.IsAny<NMSException>()))
+ // .Callback(() => { disconnected.Set(); });
+ //
+ // context.AddConnectionListener(connectionListener.Object);
+ //
+ // context.Start();
+ //
+ // testPeer.ExpectBegin();
+ //
+ // IQueue queue = context.GetQueue("queue");
+ //
+ // testPeer.ExpectReceiverAttach();
+ // testPeer.ExpectLinkFlow();
+ // testPeer.RemotelyCloseConnection(expectCloseResponse: true, errorCondition: ConnectionError.CONNECTION_FORCED, errorMessage: "buba");
+ //
+ // var consumer = context.CreateConsumer(queue);
+ //
+ // Assert.True(disconnected.WaitOne(), "Connection should be disconnected");
+ //
+ // try
+ // {
+ // consumer.ReceiveNoWait();
+ // Assert.Fail("An exception should have been thrown");
+ // }
+ // catch (NMSException)
+ // {
+ // // Expected
+ // }
+ // }
+ // }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSetMessageListenerAfterStartAndSend()
+ {
+ int messageCount = 4;
+ CountdownEvent latch = new CountdownEvent(messageCount);
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), messageCount);
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ for (int i = 0; i < messageCount; i++)
+ {
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+ }
+
+ consumer.Listener += message => latch.Signal();
+
+ Assert.True(latch.Wait(4000), "Messages not received within given timeout. Count remaining: " + latch.CurrentCount);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ await consumer.CloseAsync();
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSyncReceiveFailsWhenListenerSet()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ consumer.Listener += message => { };
+
+ Assert.CatchAsync<NMSException>(async () => await consumer.ReceiveAsync(), "Should have thrown an exception.");
+ Assert.CatchAsync<NMSException>(async () => await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(1000)), "Should have thrown an exception.");
+ Assert.CatchAsync<NMSException>(async () => consumer.ReceiveNoWait(), "Should have thrown an exception.");
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateProducerInOnMessage()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ IQueue outbound = await context.GetQueueAsync("ForwardDest");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ testPeer.ExpectSenderAttach();
+ testPeer.ExpectTransfer(messageMatcher: Assert.NotNull);
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ consumer.Listener += message =>
+ {
+ var producer = context.CreateProducer();
+ producer.Send(outbound, message);
+ producer.Close();
+ };
+
+ testPeer.WaitForAllMatchersToComplete(10_000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestMessageListenerCallsConnectionCloseThrowsIllegalStateException()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ ManualResetEvent latch = new ManualResetEvent(false);
+ Exception exception = null;
+ consumer.Listener += message =>
+ {
+ try
+ {
+ context.Close();
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+
+ latch.Set();
+ };
+
+ Assert.True(latch.WaitOne(4000), "Messages not received within given timeout.");
+ Assert.IsNotNull(exception);
+ Assert.IsInstanceOf<IllegalStateException>(exception);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.ExpectEnd();
+ // testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestMessageListenerCallsConnectionStopThrowsIllegalStateException()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ ManualResetEvent latch = new ManualResetEvent(false);
+ Exception exception = null;
+ consumer.Listener += message =>
+ {
+ try
+ {
+ context.Stop();
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+
+ latch.Set();
+ };
+
+ Assert.True(latch.WaitOne(3000), "Messages not received within given timeout.");
+ Assert.IsNotNull(exception);
+ Assert.IsInstanceOf<IllegalStateException>(exception);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestMessageListenerCallsSessionCloseThrowsIllegalStateException()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ ManualResetEvent latch = new ManualResetEvent(false);
+ Exception exception = null;
+ consumer.Listener += message =>
+ {
+ try
+ {
+ context.Close();
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+
+ latch.Set();
+ };
+
+ Assert.True(latch.WaitOne(3000), "Messages not received within given timeout.");
+ Assert.IsNotNull(exception);
+ Assert.IsInstanceOf<IllegalStateException>(exception);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.ExpectEnd();
+ // testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ // TODO: To be fixed
+ [Test, Timeout(20_000), Ignore("Ignore")]
+ public async Task TestMessageListenerClosesItsConsumer()
+ {
+ var latch = new ManualResetEvent(false);
+ var exceptionListenerFired = new ManualResetEvent(false);
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ context.ExceptionListener += _ => exceptionListenerFired.Set();
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), 1);
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true, creditMatcher: credit => Assert.AreEqual(99, credit)); // Not sure if expected credit is right
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ Exception exception = null;
+ consumer.Listener += message =>
+ {
+ try
+ {
+ consumer.Close();
+ latch.Set();
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(1000)), "Process not completed within given timeout");
+ Assert.IsNull(exception, "No error expected during close");
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ Assert.False(exceptionListenerFired.WaitOne(20), "Exception listener shouldn't have fired");
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRecoverOrderingWithAsyncConsumer()
+ {
+ ManualResetEvent latch = new ManualResetEvent(false);
+ Exception asyncError = null;
+
+ int recoverCount = 5;
+ int messageCount = 8;
+ int testPayloadLength = 255;
+ string payload = Encoding.UTF8.GetString(new byte[testPayloadLength]);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer, acknowledgementMode:AcknowledgementMode.ClientAcknowledge);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(
+ message: new Amqp.Message() { BodySection = new AmqpValue() { Value = payload } },
+ count: messageCount,
+ drain: false,
+ nextIncomingId: 1,
+ addMessageNumberProperty: true,
+ sendDrainFlowResponse: false,
+ sendSettled: false,
+ creditMatcher: credit => Assert.Greater(credit, messageCount)
+ );
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ bool complete = false;
+ int messageSeen = 0;
+ int expectedIndex = 0;
+ consumer.Listener += message =>
+ {
+ if (complete)
+ {
+ return;
+ }
+
+ try
+ {
+ int actualIndex = message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER);
+ Assert.AreEqual(expectedIndex, actualIndex, "Received Message Out Of Order");
+
+ // don't ack the message until we receive it X times
+ if (messageSeen < recoverCount)
+ {
+ context.Recover();
+ messageSeen++;
+ }
+ else
+ {
+ messageSeen = 0;
+ expectedIndex++;
+
+ // Have the peer expect the accept the disposition (1-based, hence pre-incremented).
+ testPeer.ExpectDisposition(settled: true,
+ stateMatcher: state => Assert.AreEqual(state.Descriptor.Code, MessageSupport.ACCEPTED_INSTANCE.Descriptor.Code
+ ));
+
+ message.Acknowledge();
+
+ if (expectedIndex == messageCount)
+ {
+ complete = true;
+ latch.Set();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ complete = true;
+ asyncError = e;
+ latch.Set();
+ }
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromSeconds(15)), "Messages not received within given timeout.");
+ Assert.IsNull(asyncError, "Unexpected exception");
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerCloseWaitsForAsyncDeliveryToComplete()
+ {
+ ManualResetEvent latch = new ManualResetEvent(false);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ consumer.Listener += _ =>
+ {
+ latch.Set();
+ Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSessionCloseWaitsForAsyncDeliveryToComplete()
+ {
+ ManualResetEvent latch = new ManualResetEvent(false);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ consumer.Listener += _ =>
+ {
+ latch.Set();
+ Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConnectionCloseWaitsForAsyncDeliveryToComplete()
+ {
+ ManualResetEvent latch = new ManualResetEvent(false);
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ await context.StartAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ testPeer.ExpectDispositionThatIsAcceptedAndSettled();
+
+ consumer.Listener += _ =>
+ {
+ latch.Set();
+ Task.Delay(TimeSpan.FromMilliseconds(100)).GetAwaiter().GetResult();
+ };
+
+ Assert.True(latch.WaitOne(TimeSpan.FromMilliseconds(3000)), "Messages not received within given timeout.");
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRecoveredMessageShouldNotBeMutated()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer, acknowledgementMode:AcknowledgementMode.ClientAcknowledge);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ string originalPayload = "testMessage";
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: new Amqp.Message { BodySection = new AmqpValue() { Value = originalPayload } }, count: 1);
+
+ var consumer = await context.CreateConsumerAsync(destination);
+ NmsTextMessage message = await consumer.ReceiveAsync() as NmsTextMessage;
+ Assert.NotNull(message);
+ message.IsReadOnlyBody = false;
+ message.Text = message.Text + "Received";
+ await context.RecoverAsync();
+
+ ITextMessage recoveredMessage = await consumer.ReceiveAsync() as ITextMessage;
+ Assert.IsNotNull(recoveredMessage);
+ Assert.AreNotEqual(message.Text, recoveredMessage.Text);
+ Assert.AreEqual(originalPayload, recoveredMessage.Text);
+ Assert.AreNotSame(message, recoveredMessage);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [TestCaseSource("TestReceiveBodyCaseSource")]
+ [Timeout(20_000)]
+ public async Task TestReceiveBody<T>(T inputValue)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(CreateMessageWithValueContent(inputValue));
+ testPeer.ExpectDisposition(true, _ => { } );
+
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var consumer = await context.CreateConsumerAsync(destination);
+
+ T body = await consumer.ReceiveBodyAsync<T>();
+ Assert.AreEqual(inputValue, body);
+ Assert.AreNotSame(inputValue, body);
+
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+
+
+ public static IEnumerable<object> TestReceiveBodyCaseSource()
+ {
+ yield return new Map()
+ {
+ ["Parameter1"] = "test",
+ ["Parameter2"] = 23423
+ };
+ yield return 1233;
+ yield return "test";
+ yield return (uint) 1233;
+ yield return (ulong) 1233;
+ yield return (long) -1233;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/NMSContextIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSContextIntegrationTest.cs
new file mode 100644
index 0000000..16940d4
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSContextIntegrationTest.cs
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ // Adapted from SessionIntegrationTest to use NMSContext
+ [TestFixture]
+ public class NMSContextIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestClose()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ INMSContext context = await EstablishNMSContextAsync(testPeer);
+
+ testPeer.ExpectClose();
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateProducer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+
+ testPeer.ExpectSenderAttach();
+
+ var producer = await context.CreateProducerAsync();
+
+ testPeer.ExpectDetach(true, true, true);
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ await producer.CloseAsync();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ var consumer = await context.CreateConsumerAsync(await context.GetQueueAsync("myQueue"));
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateConsumerWithEmptySelector()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+ await context.CreateConsumerAsync(queue, "");
+ await context.CreateConsumerAsync(queue, "", noLocal: false);
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateConsumerWithNullSelector()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+ await context.CreateConsumerAsync(queue, null);
+ await context.CreateConsumerAsync(queue, null, noLocal: false);
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateDurableConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ string topicName = "myTopic";
+ ITopic topic = await context.GetTopicAsync(topicName);
+ string subscriptionName = "mySubscription";
+
+ testPeer.ExpectDurableSubscriberAttach(topicName, subscriptionName);
+ testPeer.ExpectLinkFlow();
+
+ var durableConsumer = await context.CreateDurableConsumerAsync(topic, subscriptionName, null, false);
+ Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateTemporaryQueue()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+
+ testPeer.ExpectBegin();
+
+ string dynamicAddress = "myTempQueueAddress";
+ testPeer.ExpectTempQueueCreationAttach(dynamicAddress);
+
+ ITemporaryQueue temporaryQueue = await context.CreateTemporaryQueueAsync();
+ Assert.NotNull(temporaryQueue, "TemporaryQueue object was null");
+ Assert.NotNull(temporaryQueue.QueueName, "TemporaryQueue queue name was null");
+ Assert.AreEqual(dynamicAddress, temporaryQueue.QueueName, "TemporaryQueue name not as expected");
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateTemporaryTopic()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+
+ testPeer.ExpectBegin();
+
+ string dynamicAddress = "myTempTopicAddress";
+ testPeer.ExpectTempTopicCreationAttach(dynamicAddress);
+
+ ITemporaryTopic temporaryTopic = await context.CreateTemporaryTopicAsync();
+ Assert.NotNull(temporaryTopic, "TemporaryTopic object was null");
+ Assert.NotNull(temporaryTopic.TopicName, "TemporaryTopic name was null");
+ Assert.AreEqual(dynamicAddress, temporaryTopic.TopicName, "TemporaryTopic name not as expected");
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateSharedConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ string topicName = "myTopic";
+ ITopic topic = await context.GetTopicAsync(topicName);
+ string subscriptionName = "mySubscription";
+
+ testPeer.ExpectSharedSubscriberAttach(topicName, subscriptionName);
+ testPeer.ExpectLinkFlow();
+
+ var durableConsumer = await context.CreateSharedConsumerAsync(topic, subscriptionName, null); //, false);
+ Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(20000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateSharedDurableConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ string topicName = "myTopic";
+ ITopic topic = await context.GetTopicAsync(topicName);
+ string subscriptionName = "mySubscription";
+
+ testPeer.ExpectSharedDurableSubscriberAttach(topicName, subscriptionName);
+ testPeer.ExpectLinkFlow();
+
+ var durableConsumer = await context.CreateSharedDurableConsumerAsync(topic, subscriptionName, null); //, false);
+ Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/NMSProducerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSProducerIntegrationTest.cs
new file mode 100644
index 0000000..4d5ca30
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/NMSProducerIntegrationTest.cs
@@ -0,0 +1,723 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ // Adapted from ProducerIntegrationTest to use NMSContext
+ [TestFixture]
+ public class NMSProducerIntegrationTestAsync : IntegrationTestFixture
+ {
+ private const long TICKS_PER_MILLISECOND = 10000;
+
+ [Test, Timeout(20_000)]
+ public async Task TestCloseSender()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await base.EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ await producer.CloseAsync();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSentTextMessageCanBeModified()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await base.EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ // Create and transfer a new message
+ String text = "myMessage";
+ testPeer.ExpectTransfer(x => Assert.AreEqual(text, (x.BodySection as AmqpValue).Value));
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ ITextMessage message = await context.CreateTextMessageAsync(text);
+ await producer.SendAsync(queue, message);
+
+ Assert.AreEqual(text, message.Text);
+ message.Text = text + text;
+ Assert.AreEqual(text + text, message.Text);
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestDefaultDeliveryModeProducesDurableMessages()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await base.EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ // Create and transfer a new message
+ testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ ITextMessage textMessage = await context.CreateTextMessageAsync();
+
+ await producer.SendAsync(queue, textMessage);
+ Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestProducerOverridesMessageDeliveryMode()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await base.EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ // Create and transfer a new message, explicitly setting the deliveryMode on the
+ // message (which applications shouldn't) to NON_PERSISTENT and sending it to check
+ // that the producer ignores this value and sends the message as PERSISTENT(/durable)
+ testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ ITextMessage textMessage = await context.CreateTextMessageAsync();
+ textMessage.NMSDeliveryMode = MsgDeliveryMode.NonPersistent;
+ Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode);
+
+ await producer.SendAsync(queue, textMessage);
+
+ Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+ await context.CloseAsync();
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentProducerSetDurableFalse()
+ {
+ await DoSendingMessageNonPersistentTestImpl(true);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentProducerOmitsHeader()
+ {
+ await DoSendingMessageNonPersistentTestImpl(false);
+ }
+
+ private async Task DoSendingMessageNonPersistentTestImpl(bool setPriority)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ //Add capability to indicate support for ANONYMOUS-RELAY
+ Symbol[] serverCapabilities = {SymbolUtil.OPEN_CAPABILITY_ANONYMOUS_RELAY};
+ var context = await EstablishNMSContextAsync(testPeer, serverCapabilities: serverCapabilities);
+ testPeer.ExpectBegin();
+
+ string queueName = "myQueue";
+ Action<object> targetMatcher = t =>
+ {
+ var target = t as Target;
+ Assert.IsNotNull(target);
+ };
+
+
+ testPeer.ExpectSenderAttach(targetMatcher: targetMatcher, sourceMatcher: Assert.NotNull, senderSettled: false);
+
+ IQueue queue = await context.GetQueueAsync(queueName);
+ INMSProducer producer = await context.CreateProducerAsync();
+
+ byte priority = 5;
+ String text = "myMessage";
+ testPeer.ExpectTransfer(messageMatcher: message =>
+ {
+ if (setPriority)
+ {
+ Assert.IsFalse(message.Header.Durable);
+ Assert.AreEqual(priority, message.Header.Priority);
+ }
+
+ Assert.AreEqual(text, (message.BodySection as AmqpValue).Value);
+ }, stateMatcher: Assert.IsNull,
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: new Accepted(),
+ responseSettled: true);
+
+ ITextMessage textMessage = await context.CreateTextMessageAsync(text);
+
+ producer.DeliveryMode = MsgDeliveryMode.NonPersistent;
+ if (setPriority)
+ producer.Priority = (MsgPriority) priority;
+
+ await producer.SendAsync(queue, textMessage);
+
+ Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode, "Should have NonPersistent delivery mode set");
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageSetsNMSDestination()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ string text = "myMessage";
+ ITextMessage message = await context.CreateTextMessageAsync(text);
+
+ testPeer.ExpectTransfer(m => Assert.AreEqual(text, (m.BodySection as AmqpValue).Value));
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ Assert.IsNull(message.NMSDestination, "Should not yet have a NMSDestination");
+
+ await producer.SendAsync(destination, message);
+
+ Assert.AreEqual(destination, message.NMSDestination, "Should have had NMSDestination set");
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageSetsNMSTimestamp()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ // Create matcher to expect the absolute-expiry-time field of the properties section to
+ // be set to a value greater than 'now'+ttl, within a delta.
+
+ DateTime creationLower = DateTime.UtcNow;
+ DateTime creationUpper = creationLower + TimeSpan.FromMilliseconds(3000);
+
+ var text = "myMessage";
+ testPeer.ExpectTransfer(m =>
+ {
+ Assert.IsTrue(m.Header.Durable);
+ Assert.That(m.Properties.CreationTime.Ticks, Is.GreaterThanOrEqualTo(creationLower.Ticks).Within(TICKS_PER_MILLISECOND));
+ Assert.That(m.Properties.CreationTime.Ticks, Is.LessThanOrEqualTo(creationUpper.Ticks).Within(TICKS_PER_MILLISECOND));
+ Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+ });
+
+ ITextMessage message = await context.CreateTextMessageAsync(text);
+ await producer.SendAsync(destination, message);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageSetsNMSExpirationRelatedAbsoluteExpiryAndTtlFields()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ uint ttl = 100_000;
+ DateTime currentTime = DateTime.UtcNow;
+ DateTime expirationLower = currentTime + TimeSpan.FromMilliseconds(ttl);
+ DateTime expirationUpper = currentTime + TimeSpan.FromMilliseconds(ttl) + TimeSpan.FromMilliseconds(5000);
+
+ // Create matcher to expect the absolute-expiry-time field of the properties section to
+ // be set to a value greater than 'now'+ttl, within a delta.
+ string text = "myMessage";
+ testPeer.ExpectTransfer(m =>
+ {
+ Assert.IsTrue(m.Header.Durable);
+ Assert.AreEqual(ttl, m.Header.Ttl);
+ Assert.That(m.Properties.AbsoluteExpiryTime.Ticks, Is.GreaterThanOrEqualTo(expirationLower.Ticks).Within(TICKS_PER_MILLISECOND));
+ Assert.That(m.Properties.AbsoluteExpiryTime.Ticks, Is.LessThanOrEqualTo(expirationUpper.Ticks).Within(TICKS_PER_MILLISECOND));
+ Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+ });
+
+ ITextMessage message = await context.CreateTextMessageAsync(text);
+ producer.TimeToLive = TimeSpan.FromMilliseconds(ttl);
+ producer.Priority = NMSConstants.defaultPriority;
+ producer.DeliveryMode = NMSConstants.defaultDeliveryMode;
+ await producer.SendAsync(destination, message);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestMessagesAreProducedWithProperDefaultPriorityWhenNoPrioritySpecified()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ byte priority = 4;
+
+ testPeer.ExpectTransfer(m => Assert.AreEqual(priority, m.Header.Priority));
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ ITextMessage message = await context.CreateTextMessageAsync();
+ Assert.AreEqual(MsgPriority.BelowNormal, message.NMSPriority);
+
+ await producer.SendAsync(destination, message);
+
+ Assert.AreEqual((MsgPriority) priority, message.NMSPriority);
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestNonDefaultPriorityProducesMessagesWithPriorityFieldAndSetsNMSPriority()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ byte priority = 9;
+
+ testPeer.ExpectTransfer(m => Assert.AreEqual(priority, m.Header.Priority));
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ ITextMessage message = await context.CreateTextMessageAsync();
+ Assert.AreEqual(MsgPriority.BelowNormal, message.NMSPriority);
+
+ producer.DeliveryMode = MsgDeliveryMode.Persistent;
+ producer.Priority = (MsgPriority) priority;
+ producer.TimeToLive = NMSConstants.defaultTimeToLive;
+ await producer.SendAsync(destination, message);
+
+ Assert.AreEqual((MsgPriority) priority, message.NMSPriority);
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageSetsNMSMessageId()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ string text = "myMessage";
+ string actualMessageId = null;
+ testPeer.ExpectTransfer(m =>
+ {
+ Assert.IsTrue(m.Header.Durable);
+ Assert.IsNotEmpty(m.Properties.MessageId);
+ actualMessageId = m.Properties.MessageId;
+ });
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ ITextMessage message = await context.CreateTextMessageAsync(text);
+ Assert.IsNull(message.NMSMessageId, "NMSMessageId should not yet be set");
+
+ await producer.SendAsync(destination, message);
+
+ Assert.IsNotNull(message.NMSMessageId);
+ Assert.IsNotEmpty(message.NMSMessageId, "NMSMessageId should be set");
+ Assert.IsTrue(message.NMSMessageId.StartsWith("ID:"), "MMS 'ID:' prefix not found");
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ // Get the value that was actually transmitted/received, verify it is a string, compare to what we have locally
+ Assert.AreEqual(message.NMSMessageId, actualMessageId, "Expected NMSMessageId value to be present in AMQP message");
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageWithDisableMessageIdHint()
+ {
+ await DoSendingMessageWithDisableMessageIdHintTestImpl(false);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageWithDisableMessageIdHintAndExistingMessageId()
+ {
+ await DoSendingMessageWithDisableMessageIdHintTestImpl(true);
+ }
+
+ private async Task DoSendingMessageWithDisableMessageIdHintTestImpl(bool existingId)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ string text = "myMessage";
+ testPeer.ExpectTransfer(m =>
+ {
+ Assert.IsTrue(m.Header.Durable);
+ Assert.IsNull(m.Properties.MessageId); // Check there is no message-id value;
+ Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+ });
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ ITextMessage message = await context.CreateTextMessageAsync(text);
+
+ Assert.IsNull(message.NMSMessageId, "NMSMessageId should not yet be set");
+
+ if (existingId)
+ {
+ string existingMessageId = "ID:this-should-be-overwritten-in-send";
+ message.NMSMessageId = existingMessageId;
+ Assert.AreEqual(existingMessageId, message.NMSMessageId, "NMSMessageId should now be se");
+ }
+
+ producer.DisableMessageID = true;
+
+ await producer.SendAsync(destination, message);
+
+ Assert.IsNull(message.NMSMessageId, "NMSMessageID should be null");
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ // TODO No connection listener in nms context
+ // [Test, Timeout(20_000)]
+ // public async Task TestRemotelyCloseProducer()
+ // {
+ // string breadCrumb = "ErrorMessageBreadCrumb";
+ //
+ // ManualResetEvent producerClosed = new ManualResetEvent(false);
+ // Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+ // mockConnectionListener
+ // .Setup(listener => listener.OnProducerClosed(It.IsAny<NmsMessageProducer>(), It.IsAny<Exception>()))
+ // .Callback(() => { producerClosed.Set(); });
+ //
+ // using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ // {
+ // NmsContext context = (NmsContext) EstablishNMSContext(testPeer);
+ // context.AddConnectionListener(mockConnectionListener.Object);
+ //
+ // testPeer.ExpectBegin();
+ // ISession session = context.CreateSession(AcknowledgementMode.AutoAcknowledge);
+ //
+ // // Create a producer, then remotely end it afterwards.
+ // testPeer.ExpectSenderAttach();
+ // testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, breadCrumb, delayBeforeSend: 10);
+ //
+ // IQueue destination = session.GetQueue("myQueue");
+ // IMessageProducer producer = session.CreateProducer(destination);
+ //
+ // // Verify the producer gets marked closed
+ // testPeer.WaitForAllMatchersToComplete(1000);
+ //
+ // Assert.True(producerClosed.WaitOne(TimeSpan.FromMilliseconds(1000)), "Producer closed callback didn't trigger");
+ // Assert.That(() => producer.DisableMessageID, Throws.Exception.InstanceOf<IllegalStateException>(), "Producer never closed");
+ //
+ // // Try closing it explicitly, should effectively no-op in client.
+ // // The test peer will throw during close if it sends anything.
+ // producer.Close();
+ // }
+ // }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendWhenLinkCreditIsZeroAndTimeout()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer, optionsString: "nms.sendTimeout=500");
+ testPeer.ExpectBegin();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+
+ ITextMessage message = await context.CreateTextMessageAsync("text");
+
+ // Expect the producer to attach. Don't send any credit so that the client will
+ // block on a send and we can test our timeouts.
+ testPeer.ExpectSenderAttachWithoutGrantingCredit();
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ var producer = await context.CreateProducerAsync();
+
+ Assert.CatchAsync<Exception>(async () => await producer.SendAsync(queue, message), "Send should time out.");
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendTimesOutWhenNoDispositionArrives()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer, optionsString: "nms.sendTimeout=500");
+ testPeer.ExpectBegin();
+
+ IQueue queue = await context.GetQueueAsync("myQueue");
+
+ ITextMessage message = await context.CreateTextMessageAsync("text");
+
+ // Expect the producer to attach and grant it some credit, it should send
+ // a transfer which we will not send any response for which should cause the
+ // send operation to time out.
+ testPeer.ExpectSenderAttach();
+ testPeer.ExpectTransferButDoNotRespond(messageMatcher: Assert.NotNull);
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ var producer = await context.CreateProducerAsync();
+
+ Assert.CatchAsync<Exception>(async () => await producer.SendAsync(queue, message), "Send should time out.");
+
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendWorksWhenConnectionNotStarted()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ testPeer.ExpectTransfer(Assert.IsNotNull);
+
+ await producer.SendAsync(destination, await context.CreateMessageAsync());
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await producer.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendWorksAfterConnectionStopped()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+
+ testPeer.ExpectTransfer(Assert.IsNotNull);
+
+ await context.StopAsync();
+
+ await producer.SendAsync(destination, await context.CreateMessageAsync());
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ await producer.CloseAsync();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessagePersistentSetsBatchableFalse()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+ testPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull,
+ stateMatcher: Assert.IsNull,
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: new Accepted(),
+ responseSettled: true,
+ batchable: false);
+
+ IMessage message = await context.CreateMessageAsync();
+ producer.DeliveryMode = MsgDeliveryMode.Persistent;
+ producer.Priority = MsgPriority.Normal;
+ producer.TimeToLive = NMSConstants.defaultTimeToLive;
+ await producer.SendAsync(destination, message);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentSetsBatchableFalse()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = await EstablishNMSContextAsync(testPeer);
+ await context.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ IQueue destination = await context.GetQueueAsync("myQueue");
+ var producer = await context.CreateProducerAsync();
+ testPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull,
+ stateMatcher: Assert.IsNull,
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: new Accepted(),
+ responseSettled: true,
+ batchable: false);
+
+ IMessage message = await context.CreateMessageAsync();
+ producer.DeliveryMode = MsgDeliveryMode.NonPersistent;
+ producer.Priority = MsgPriority.Normal;
+ producer.TimeToLive = NMSConstants.defaultTimeToLive;
+ await producer.SendAsync(destination, message);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+ await context.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationAsyncTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationAsyncTest.cs
new file mode 100644
index 0000000..641b36b
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationAsyncTest.cs
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class ProducerIntegrationAsyncTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestSentAsyncIsAsynchronous()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await base.EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Create and transfer a new message
+ String text = "myMessage";
+ testPeer.ExpectTransfer(messageMatcher: m => Assert.AreEqual(text, (m.BodySection as AmqpValue).Value),
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: new Accepted(),
+ responseSettled: true,
+ stateMatcher: Assert.IsNull,
+ dispositionDelay: 10); // 10ms should be enough
+ testPeer.ExpectClose();
+
+ ITextMessage message = await session.CreateTextMessageAsync(text);
+ var sendTask = producer.SendAsync(message);
+ // Instantly check if its not completed yet, we want async, so it should not be completed right after
+ Assert.AreEqual(false, sendTask.IsCompleted);
+
+ // And now wait for task to complete
+ sendTask.Wait(20_000);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSentAsync()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await base.EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Create and transfer a new message
+ String text = "myMessage";
+ testPeer.ExpectTransfer(messageMatcher: m => Assert.AreEqual(text, (m.BodySection as AmqpValue).Value),
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: new Accepted(),
+ responseSettled: true,
+ stateMatcher: Assert.IsNull,
+ dispositionDelay: 10); // 10ms should be enough
+ testPeer.ExpectClose();
+
+ ITextMessage message = await session.CreateTextMessageAsync(text);
+ await producer.SendAsync(message);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+
+ [Test, Timeout(20_000)]
+ public async Task TestProducerWorkWithAsyncAwait()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await base.EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Create and transfer a new message, explicitly setting the deliveryMode on the
+ // message (which applications shouldn't) to NON_PERSISTENT and sending it to check
+ // that the producer ignores this value and sends the message as PERSISTENT(/durable)
+ testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+ testPeer.ExpectClose();
+
+ ITextMessage textMessage = await session.CreateTextMessageAsync();
+ textMessage.NMSDeliveryMode = MsgDeliveryMode.NonPersistent;
+ Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode);
+
+ await producer.SendAsync(textMessage);
+
+ Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+ await connection.CloseAsync();
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationTest.cs
new file mode 100644
index 0000000..f1328a0
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/ProducerIntegrationTest.cs
@@ -0,0 +1,782 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Amqp.Framing;
+using Amqp.Types;
+using Apache.NMS;
+using Apache.NMS.AMQP;
+using Apache.NMS.AMQP.Util;
+using Moq;
+using NMS.AMQP.Test.TestAmqp;
+using NMS.AMQP.Test.TestAmqp.BasicTypes;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class ProducerIntegrationTestAsync : IntegrationTestFixture
+ {
+ private const long TICKS_PER_MILLISECOND = 10000;
+
+ [Test, Timeout(20_000)]
+ public async Task TestCloseSender()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await base.EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync();
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ testPeer.ExpectClose();
+
+ await producer.CloseAsync();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSentTextMessageCanBeModified()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await base.EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Create and transfer a new message
+ String text = "myMessage";
+ testPeer.ExpectTransfer(x => Assert.AreEqual(text, (x.BodySection as AmqpValue).Value));
+ testPeer.ExpectClose();
+
+ ITextMessage message = await session.CreateTextMessageAsync(text);
+ await producer.SendAsync(message);
+
+ Assert.AreEqual(text, message.Text);
+ message.Text = text + text;
+ Assert.AreEqual(text + text, message.Text);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestDefaultDeliveryModeProducesDurableMessages()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await base.EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Create and transfer a new message
+ testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+ testPeer.ExpectClose();
+
+ ITextMessage textMessage = await session.CreateTextMessageAsync();
+
+ await producer.SendAsync(textMessage);
+ Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestProducerOverridesMessageDeliveryMode()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await base.EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Create and transfer a new message, explicitly setting the deliveryMode on the
+ // message (which applications shouldn't) to NON_PERSISTENT and sending it to check
+ // that the producer ignores this value and sends the message as PERSISTENT(/durable)
+ testPeer.ExpectTransfer(message => Assert.IsTrue(message.Header.Durable));
+ testPeer.ExpectClose();
+
+ ITextMessage textMessage = await session.CreateTextMessageAsync();
+ textMessage.NMSDeliveryMode = MsgDeliveryMode.NonPersistent;
+ Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode);
+
+ await producer.SendAsync(textMessage);
+
+ Assert.AreEqual(MsgDeliveryMode.Persistent, textMessage.NMSDeliveryMode);
+
+ await connection.CloseAsync();
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentProducerSetDurableFalse()
+ {
+ await DoSendingMessageNonPersistentTestImpl(false, true, true);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentProducerSetDurableFalseAnonymousProducer()
+ {
+ await DoSendingMessageNonPersistentTestImpl(true, true, true);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentSendSetDurableFalse()
+ {
+ await DoSendingMessageNonPersistentTestImpl(false, true, false);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentSendSetDurableFalseAnonymousProducer()
+ {
+ await DoSendingMessageNonPersistentTestImpl(true, true, false);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentProducerOmitsHeader()
+ {
+ await DoSendingMessageNonPersistentTestImpl(false, false, true);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentProducerOmitsHeaderAnonymousProducer()
+ {
+ await DoSendingMessageNonPersistentTestImpl(true, false, true);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentSendOmitsHeader()
+ {
+ await DoSendingMessageNonPersistentTestImpl(false, false, false);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentSendOmitsHeaderAnonymousProducer()
+ {
+ await DoSendingMessageNonPersistentTestImpl(true, false, false);
+ }
+
+ private async Task DoSendingMessageNonPersistentTestImpl(bool anonymousProducer, bool setPriority, bool setOnProducer)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ //Add capability to indicate support for ANONYMOUS-RELAY
+ Symbol[] serverCapabilities = { SymbolUtil.OPEN_CAPABILITY_ANONYMOUS_RELAY };
+ IConnection connection = await EstablishConnectionAsync(testPeer, serverCapabilities: serverCapabilities);
+ testPeer.ExpectBegin();
+
+ string queueName = "myQueue";
+ Action<object> targetMatcher = t =>
+ {
+ var target = t as Target;
+ Assert.IsNotNull(target);
+ if (anonymousProducer)
+ Assert.IsNull(target.Address);
+ else
+ Assert.AreEqual(queueName, target.Address);
+ };
+
+
+ testPeer.ExpectSenderAttach(targetMatcher: targetMatcher, sourceMatcher: Assert.NotNull, senderSettled: false);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync(queueName);
+ IMessageProducer producer = null;
+ if (anonymousProducer)
+ producer = await session.CreateProducerAsync();
+ else
+ producer = await session.CreateProducerAsync(queue);
+
+ byte priority = 5;
+ String text = "myMessage";
+ testPeer.ExpectTransfer(messageMatcher: message =>
+ {
+ if (setPriority)
+ {
+ Assert.IsFalse(message.Header.Durable);
+ Assert.AreEqual(5, message.Header.Priority);
+ }
+
+ Assert.AreEqual(text, (message.BodySection as AmqpValue).Value);
+ }, stateMatcher: Assert.IsNull,
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: new Accepted(),
+ responseSettled: true);
+
+ ITextMessage textMessage = await session.CreateTextMessageAsync(text);
+
+ if (setOnProducer)
+ {
+ producer.DeliveryMode = MsgDeliveryMode.NonPersistent;
+ if (setPriority)
+ producer.Priority = (MsgPriority) 5;
+
+ if (anonymousProducer)
+ await producer.SendAsync(queue, textMessage);
+ else
+ await producer.SendAsync(textMessage);
+ }
+ else
+ {
+ if (anonymousProducer)
+ {
+ await producer.SendAsync(destination: queue,
+ message: textMessage,
+ deliveryMode: MsgDeliveryMode.NonPersistent,
+ priority: setPriority ? (MsgPriority) priority : NMSConstants.defaultPriority,
+ timeToLive: NMSConstants.defaultTimeToLive);
+ }
+ else
+ {
+ await producer.SendAsync(message: textMessage,
+ deliveryMode: MsgDeliveryMode.NonPersistent,
+ priority: setPriority ? (MsgPriority) priority : NMSConstants.defaultPriority,
+ timeToLive: NMSConstants.defaultTimeToLive);
+ }
+ }
+
+ Assert.AreEqual(MsgDeliveryMode.NonPersistent, textMessage.NMSDeliveryMode, "Should have NonPersistent delivery mode set");
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageSetsNMSDestination()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ string text = "myMessage";
+ ITextMessage message = await session.CreateTextMessageAsync(text);
+
+ testPeer.ExpectTransfer(m => Assert.AreEqual(text, (m.BodySection as AmqpValue).Value));
+ testPeer.ExpectClose();
+
+ Assert.IsNull(message.NMSDestination, "Should not yet have a NMSDestination");
+
+ await producer.SendAsync(message);
+
+ Assert.AreEqual(destination, message.NMSDestination, "Should have had NMSDestination set");
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageSetsNMSTimestamp()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ // Create matcher to expect the absolute-expiry-time field of the properties section to
+ // be set to a value greater than 'now'+ttl, within a delta.
+
+ DateTime creationLower = DateTime.UtcNow;
+ DateTime creationUpper = creationLower + TimeSpan.FromMilliseconds(3000);
+
+ var text = "myMessage";
+ testPeer.ExpectTransfer(m =>
+ {
+ Assert.IsTrue(m.Header.Durable);
+ Assert.That(m.Properties.CreationTime.Ticks, Is.GreaterThanOrEqualTo(creationLower.Ticks).Within(TICKS_PER_MILLISECOND));
+ Assert.That(m.Properties.CreationTime.Ticks, Is.LessThanOrEqualTo(creationUpper.Ticks).Within(TICKS_PER_MILLISECOND));
+ Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+ });
+
+ ITextMessage message = await session.CreateTextMessageAsync(text);
+ await producer.SendAsync(message);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageSetsNMSExpirationRelatedAbsoluteExpiryAndTtlFields()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ uint ttl = 100_000;
+ DateTime currentTime = DateTime.UtcNow;
+ DateTime expirationLower = currentTime + TimeSpan.FromMilliseconds(ttl);
+ DateTime expirationUpper = currentTime + TimeSpan.FromMilliseconds(ttl) + TimeSpan.FromMilliseconds(5000);
+
+ // Create matcher to expect the absolute-expiry-time field of the properties section to
+ // be set to a value greater than 'now'+ttl, within a delta.
+ string text = "myMessage";
+ testPeer.ExpectTransfer(m =>
+ {
+ Assert.IsTrue(m.Header.Durable);
+ Assert.AreEqual(ttl, m.Header.Ttl);
+ Assert.That(m.Properties.AbsoluteExpiryTime.Ticks, Is.GreaterThanOrEqualTo(expirationLower.Ticks).Within(TICKS_PER_MILLISECOND));
+ Assert.That(m.Properties.AbsoluteExpiryTime.Ticks, Is.LessThanOrEqualTo(expirationUpper.Ticks).Within(TICKS_PER_MILLISECOND));
+ Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+ });
+
+ ITextMessage message = await session.CreateTextMessageAsync(text);
+ await producer.SendAsync(message, NMSConstants.defaultDeliveryMode, NMSConstants.defaultPriority, TimeSpan.FromMilliseconds(ttl));
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestMessagesAreProducedWithProperDefaultPriorityWhenNoPrioritySpecified()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ byte priority = 4;
+
+ testPeer.ExpectTransfer(m => Assert.AreEqual(priority, m.Header.Priority));
+ testPeer.ExpectClose();
+
+ ITextMessage message = await session.CreateTextMessageAsync();
+ Assert.AreEqual(MsgPriority.BelowNormal, message.NMSPriority);
+
+ await producer.SendAsync(message);
+
+ Assert.AreEqual((MsgPriority) priority, message.NMSPriority);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestNonDefaultPriorityProducesMessagesWithPriorityFieldAndSetsNMSPriority()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ byte priority = 9;
+
+ testPeer.ExpectTransfer(m => Assert.AreEqual(priority, m.Header.Priority));
+ testPeer.ExpectClose();
+
+ ITextMessage message = await session.CreateTextMessageAsync();
+ Assert.AreEqual(MsgPriority.BelowNormal, message.NMSPriority);
+
+ await producer.SendAsync(message, MsgDeliveryMode.Persistent, (MsgPriority) priority, NMSConstants.defaultTimeToLive);
+
+ Assert.AreEqual((MsgPriority) priority, message.NMSPriority);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageSetsNMSMessageId()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ string text = "myMessage";
+ string actualMessageId = null;
+ testPeer.ExpectTransfer(m =>
+ {
+ Assert.IsTrue(m.Header.Durable);
+ Assert.IsNotEmpty(m.Properties.MessageId);
+ actualMessageId = m.Properties.MessageId;
+ });
+ testPeer.ExpectClose();
+
+ ITextMessage message = await session.CreateTextMessageAsync(text);
+ Assert.IsNull(message.NMSMessageId, "NMSMessageId should not yet be set");
+
+ await producer.SendAsync(message);
+
+ Assert.IsNotNull(message.NMSMessageId);
+ Assert.IsNotEmpty(message.NMSMessageId, "NMSMessageId should be set");
+ Assert.IsTrue(message.NMSMessageId.StartsWith("ID:"), "MMS 'ID:' prefix not found");
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ // Get the value that was actually transmitted/received, verify it is a string, compare to what we have locally
+ Assert.AreEqual(message.NMSMessageId, actualMessageId, "Expected NMSMessageId value to be present in AMQP message");
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageWithDisableMessageIdHint()
+ {
+ await DoSendingMessageWithDisableMessageIdHintTestImpl(false);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageWithDisableMessageIdHintAndExistingMessageId()
+ {
+ await DoSendingMessageWithDisableMessageIdHintTestImpl(true);
+ }
+
+ private async Task DoSendingMessageWithDisableMessageIdHintTestImpl(bool existingId)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ string text = "myMessage";
+ testPeer.ExpectTransfer(m =>
+ {
+ Assert.IsTrue(m.Header.Durable);
+ Assert.IsNull(m.Properties.MessageId); // Check there is no message-id value;
+ Assert.AreEqual(text, (m.BodySection as AmqpValue).Value);
+ });
+ testPeer.ExpectClose();
+
+ ITextMessage message = await session.CreateTextMessageAsync(text);
+
+ Assert.IsNull(message.NMSMessageId, "NMSMessageId should not yet be set");
+
+ if (existingId)
+ {
+ string existingMessageId = "ID:this-should-be-overwritten-in-send";
+ message.NMSMessageId = existingMessageId;
+ Assert.AreEqual(existingMessageId, message.NMSMessageId, "NMSMessageId should now be se");
+ }
+
+ producer.DisableMessageID = true;
+
+ await producer.SendAsync(message);
+
+ Assert.IsNull(message.NMSMessageId, "NMSMessageID should be null");
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRemotelyCloseProducer()
+ {
+ string breadCrumb = "ErrorMessageBreadCrumb";
+
+ ManualResetEvent producerClosed = new ManualResetEvent(false);
+ Mock<INmsConnectionListener> mockConnectionListener = new Mock<INmsConnectionListener>();
+ mockConnectionListener
+ .Setup(listener => listener.OnProducerClosed(It.IsAny<NmsMessageProducer>(), It.IsAny<Exception>()))
+ .Callback(() => { producerClosed.Set(); });
+
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ NmsConnection connection = (NmsConnection) await EstablishConnectionAsync(testPeer);
+ connection.AddConnectionListener(mockConnectionListener.Object);
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ // Create a producer, then remotely end it afterwards.
+ testPeer.ExpectSenderAttach();
+ testPeer.RemotelyDetachLastOpenedLinkOnLastOpenedSession(expectDetachResponse: true, closed: true, errorType: AmqpError.RESOURCE_DELETED, breadCrumb, delayBeforeSend: 10);
+
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ // Verify the producer gets marked closed
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ Assert.True(producerClosed.WaitOne(TimeSpan.FromMilliseconds(1000)), "Producer closed callback didn't trigger");
+ Assert.That(() => producer.DisableMessageID, Throws.Exception.InstanceOf<IllegalStateException>(), "Producer never closed");
+
+ // Try closing it explicitly, should effectively no-op in client.
+ // The test peer will throw during close if it sends anything.
+ await producer.CloseAsync();
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendWhenLinkCreditIsZeroAndTimeout()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, optionsString: "nms.sendTimeout=500");
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ ITextMessage message = await session.CreateTextMessageAsync("text");
+
+ // Expect the producer to attach. Don't send any credit so that the client will
+ // block on a send and we can test our timeouts.
+ testPeer.ExpectSenderAttachWithoutGrantingCredit();
+ testPeer.ExpectClose();
+
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ Assert.CatchAsync<Exception>(async () => await producer.SendAsync(message), "Send should time out.");
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendTimesOutWhenNoDispositionArrives()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, optionsString: "nms.sendTimeout=500");
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ ITextMessage message = await session.CreateTextMessageAsync("text");
+
+ // Expect the producer to attach and grant it some credit, it should send
+ // a transfer which we will not send any response for which should cause the
+ // send operation to time out.
+ testPeer.ExpectSenderAttach();
+ testPeer.ExpectTransferButDoNotRespond(messageMatcher: Assert.NotNull);
+ testPeer.ExpectClose();
+
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ Assert.CatchAsync<Exception>(async () => await producer.SendAsync(message), "Send should time out.");
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendWorksWhenConnectionNotStarted()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ testPeer.ExpectTransfer(Assert.IsNotNull);
+
+ await producer.SendAsync(await session.CreateMessageAsync());
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await producer.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendWorksAfterConnectionStopped()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+
+ testPeer.ExpectTransfer(Assert.IsNotNull);
+
+ await connection.StopAsync();
+
+ await producer.SendAsync(await session.CreateMessageAsync());
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ testPeer.ExpectClose();
+
+ await producer.CloseAsync();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessagePersistentSetsBatchableFalse()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+ testPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull,
+ stateMatcher: Assert.IsNull,
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: new Accepted(),
+ responseSettled: true,
+ batchable: false);
+
+ IMessage message = await session.CreateMessageAsync();
+ await producer.SendAsync(message: message, deliveryMode: MsgDeliveryMode.Persistent, MsgPriority.Normal, NMSConstants.defaultTimeToLive);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendingMessageNonPersistentSetsBatchableFalse()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectSenderAttach();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ IQueue destination = await session.GetQueueAsync("myQueue");
+ IMessageProducer producer = await session.CreateProducerAsync(destination);
+ testPeer.ExpectTransfer(messageMatcher: Assert.IsNotNull,
+ stateMatcher: Assert.IsNull,
+ settled: false,
+ sendResponseDisposition: true,
+ responseState: new Accepted(),
+ responseSettled: true,
+ batchable: false);
+
+ IMessage message = await session.CreateMessageAsync();
+ await producer.SendAsync(message: message, deliveryMode: MsgDeliveryMode.NonPersistent, MsgPriority.Normal, NMSConstants.defaultTimeToLive);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/SessionIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/SessionIntegrationTest.cs
new file mode 100644
index 0000000..ca34e19
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/SessionIntegrationTest.cs
@@ -0,0 +1,289 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class SessionIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestCloseSession()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+ Assert.NotNull(session, "Session should not be null");
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ await session.CloseAsync();
+
+ // Should send nothing and throw no error.
+ await session.CloseAsync();
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateProducer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ testPeer.ExpectSenderAttach();
+ testPeer.ExpectClose();
+
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ await session.CreateProducerAsync(queue);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectClose();
+
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ await session.CreateConsumerAsync(queue);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateConsumerWithEmptySelector()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectClose();
+
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ await session.CreateConsumerAsync(queue, "");
+ await session.CreateConsumerAsync(queue, "", noLocal: false);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateConsumerWithNullSelector()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectClose();
+
+ IQueue queue = await session.GetQueueAsync("myQueue");
+ await session.CreateConsumerAsync(queue, null);
+ await session.CreateConsumerAsync(queue, null, noLocal: false);
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateDurableConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string topicName = "myTopic";
+ ITopic topic = await session.GetTopicAsync(topicName);
+ string subscriptionName = "mySubscription";
+
+ testPeer.ExpectDurableSubscriberAttach(topicName, subscriptionName);
+ testPeer.ExpectLinkFlow();
+
+ IMessageConsumer durableConsumer = await session.CreateDurableConsumerAsync(topic, subscriptionName, null, false);
+ Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateTemporaryQueue()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string dynamicAddress = "myTempQueueAddress";
+ testPeer.ExpectTempQueueCreationAttach(dynamicAddress);
+
+ ITemporaryQueue temporaryQueue = await session.CreateTemporaryQueueAsync();
+ Assert.NotNull(temporaryQueue, "TemporaryQueue object was null");
+ Assert.NotNull(temporaryQueue.QueueName, "TemporaryQueue queue name was null");
+ Assert.AreEqual(dynamicAddress, temporaryQueue.QueueName, "TemporaryQueue name not as expected");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateTemporaryTopic()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string dynamicAddress = "myTempTopicAddress";
+ testPeer.ExpectTempTopicCreationAttach(dynamicAddress);
+
+ ITemporaryTopic temporaryTopic = await session.CreateTemporaryTopicAsync();
+ Assert.NotNull(temporaryTopic, "TemporaryTopic object was null");
+ Assert.NotNull(temporaryTopic.TopicName, "TemporaryTopic name was null");
+ Assert.AreEqual(dynamicAddress, temporaryTopic.TopicName, "TemporaryTopic name not as expected");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateSharedConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string topicName = "myTopic";
+ ITopic topic = await session.GetTopicAsync(topicName);
+ string subscriptionName = "mySubscription";
+
+ testPeer.ExpectSharedSubscriberAttach(topicName, subscriptionName);
+ testPeer.ExpectLinkFlow();
+
+ IMessageConsumer durableConsumer = await session.CreateSharedConsumerAsync(topic, subscriptionName, null);//, false);
+ // IMessageConsumer durableConsumer = session.CreateDurableConsumer(topic, subscriptionName, null, false);
+ Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(20000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCreateSharedDurableConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string topicName = "myTopic";
+ ITopic topic = await session.GetTopicAsync(topicName);
+ string subscriptionName = "mySubscription";
+
+ testPeer.ExpectSharedDurableSubscriberAttach(topicName, subscriptionName);
+ testPeer.ExpectLinkFlow();
+
+ IMessageConsumer durableConsumer = await session.CreateSharedDurableConsumerAsync(topic, subscriptionName, null); //, false);
+ Assert.NotNull(durableConsumer, "MessageConsumer object was null");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/SubscriptionsIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/SubscriptionsIntegrationTest.cs
new file mode 100644
index 0000000..52c5920
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/SubscriptionsIntegrationTest.cs
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class SubscriptionsIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestUnsubscribeExclusiveDurableSubWhileActiveThenInactive()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ String topicName = "myTopic";
+ ITopic dest = await session.GetTopicAsync("myTopic");
+ String subscriptionName = "mySubscription";
+
+ // Attach the durable exclusive receiver
+ testPeer.ExpectDurableSubscriberAttach(topicName: topicName, subscriptionName: subscriptionName);
+ testPeer.ExpectLinkFlow();
+
+ IMessageConsumer consumer = await session.CreateDurableConsumerAsync(dest, subscriptionName, null, false);
+ Assert.NotNull(consumer, "TopicSubscriber object was null");
+
+ // Now try to unsubscribe, should fail
+ Assert.CatchAsync<NMSException>(async () => session.DeleteDurableConsumer(subscriptionName));
+
+ // Now close the subscriber
+ testPeer.ExpectDetach(expectClosed: false, sendResponse: true, replyClosed: false);
+
+ await consumer.CloseAsync();
+
+ // Try to unsubscribe again, should work now
+ testPeer.ExpectDurableSubUnsubscribeNullSourceLookup(failLookup: false, shared: false, subscriptionName: subscriptionName, topicName: topicName, hasClientId: true);
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ session.DeleteDurableConsumer(subscriptionName);
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryQueueIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryQueueIntegrationTest.cs
new file mode 100644
index 0000000..8b0ee5f
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryQueueIntegrationTest.cs
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class TemporaryQueueIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestCantConsumeFromTemporaryQueueCreatedOnAnotherConnection()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string dynamicAddress = "myTempQueueAddress";
+ testPeer.ExpectTempQueueCreationAttach(dynamicAddress);
+
+ ITemporaryQueue temporaryQueue = await session.CreateTemporaryQueueAsync();
+
+ IConnection connection2 = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+
+ ISession session2 = await connection2.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ Assert.CatchAsync<InvalidDestinationException>(async () => await session2.CreateConsumerAsync(temporaryQueue), "Should not be able to create consumer from temporary queue from another connection");
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCantDeleteTemporaryQueueWithConsumers()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string dynamicAddress = "myTempQueueAddress";
+ testPeer.ExpectTempQueueCreationAttach(dynamicAddress);
+
+ ITemporaryQueue temporaryQueue = await session.CreateTemporaryQueueAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ IMessageConsumer consumer = await session.CreateConsumerAsync(temporaryQueue);
+
+ Assert.CatchAsync<IllegalStateException>(async () => await temporaryQueue.DeleteAsync(), "should not be able to delete temporary queue with active consumers");
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ // Now it should be allowed
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await temporaryQueue.DeleteAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryTopicIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryTopicIntegrationTest.cs
new file mode 100644
index 0000000..ed2122f
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/TemporaryTopicIntegrationTest.cs
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Threading.Tasks;
+using Apache.NMS;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class TemporaryTopicIntegrationTestAsync : IntegrationTestFixture
+ {
+
+ [Test, Timeout(20_000)]
+ public async Task TestCantConsumeFromTemporaryTopicCreatedOnAnotherConnection()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string dynamicAddress = "myTempTopicAddress";
+ testPeer.ExpectTempTopicCreationAttach(dynamicAddress);
+
+ ITemporaryTopic topic = await session.CreateTemporaryTopicAsync();
+
+ IConnection connection2 = await EstablishConnectionAsync(testPeer);
+ testPeer.ExpectBegin();
+
+ ISession session2 = await connection2.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ Assert.CatchAsync<InvalidDestinationException>(async () => await session2.CreateConsumerAsync(topic), "Should not be able to create consumer from temporary topic from another connection");
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCantDeleteTemporaryQueueWithConsumers()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.AutoAcknowledge);
+
+ string dynamicAddress = "myTempTopicAddress";
+ testPeer.ExpectTempTopicCreationAttach(dynamicAddress);
+
+ ITemporaryTopic topic = await session.CreateTemporaryTopicAsync();
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+ IMessageConsumer consumer = await session.CreateConsumerAsync(topic);
+
+ Assert.CatchAsync<IllegalStateException>(async () => await topic.DeleteAsync(), "should not be able to delete temporary topic with active consumers");
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await consumer.CloseAsync();
+
+ // Now it should be allowed
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+ await topic.DeleteAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/Async/TransactionsIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/Async/TransactionsIntegrationTest.cs
new file mode 100644
index 0000000..d2fa236
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Integration/Async/TransactionsIntegrationTest.cs
@@ -0,0 +1,1473 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Amqp;
+using Amqp.Framing;
+using Amqp.Transactions;
+using Apache.NMS;
+using Apache.NMS.AMQP.Util;
+using NMS.AMQP.Test.TestAmqp;
+using NUnit.Framework;
+using IConnection = Apache.NMS.IConnection;
+using ISession = Apache.NMS.ISession;
+
+namespace NMS.AMQP.Test.Integration.Async
+{
+ [TestFixture]
+ public class TransactionsIntegrationTestAsync : IntegrationTestFixture
+ {
+ [Test, Timeout(20_000)]
+ public async Task TestTransactionRolledBackOnSessionClose()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ byte[] txnId = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId);
+
+ // Closed session should roll-back the TX with a failed discharge
+ testPeer.ExpectDischarge(txnId, true);
+ testPeer.ExpectEnd();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ await session.CloseAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestTransactionCommitFailWithEmptyRejectedDisposition()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a Declared disposition state containing the txnId.
+
+ byte[] txnId1 = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId1);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Create a producer to use in provoking creation of the AMQP transaction
+ testPeer.ExpectSenderAttach();
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Expect the message which was sent under the current transaction. Check it carries
+ // TransactionalState with the above txnId but has no outcome. Respond with a
+ // TransactionalState with Accepted outcome.
+ Action<DeliveryState> stateMatcher = state =>
+ {
+ Assert.IsInstanceOf<TransactionalState>(state);
+ var transactionalState = (TransactionalState) state;
+ CollectionAssert.AreEqual(txnId1, transactionalState.TxnId);
+ Assert.IsNull(transactionalState.Outcome);
+ };
+ testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+ {
+ Outcome = new Accepted(),
+ TxnId = txnId1
+ }, responseSettled: true);
+
+ await producer.SendAsync(await session.CreateMessageAsync());
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with rejected and settled disposition to indicate the commit failed
+ testPeer.ExpectDischarge(txnId1, dischargeState: false, responseState: new Rejected());
+
+ // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId2 = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId2);
+
+ Assert.CatchAsync<TransactionRolledBackException>(async () => await session.CommitAsync(), "Commit operation should have failed.");
+
+ // session should roll back on close
+ testPeer.ExpectDischarge(txnId2, true);
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestProducedMessagesAfterCommitOfSentMessagesFails()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a Declared disposition state containing the txnId.
+ byte[] txnId1 = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId1);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Create a producer to use in provoking creation of the AMQP transaction
+ testPeer.ExpectSenderAttach();
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Expect the message which was sent under the current transaction. Check it carries
+ // TransactionalState with the above txnId but has no outcome. Respond with a
+ // TransactionalState with Accepted outcome.
+ Action<DeliveryState> stateMatcher = state =>
+ {
+ Assert.IsInstanceOf<TransactionalState>(state);
+ var transactionalState = (TransactionalState) state;
+ CollectionAssert.AreEqual(txnId1, transactionalState.TxnId);
+ Assert.IsNull(transactionalState.Outcome);
+ };
+ testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+ {
+ Outcome = new Accepted(),
+ TxnId = txnId1
+ }, responseSettled: true);
+
+ await producer.SendAsync(await session.CreateMessageAsync());
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with rejected and settled disposition to indicate the commit failed
+ testPeer.ExpectDischarge(txnId1, false, new Rejected() { Error = new Error(ErrorCode.InternalError) { Description = "Unknown error" } });
+
+ // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId2 = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId2);
+
+ Assert.CatchAsync<TransactionRolledBackException>(async () => await session.CommitAsync(), "Commit operation should have failed.");
+
+ // Expect the message which was sent under the current transaction. Check it carries
+ // TransactionalState with the above txnId but has no outcome. Respond with a
+ // TransactionalState with Accepted outcome.
+ stateMatcher = state =>
+ {
+ Assert.IsInstanceOf<TransactionalState>(state);
+ var transactionalState = (TransactionalState) state;
+ CollectionAssert.AreEqual(txnId2, transactionalState.TxnId);
+ Assert.IsNull(transactionalState.Outcome);
+ };
+ testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+ {
+ Outcome = new Accepted(),
+ TxnId = txnId2
+ }, responseSettled: true);
+ testPeer.ExpectDischarge(txnId2, dischargeState: true);
+
+ await producer.SendAsync(await session.CreateMessageAsync());
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestProducedMessagesAfterRollbackSentMessagesFails()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a Declared disposition state containing the txnId.
+ byte[] txnId1 = { 5, 6, 7, 8 };
+ byte[] txnId2 = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId1);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Create a producer to use in provoking creation of the AMQP transaction
+ testPeer.ExpectSenderAttach();
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Expect the message which was sent under the current transaction. Check it carries
+ // TransactionalState with the above txnId but has no outcome. Respond with a
+ // TransactionalState with Accepted outcome.
+
+ Action<DeliveryState> stateMatcher = state =>
+ {
+ Assert.IsInstanceOf<TransactionalState>(state);
+ var transactionalState = (TransactionalState) state;
+ CollectionAssert.AreEqual(txnId1, transactionalState.TxnId);
+ Assert.IsNull(transactionalState.Outcome);
+ };
+ testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+ {
+ Outcome = new Accepted(),
+ TxnId = txnId1
+ }, responseSettled: true);
+
+ await producer.SendAsync(await session.CreateMessageAsync());
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with rejected and settled disposition to indicate the rollback failed
+ testPeer.ExpectDischarge(txnId1, true, new Rejected() { Error = new Error(ErrorCode.InternalError) { Description = "Unknown error" } });
+
+ // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ testPeer.ExpectDeclare(txnId2);
+
+ Assert.CatchAsync<TransactionRolledBackException>(async () => await session.RollbackAsync(), "Rollback operation should have failed.");
+
+ // Expect the message which was sent under the current transaction. Check it carries
+ // TransactionalState with the above txnId but has no outcome. Respond with a
+ // TransactionalState with Accepted outcome.
+ stateMatcher = state =>
+ {
+ Assert.IsInstanceOf<TransactionalState>(state);
+ var transactionalState = (TransactionalState) state;
+ CollectionAssert.AreEqual(txnId2, transactionalState.TxnId);
+ Assert.IsNull(transactionalState.Outcome);
+ };
+ testPeer.ExpectTransfer(messageMatcher: Assert.NotNull, stateMatcher: stateMatcher, responseState: new TransactionalState
+ {
+ Outcome = new Accepted(),
+ TxnId = txnId2
+ }, responseSettled: true);
+ testPeer.ExpectDischarge(txnId2, dischargeState: true);
+
+ await producer.SendAsync(await session.CreateMessageAsync());
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCommitTransactedSessionWithConsumerReceivingAllMessages()
+ {
+ await DoCommitTransactedSessionWithConsumerTestImpl(1, 1, false, false);
+ }
+
+ [Test, Timeout(20_000), Ignore("Until deferred close is implemented for AmqpConsumer")]
+ public async Task TestCommitTransactedSessionWithConsumerReceivingAllMessagesAndCloseBefore()
+ {
+ await DoCommitTransactedSessionWithConsumerTestImpl(1, 1, true, true);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCommitTransactedSessionWithConsumerReceivingAllMessagesAndCloseAfter()
+ {
+ await DoCommitTransactedSessionWithConsumerTestImpl(1, 1, true, false);
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCommitTransactedSessionWithConsumerReceivingSomeMessages()
+ {
+ await DoCommitTransactedSessionWithConsumerTestImpl(5, 2, false, false);
+ }
+
+ [Test, Timeout(20_000), Ignore("Until deferred close is implemented for AmqpConsumer")]
+ public async Task TestCommitTransactedSessionWithConsumerReceivingSomeMessagesAndClosesBefore()
+ {
+ await DoCommitTransactedSessionWithConsumerTestImpl(5, 2, true, true);
+ }
+
+ [Test, Timeout(20_000), Ignore("Until deferred close is implemented for AmqpConsumer")]
+ public async Task TestCommitTransactedSessionWithConsumerReceivingSomeMessagesAndClosesAfter()
+ {
+ await DoCommitTransactedSessionWithConsumerTestImpl(5, 2, true, false);
+ }
+
+ private async Task DoCommitTransactedSessionWithConsumerTestImpl(int transferCount, int consumeCount, bool closeConsumer, bool closeBeforeCommit)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), transferCount);
+
+ for (int i = 1; i <= consumeCount; i++)
+ {
+ // Then expect a *settled* TransactionalState disposition for each message once received by the consumer
+ testPeer.ExpectDisposition(settled: true, stateMatcher: state =>
+ {
+ Assert.IsInstanceOf<TransactionalState>(state);
+ var transactionalState = (TransactionalState) state;
+ Assert.AreEqual(txnId, transactionalState.TxnId);
+ Assert.IsInstanceOf<Accepted>(transactionalState.Outcome);
+ });
+ }
+
+ IMessageConsumer messageConsumer = await session.CreateConsumerAsync(queue);
+
+ for (int i = 1; i <= consumeCount; i++)
+ {
+ IMessage receivedMessage = await messageConsumer.ReceiveAsync(TimeSpan.FromSeconds(3));
+ Assert.NotNull(receivedMessage);
+ Assert.IsInstanceOf<ITextMessage>(receivedMessage);
+ }
+
+ // Expect the consumer to close now
+ if (closeConsumer && closeBeforeCommit)
+ {
+ // Expect the client to then drain off all credit from the link.
+ testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true);
+
+ // Expect the messages that were not consumed to be released
+ int unconsumed = transferCount - consumeCount;
+ for (int i = 1; i <= unconsumed; i++)
+ {
+ testPeer.ExpectDispositionThatIsReleasedAndSettled();
+ }
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the commit succeeded
+ testPeer.ExpectDischarge(txnId, dischargeState: false);
+
+ // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ testPeer.ExpectDeclare(txnId);
+
+ // Now the deferred close should be performed.
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ await messageConsumer.CloseAsync();
+ }
+ else
+ {
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the commit succeeded
+ testPeer.ExpectDischarge(txnId, dischargeState: false);
+
+ // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ testPeer.ExpectDeclare(txnId);
+ }
+
+ await session.CommitAsync();
+
+ if (closeConsumer && !closeBeforeCommit)
+ {
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ // Expect the messages that were not consumed to be released
+ int unconsumed = transferCount - consumeCount;
+ for (int i = 1; i <= unconsumed; i++)
+ {
+ testPeer.ExpectDispositionThatIsReleasedAndSettled();
+ }
+
+ await messageConsumer.CloseAsync();
+ }
+
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+ testPeer.ExpectClose();
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerWithNoMessageCanCloseBeforeCommit()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+
+ // TODO: qpid-jms extend 2 additional flow links
+ // 1) Drain related with deferred consumer close, this feature is currently
+ // not implemented.
+ // 2) Consumer pull - not implemented
+ // testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true);
+ // testPeer.ExpectLinkFlow(drain: false, sendDrainFlowResponse: false);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ IMessageConsumer messageConsumer = await session.CreateConsumerAsync(queue);
+ Assert.IsNull(messageConsumer.ReceiveNoWait());
+
+ await messageConsumer.CloseAsync();
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the commit succeeded
+ testPeer.ExpectDischarge(txnId, dischargeState: false);
+
+ // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ testPeer.ExpectDeclare(txnId);
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ await session.CommitAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerWithNoMessageCanCloseBeforeRollback()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlow();
+
+ // TODO: qpid-jms extend 2 additional flow links
+ // 1) Drain related with deferred consumer close, this feature is currently
+ // not implemented.
+ // 2) Consumer pull - not implemented
+ // testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true);
+ // testPeer.ExpectLinkFlow(drain: false, sendDrainFlowResponse: false);
+
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ IMessageConsumer messageConsumer = await session.CreateConsumerAsync(queue);
+ Assert.IsNull(messageConsumer.ReceiveNoWait());
+
+ await messageConsumer.CloseAsync();
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the commit succeeded
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ testPeer.ExpectDeclare(txnId);
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ await session.RollbackAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestProducedMessagesOnTransactedSessionCarryTxnId()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Create a producer to use in provoking creation of the AMQP transaction
+ testPeer.ExpectSenderAttach();
+
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Expect the message which was sent under the current transaction. Check it carries
+ // TransactionalState with the above txnId but has no outcome. Respond with a
+ // TransactionalState with Accepted outcome.
+ testPeer.ExpectTransfer(messageMatcher: Assert.NotNull,
+ stateMatcher: state =>
+ {
+ Assert.IsInstanceOf<TransactionalState>(state);
+ TransactionalState transactionalState = (TransactionalState) state;
+ CollectionAssert.AreEqual(txnId, transactionalState.TxnId);
+ Assert.IsNull(transactionalState.Outcome);
+ },
+ responseState: new TransactionalState() { TxnId = txnId, Outcome = new Accepted() },
+ responseSettled: true);
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ await producer.SendAsync(await session.CreateMessageAsync());
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestProducedMessagesOnTransactedSessionCanBeReused()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Create a producer to use in provoking creation of the AMQP transaction
+ testPeer.ExpectSenderAttach();
+
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ // Expect the message which was sent under the current transaction. Check it carries
+ // TransactionalState with the above txnId but has no outcome. Respond with a
+ // TransactionalState with Accepted outcome.
+ IMessage message = await session.CreateMessageAsync();
+ for (int i = 0; i < 3; i++)
+ {
+ testPeer.ExpectTransfer(messageMatcher: Assert.NotNull,
+ stateMatcher: state =>
+ {
+ Assert.IsInstanceOf<TransactionalState>(state);
+ TransactionalState transactionalState = (TransactionalState) state;
+ CollectionAssert.AreEqual(txnId, transactionalState.TxnId);
+ Assert.IsNull(transactionalState.Outcome);
+ },
+ responseState: new TransactionalState() { TxnId = txnId, Outcome = new Accepted() },
+ responseSettled: true);
+
+ message.Properties.SetInt("sequence", i);
+
+ await producer.SendAsync(message);
+ }
+
+ // Expect rollback on close without a commit call.
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+ testPeer.ExpectClose();
+
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRollbackTransactedSessionWithConsumerReceivingAllMessages()
+ {
+ await DoRollbackTransactedSessionWithConsumerTestImpl(1, 1, false);
+ }
+
+ [Test, Timeout(20_000), Ignore("Until deferred close is implemented for AmqpConsumer")]
+ public async Task TestRollbackTransactedSessionWithConsumerReceivingAllMessagesThenCloses()
+ {
+ await DoRollbackTransactedSessionWithConsumerTestImpl(1, 1, true);
+ }
+
+ [Test, Timeout(20_000), Ignore("TODO: Fix")]
+ public async Task TestRollbackTransactedSessionWithConsumerReceivingSomeMessages()
+ {
+ await DoRollbackTransactedSessionWithConsumerTestImpl(5, 2, false);
+ }
+
+ [Test, Timeout(20_000), Ignore("Until deferred close is implemented for AmqpConsumer")]
+ public async Task TestRollbackTransactedSessionWithConsumerReceivingSomeMessagesThenCloses()
+ {
+ await DoRollbackTransactedSessionWithConsumerTestImpl(5, 2, true);
+ }
+
+ private async Task DoRollbackTransactedSessionWithConsumerTestImpl(int transferCount, int consumeCount, bool closeConsumer)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), transferCount);
+
+ for (int i = 1; i <= consumeCount; i++)
+ {
+ // Then expect a *settled* TransactionalState disposition for each message once received by the consumer
+ testPeer.ExpectDisposition(settled: true, stateMatcher: state =>
+ {
+ Assert.IsInstanceOf<TransactionalState>(state);
+ var transactionalState = (TransactionalState) state;
+ Assert.AreEqual(txnId, transactionalState.TxnId);
+ Assert.IsInstanceOf<Accepted>(transactionalState.Outcome);
+ });
+ }
+
+ IMessageConsumer messageConsumer = await session.CreateConsumerAsync(queue);
+
+ for (int i = 1; i <= consumeCount; i++)
+ {
+ IMessage receivedMessage = await messageConsumer.ReceiveAsync(TimeSpan.FromSeconds(3));
+ Assert.IsNotNull(receivedMessage);
+ Assert.IsInstanceOf<ITextMessage>(receivedMessage);
+ }
+
+ // Expect the consumer to be 'stopped' prior to rollback by issuing a 'drain'
+ testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: true, creditMatcher: c => Assert.AreEqual(0, c));
+
+ if (closeConsumer)
+ {
+ // Expect the messages that were not consumed to be released
+ int unconsumed = transferCount - consumeCount;
+ for (int i = 1; i <= unconsumed; i++)
+ {
+ testPeer.ExpectDispositionThatIsReleasedAndSettled();
+ }
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the commit succeeded
+ testPeer.ExpectDischarge(txnId, dischargeState: false);
+
+ // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ testPeer.ExpectDeclare(txnId);
+
+ // Now the deferred close should be performed.
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: true, replyClosed: true);
+
+ await messageConsumer.CloseAsync();
+ }
+ else
+ {
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the rollback succeeded
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ // Then expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ testPeer.ExpectDeclare(txnId);
+
+ // Expect the messages that were not consumed to be released
+ int unconsumed = transferCount - consumeCount;
+ for (int i = 1; i <= unconsumed; i++)
+ {
+ testPeer.ExpectDispositionThatIsReleasedAndSettled();
+ }
+
+ // Expect the consumer to be 'started' again as rollback completes
+ testPeer.ExpectLinkFlow(drain: false, sendDrainFlowResponse: false, creditMatcher: c => Assert.Greater(c, 0));
+ }
+
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+ await session.RollbackAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ // TODO:
+ // TestRollbackTransactedSessionWithPrefetchFullBeforeStoppingConsumer
+ // TestRollbackTransactedSessionWithPrefetchFullyUtilisedByDrainWhenStoppingConsumer
+
+ [Test, Timeout(20_000)]
+ public async Task TestDefaultOutcomeIsModifiedForConsumerSourceOnTransactedSession()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ string queueName = "myQueue";
+ IQueue queue = await session.GetQueueAsync(queueName);
+
+ testPeer.ExpectReceiverAttach(linkNameMatcher: Assert.IsNotNull, targetMatcher: Assert.IsNotNull, sourceMatcher: source =>
+ {
+ Assert.AreEqual(queueName, source.Address);
+ Assert.IsFalse(source.Dynamic);
+ CollectionAssert.Contains(source.Outcomes, SymbolUtil.ATTACH_OUTCOME_ACCEPTED);
+ CollectionAssert.Contains(source.Outcomes, SymbolUtil.ATTACH_OUTCOME_REJECTED);
+ CollectionAssert.Contains(source.Outcomes, SymbolUtil.ATTACH_OUTCOME_RELEASED);
+ CollectionAssert.Contains(source.Outcomes, SymbolUtil.ATTACH_OUTCOME_MODIFIED);
+
+ Assert.IsInstanceOf<Modified>(source.DefaultOutcome);
+ Modified modified = (Modified) source.DefaultOutcome;
+ Assert.IsTrue(modified.DeliveryFailed);
+ Assert.IsFalse(modified.UndeliverableHere);
+ });
+
+ testPeer.ExpectLinkFlow();
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+ await session.CreateConsumerAsync(queue);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestCoordinatorLinkSupportedOutcomes()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach(sourceMatcher: s =>
+ {
+ Source source = (Source) s;
+ CollectionAssert.Contains(source.Outcomes, SymbolUtil.ATTACH_OUTCOME_ACCEPTED);
+ CollectionAssert.Contains(source.Outcomes, SymbolUtil.ATTACH_OUTCOME_REJECTED);
+ CollectionAssert.Contains(source.Outcomes, SymbolUtil.ATTACH_OUTCOME_RELEASED);
+ CollectionAssert.Contains(source.Outcomes, SymbolUtil.ATTACH_OUTCOME_MODIFIED);
+ });
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId);
+
+ await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+
+ //Expect rollback on close
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRollbackErrorCoordinatorClosedOnCommit()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ byte[] txnId1 = { 5, 6, 7, 8 };
+ byte[] txnId2 = { 1, 2, 3, 4 };
+
+ testPeer.ExpectDeclare(txnId1);
+ testPeer.RemotelyCloseLastCoordinatorLinkOnDischarge(txnId: txnId1, dischargeState: false, nextTxnId: txnId2);
+ testPeer.ExpectCoordinatorAttach();
+ testPeer.ExpectDeclare(txnId2);
+ testPeer.ExpectDischarge(txnId2, dischargeState: true);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+
+ Assert.CatchAsync<TransactionRolledBackException>(async () => await session.CommitAsync(), "Transaction should have rolled back");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestRollbackErrorWhenCoordinatorRemotelyClosed()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ byte[] txnId = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId);
+ testPeer.RemotelyCloseLastCoordinatorLink();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectCoordinatorAttach();
+ testPeer.ExpectDeclare(txnId);
+
+ testPeer.ExpectDischarge(txnId, true);
+
+ Assert.CatchAsync<TransactionRolledBackException>(async () => await session.CommitAsync(), "Transaction should have rolled back");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestNMSErrorCoordinatorClosedOnRollback()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ byte[] txnId1 = { 5, 6, 7, 8 };
+ byte[] txnId2 = { 1, 2, 3, 4 };
+
+ testPeer.ExpectDeclare(txnId1);
+ testPeer.RemotelyCloseLastCoordinatorLinkOnDischarge(txnId: txnId1, dischargeState: true, nextTxnId: txnId2);
+ testPeer.ExpectCoordinatorAttach();
+ testPeer.ExpectDeclare(txnId2);
+ testPeer.ExpectDischarge(txnId2, dischargeState: true);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+
+ Assert.CatchAsync<NMSException>(async () => await session.RollbackAsync(), "Rollback should have thrown a NMSException");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestNMSExceptionOnRollbackWhenCoordinatorRemotelyClosed()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ byte[] txnId = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId);
+ testPeer.RemotelyCloseLastCoordinatorLink();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ testPeer.ExpectCoordinatorAttach();
+ testPeer.ExpectDeclare(txnId);
+
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ Assert.CatchAsync<NMSException>(async () => await session.RollbackAsync(), "Rollback should have thrown a NMSException");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSendAfterCoordinatorLinkClosedDuringTX()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a Declared disposition state containing the txnId.
+ byte[] txnId = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Create a producer to use in provoking creation of the AMQP transaction
+ testPeer.ExpectSenderAttach();
+
+ // Close the link, the messages should now just get dropped on the floor.
+ testPeer.RemotelyCloseLastCoordinatorLink();
+
+ IMessageProducer producer = await session.CreateProducerAsync(queue);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ await producer.SendAsync(await session.CreateMessageAsync());
+
+ // Expect that a new link will be created in order to start the next TX.
+ txnId = new byte[] { 1, 2, 3, 4 };
+ testPeer.ExpectCoordinatorAttach();
+ testPeer.ExpectDeclare(txnId);
+
+ // Expect that the session TX will rollback on close.
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ Assert.CatchAsync<TransactionRolledBackException>(async () => await session.CommitAsync(), "Commit operation should have failed.");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestReceiveAfterCoordinatorLinkClosedDuringTX()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a Declared disposition state containing the txnId.
+ byte[] txnId = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Create a consumer and send it an initial message for receive to process.
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent());
+
+ // Close the link, the messages should now just get dropped on the floor.
+ testPeer.RemotelyCloseLastCoordinatorLink();
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+
+ // receiving the message would normally ack it, since the TX is failed this
+ // should not result in a disposition going out.
+ IMessage received = await consumer.ReceiveAsync();
+ Assert.IsNotNull(received);
+
+ // Expect that a new link will be created in order to start the next TX.
+ txnId = new byte[] { 1, 2, 3, 4 };
+ testPeer.ExpectCoordinatorAttach();
+ testPeer.ExpectDeclare(txnId);
+
+ // Expect that the session TX will rollback on close.
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ Assert.CatchAsync<TransactionRolledBackException>(async () => await session.CommitAsync(), "Commit operation should have failed.");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSessionCreateFailsOnDeclareTimeout()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, "nms.requestTimeout=500");
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+ testPeer.ExpectDeclareButDoNotRespond();
+
+ // Expect the AMQP session to be closed due to the NMS session creation failure.
+ testPeer.ExpectEnd();
+
+ // TODO: Replace NMSException with sth more specific, in qpid-jms it is JmsOperationTimedOutException
+ Assert.CatchAsync<NMSException>(async () => await connection.CreateSessionAsync(AcknowledgementMode.Transactional), "Should have timed out waiting for declare.");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSessionCreateFailsOnDeclareRejection()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, "nms.closeTimeout=100");
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a Rejected disposition state to indicate failure.
+ testPeer.ExpectDeclareAndReject();
+
+ // Expect the AMQP session to be closed due to the NMS session creation failure.
+ testPeer.ExpectEnd();
+
+ Assert.CatchAsync<NMSException>(async () => await connection.CreateSessionAsync(AcknowledgementMode.Transactional));
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestSessionCreateFailsOnCoordinatorLinkRefusal()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, "nms.closeTimeout=100");
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+
+ // Expect coordinator link, refuse it, expect detach reply
+ string errorMessage = "CoordinatorLinkRefusal-breadcrumb";
+ testPeer.ExpectCoordinatorAttach(refuseLink: true, error: new Error(ErrorCode.NotImplemented) { Description = errorMessage });
+ testPeer.ExpectDetach(expectClosed: true, sendResponse: false, replyClosed: false);
+
+ // Expect the AMQP session to be closed due to the NMS session creation failure.
+ testPeer.ExpectEnd();
+
+ NMSException exception = Assert.CatchAsync<NMSException>(async () => await connection.CreateSessionAsync(AcknowledgementMode.Transactional));
+ Assert.IsTrue(exception.Message.Contains(errorMessage), "Expected exception message to contain breadcrumb");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestTransactionRolledBackOnSessionCloseTimesOut()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, "nms.requestTimeout=500");
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ byte[] txnId = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId);
+
+ // Closed session should roll-back the TX with a failed discharge
+ testPeer.ExpectDischargeButDoNotRespond(txnId, dischargeState: true);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+
+ // TODO: Replace NMSException with sth more specific, in qpid-jms it is JmsOperationTimedOutException
+ Assert.CatchAsync<NMSException>(async () => await session.CloseAsync(), "Should have timed out waiting for discharge.");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestTransactionRolledBackTimesOut()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, "nms.requestTimeout=500");
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ byte[] txnId1 = { 5, 6, 7, 8 };
+ byte[] txnId2 = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId1);
+
+ // Expect discharge but don't respond so that the request timeout kicks in and fails
+ // the discharge. The pipelined declare should arrive as well and be discharged as the
+ // client attempts to recover to a known good state.
+ testPeer.ExpectDischargeButDoNotRespond(txnId1, dischargeState: true);
+
+ // Session should throw from the rollback and then try and recover.
+ testPeer.ExpectDeclare(txnId2);
+ testPeer.ExpectDischarge(txnId2, dischargeState: true);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+
+ // TODO: Replace NMSException with sth more specific, in qpid-jms it is JmsOperationTimedOutException
+ Assert.CatchAsync<NMSException>(async () => await session.RollbackAsync(), "Should have timed out waiting for discharge.");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestTransactionCommitTimesOut()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, "nms.requestTimeout=500");
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ byte[] txnId1 = { 5, 6, 7, 8 };
+ byte[] txnId2 = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId1);
+
+ // Expect discharge but don't respond so that the request timeout kicks in and fails
+ // the discharge. The pipelined declare should arrive as well and be discharged as the
+ // client attempts to recover to a known good state.
+ testPeer.ExpectDischargeButDoNotRespond(txnId1, dischargeState: false);
+ testPeer.ExpectDeclare(txnId2);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+
+ // TODO: Replace NMSException with sth more specific, in qpid-jms it is JmsOperationTimedOutException
+ Assert.CatchAsync<NMSException>(async () => await session.CommitAsync(), "Should have timed out waiting for discharge.");
+
+ // Session rolls back on close
+ testPeer.ExpectDischarge(txnId2, dischargeState: true);
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000), Ignore("TODO: Fix")]
+ public async Task TestTransactionCommitTimesOutAndNoNextBeginTimesOut()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer, "nms.requestTimeout=500");
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ byte[] txnId1 = { 5, 6, 7, 8 };
+ byte[] txnId2 = { 1, 2, 3, 4 };
+ testPeer.ExpectDeclare(txnId1);
+
+ // Expect discharge and don't respond so that the request timeout kicks in
+ // Expect pipelined declare and don't response so that the request timeout kicks in.
+ // The commit operation should throw a timed out exception at that point.
+ testPeer.ExpectDischargeButDoNotRespond(txnId1, dischargeState: false);
+ testPeer.ExpectDeclareButDoNotRespond();
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+
+ // After the pipelined operations both time out, the session should attempt to
+ // recover by creating a new TX, then on close the session should roll it back
+ testPeer.ExpectDeclare(txnId2);
+ testPeer.ExpectDischarge(txnId2, dischargeState: true);
+
+ // TODO: Replace NMSException with sth more specific, in qpid-jms it is JmsOperationTimedOutException
+ Assert.CatchAsync<NMSException>(async () => await session.CommitAsync(), "Should have timed out waiting for discharge.");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000), Ignore("TODO: Fix")]
+ public async Task TestRollbackWithNoResponseForSuspendConsumer()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithContent(), count: 1);
+
+ // Then expect a *settled* TransactionalState disposition for the message once received by the consumer
+ testPeer.ExpectDisposition(settled: true, state =>
+ {
+ var transactionalState = (TransactionalState) state;
+ CollectionAssert.AreEqual(txnId, transactionalState.TxnId);
+ Assert.IsInstanceOf<Accepted>(transactionalState.Outcome);
+ });
+
+ // Read one so we try to suspend on rollback
+ IMessageConsumer messageConsumer = await session.CreateConsumerAsync(queue);
+ IMessage receivedMessage = await messageConsumer.ReceiveAsync(TimeSpan.FromSeconds(3));
+
+ Assert.NotNull(receivedMessage);
+ Assert.IsInstanceOf<ITextMessage>(receivedMessage);
+
+ // Expect the consumer to be 'stopped' prior to rollback by issuing a 'drain'
+ testPeer.ExpectLinkFlow(drain: true, sendDrainFlowResponse: false);
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the rollback succeeded
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ testPeer.ExpectDeclare(txnId);
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+
+ Assert.CatchAsync<NMSException>(async () => await session.RollbackAsync(), "Should throw a timed out exception");
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(1000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumerMessageOrderOnTransactedSession()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ int messageCount = 10;
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = { 5, 6, 7, 8 };
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Expect the browser enumeration to create a underlying consumer
+ testPeer.ExpectReceiverAttach();
+
+ // Expect initial credit to be sent, respond with some messages that are tagged with
+ // a sequence number we can use to determine if order is maintained.
+ testPeer.ExpectLinkFlowRespondWithTransfer(CreateMessageWithNullContent(), count: messageCount, addMessageNumberProperty: true);
+
+ for (int i = 1; i <= messageCount; i++)
+ {
+ // Then expect an *settled* TransactionalState disposition for each message once received by the consumer
+ testPeer.ExpectSettledTransactionalDisposition(txnId);
+ }
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+ for (int i = 0; i < messageCount; i++)
+ {
+ IMessage message = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(500));
+ Assert.IsNotNull(message);
+ Assert.AreEqual(i, message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER));
+ }
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the rollback succeeded
+ testPeer.ExpectDischarge(txnId, true);
+ testPeer.ExpectEnd();
+
+ await session.CloseAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+
+ [Test, Timeout(20_000)]
+ public async Task TestConsumeManyWithSingleTXPerMessage()
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ IConnection connection = await EstablishConnectionAsync(testPeer);
+ await connection.StartAsync();
+
+ int messageCount = 10;
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectCoordinatorAttach();
+
+ var txnIdQueue = new Queue<byte[]>(3);
+ txnIdQueue.Enqueue(new byte[] { 1, 2, 3, 4 });
+ txnIdQueue.Enqueue(new byte[] { 2, 4, 6, 8 });
+ txnIdQueue.Enqueue(new byte[] { 5, 4, 3, 2 });
+
+ // First expect an unsettled 'declare' transfer to the txn coordinator, and
+ // reply with a declared disposition state containing the txnId.
+ byte[] txnId = txnIdQueue.Dequeue();
+ txnIdQueue.Enqueue(txnId);
+ testPeer.ExpectDeclare(txnId);
+
+ ISession session = await connection.CreateSessionAsync(AcknowledgementMode.Transactional);
+ IQueue queue = await session.GetQueueAsync("myQueue");
+
+ // Expect the browser enumeration to create a underlying consumer
+ testPeer.ExpectReceiverAttach();
+
+ // Expect initial credit to be sent, respond with some messages that are tagged with
+ // a sequence number we can use to determine if order is maintained.
+ testPeer.ExpectLinkFlowRespondWithTransfer(message: CreateMessageWithNullContent(), count: messageCount, addMessageNumberProperty: true);
+
+ IMessageConsumer consumer = await session.CreateConsumerAsync(queue);
+
+ for (int i = 0; i < messageCount; i++)
+ {
+ // Then expect an *settled* TransactionalState disposition for each message once received by the consumer
+ testPeer.ExpectSettledTransactionalDisposition(txnId);
+
+ IMessage message = await consumer.ReceiveAsync(TimeSpan.FromMilliseconds(500));
+ Assert.NotNull(message);
+ Assert.AreEqual(i, message.Properties.GetInt(TestAmqpPeer.MESSAGE_NUMBER));
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the commit succeeded
+ testPeer.ExpectDischarge(txnId, dischargeState: false);
+
+ // Expect the next transaction to start.
+ txnId = txnIdQueue.Dequeue();
+ txnIdQueue.Enqueue(txnId);
+ testPeer.ExpectDeclare(txnId);
+
+ await session.CommitAsync();
+ }
+
+ // Expect an unsettled 'discharge' transfer to the txn coordinator containing the txnId,
+ // and reply with accepted and settled disposition to indicate the rollback succeeded
+ testPeer.ExpectDischarge(txnId, dischargeState: true);
+ testPeer.ExpectEnd();
+
+ await session.CloseAsync();
+
+ testPeer.ExpectClose();
+ await connection.CloseAsync();
+
+ testPeer.WaitForAllMatchersToComplete(3000);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Integration/ConsumerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/ConsumerIntegrationTest.cs
index cdb25e6..f42ba51 100644
--- a/test/Apache-NMS-AMQP-Test/Integration/ConsumerIntegrationTest.cs
+++ b/test/Apache-NMS-AMQP-Test/Integration/ConsumerIntegrationTest.cs
@@ -459,6 +459,8 @@
testPeer.WaitForAllMatchersToComplete(2000);
}
}
+
+
[Test, Timeout(20_000)]
public void TestCreateProducerInOnMessage()
diff --git a/test/Apache-NMS-AMQP-Test/Integration/IntegrationTestFixture.cs b/test/Apache-NMS-AMQP-Test/Integration/IntegrationTestFixture.cs
index fac9499..52af9af 100644
--- a/test/Apache-NMS-AMQP-Test/Integration/IntegrationTestFixture.cs
+++ b/test/Apache-NMS-AMQP-Test/Integration/IntegrationTestFixture.cs
@@ -15,10 +15,12 @@
* limitations under the License.
*/
+using System.Threading.Tasks;
using Amqp.Framing;
using Amqp.Types;
using Apache.NMS;
using Apache.NMS.AMQP;
+using Apache.NMS.AMQP.Util.Synchronization;
using NMS.AMQP.Test.TestAmqp;
namespace NMS.AMQP.Test.Integration
@@ -29,9 +31,14 @@
{
Tracer.Trace = new NLogAdapter();
}
-
+
protected IConnection EstablishConnection(TestAmqpPeer testPeer, string optionsString = null, Symbol[] serverCapabilities = null, Fields serverProperties = null, bool setClientId = true)
{
+ return EstablishConnectionAsync(testPeer, optionsString, serverCapabilities, serverProperties, setClientId).GetAsyncResult();
+ }
+
+ protected async Task<IConnection> EstablishConnectionAsync(TestAmqpPeer testPeer, string optionsString = null, Symbol[] serverCapabilities = null, Fields serverProperties = null, bool setClientId = true)
+ {
testPeer.ExpectSaslPlain("guest", "guest");
testPeer.ExpectOpen(serverCapabilities: serverCapabilities, serverProperties: serverProperties);
@@ -40,7 +47,7 @@
var remoteUri = BuildUri(testPeer, optionsString);
var connectionFactory = new NmsConnectionFactory(remoteUri);
- var connection = connectionFactory.CreateConnection("guest", "guest");
+ var connection = await connectionFactory.CreateConnectionAsync("guest", "guest");
if (setClientId)
{
// Set a clientId to provoke the actual AMQP connection process to occur.
@@ -52,6 +59,11 @@
protected INMSContext EstablishNMSContext(TestAmqpPeer testPeer, string optionsString = null, Symbol[] serverCapabilities = null, Fields serverProperties = null, bool setClientId = true, AcknowledgementMode acknowledgementMode = AcknowledgementMode.AutoAcknowledge)
{
+ return EstablishNMSContextAsync(testPeer, optionsString, serverCapabilities, serverProperties, setClientId, acknowledgementMode).GetAsyncResult();
+ }
+
+ protected async Task<INMSContext> EstablishNMSContextAsync(TestAmqpPeer testPeer, string optionsString = null, Symbol[] serverCapabilities = null, Fields serverProperties = null, bool setClientId = true, AcknowledgementMode acknowledgementMode = AcknowledgementMode.AutoAcknowledge)
+ {
testPeer.ExpectSaslPlain("guest", "guest");
testPeer.ExpectOpen(serverCapabilities: serverCapabilities, serverProperties: serverProperties);
@@ -60,7 +72,7 @@
var remoteUri = BuildUri(testPeer, optionsString);
var connectionFactory = new NmsConnectionFactory(remoteUri);
- var context = connectionFactory.CreateContext("guest", "guest", acknowledgementMode);
+ var context = await connectionFactory.CreateContextAsync("guest", "guest", acknowledgementMode);
if (setClientId)
{
// Set a clientId to provoke the actual AMQP connection process to occur.
@@ -83,12 +95,17 @@
return baseUri + "?" + optionsString;
}
-
+
protected static Amqp.Message CreateMessageWithContent()
{
return new Amqp.Message() { BodySection = new AmqpValue() { Value = "content" } };
}
+ protected static Amqp.Message CreateMessageWithValueContent(object value)
+ {
+ return new Amqp.Message() { BodySection = new AmqpValue() { Value = value } };
+ }
+
protected static Amqp.Message CreateMessageWithNullContent()
{
return new Amqp.Message() { BodySection = new AmqpValue() { Value = null } };
diff --git a/test/Apache-NMS-AMQP-Test/Integration/MessageDeliveryTimeTest.cs b/test/Apache-NMS-AMQP-Test/Integration/MessageDeliveryTimeTest.cs
index dbe936a..6c85757 100644
--- a/test/Apache-NMS-AMQP-Test/Integration/MessageDeliveryTimeTest.cs
+++ b/test/Apache-NMS-AMQP-Test/Integration/MessageDeliveryTimeTest.cs
@@ -78,7 +78,7 @@
{
using (TestAmqpPeer testPeer = new TestAmqpPeer())
{
- var connection = EstablishConnection(testPeer, "amqp.traceFrames=true");
+ var connection = EstablishConnection(testPeer);
connection.Start();
testPeer.ExpectBegin();
var session = connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
diff --git a/test/Apache-NMS-AMQP-Test/Integration/NMSConsumerIntegrationTest.cs b/test/Apache-NMS-AMQP-Test/Integration/NMSConsumerIntegrationTest.cs
index c1bb33b..542f720 100644
--- a/test/Apache-NMS-AMQP-Test/Integration/NMSConsumerIntegrationTest.cs
+++ b/test/Apache-NMS-AMQP-Test/Integration/NMSConsumerIntegrationTest.cs
@@ -16,10 +16,12 @@
*/
using System;
+using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Amqp.Framing;
+using Amqp.Types;
using Apache.NMS;
using Apache.NMS.AMQP.Message;
using Apache.NMS.AMQP.Util;
@@ -72,7 +74,7 @@
// .Setup(listener => listener.OnConsumerClosed(It.IsAny<IMessageConsumer>(), It.IsAny<Exception>()))
// .Callback(() => consumerClosed.Set());
//
- // var context = (NmsContext) EstablishNMSContext(testPeer, "amqp.traceFrames=true");
+ // var context = (NmsContext) EstablishNMSContext(testPeer);
// context.ConnectionInterruptedListener += () => { consumerClosed.Set(); };// AddConnectionListener(mockConnectionListener.Object);}
// // context.list ConnectionInterruptedListener += () => { consumerClosed.Set(); };// AddConnectionListener(mockConnectionListener.Object);}
// context.ExceptionListener += exception => { exceptionFired.Set(); };
@@ -971,5 +973,91 @@
testPeer.WaitForAllMatchersToComplete(2000);
}
}
+
+ [TestCaseSource("TestReceiveBodyCaseSource")]
+ [Timeout(20_000)]
+ public void TestReceiveBody<T>(T inputValue)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = EstablishNMSContext(testPeer);
+
+ testPeer.ExpectBegin();
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(CreateMessageWithValueContent(inputValue));
+ testPeer.ExpectDisposition(true, _ => { } );
+
+
+ IQueue destination = context.GetQueue("myQueue");
+ var consumer = context.CreateConsumer(destination);
+
+ T body = consumer.ReceiveBody<T>();
+ Assert.AreEqual(inputValue, body);
+ Assert.AreNotSame(inputValue, body);
+
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ context.Close();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ [TestCaseSource("TestReceiveBodyCaseSource")]
+ [Timeout(20_000)]
+ public void TestReceiveBodyNoWait<T>(T inputValue)
+ {
+ using (TestAmqpPeer testPeer = new TestAmqpPeer())
+ {
+ var context = EstablishNMSContext(testPeer);
+
+ ManualResetEvent beforeFlow = new ManualResetEvent(false);
+ testPeer.ExpectBegin();
+ testPeer.ExpectReceiverAttach();
+ testPeer.ExpectLinkFlowRespondWithTransfer(CreateMessageWithValueContent(inputValue), 1, false, false, false, false,
+ (credit) => { beforeFlow.WaitOne(); }, 1);
+ testPeer.ExpectDisposition(true, _ => { } );
+
+ IQueue destination = context.GetQueue("myQueue");
+ var consumer = context.CreateConsumer(destination);
+
+ T initialBody = consumer.ReceiveBodyNoWait<T>();
+ // Assert initially its null
+ Assert.AreEqual(default(T), initialBody);
+
+ // Release and allow link to flow
+ beforeFlow.Set();
+ // Give short time to arrive
+ Thread.Sleep(100);
+
+ T body = consumer.ReceiveBodyNoWait<T>();
+ Assert.AreEqual(inputValue, body);
+ Assert.AreNotSame(inputValue, body);
+
+
+ testPeer.ExpectEnd();
+ testPeer.ExpectClose();
+
+ context.Close();
+
+ testPeer.WaitForAllMatchersToComplete(2000);
+ }
+ }
+
+ public static IEnumerable<object> TestReceiveBodyCaseSource()
+ {
+ yield return new Map()
+ {
+ ["Parameter1"] = "test",
+ ["Parameter2"] = 23423
+ };
+ yield return 1233;
+ yield return "test";
+ yield return (uint) 1233;
+ yield return (ulong) 1233;
+ yield return (long) -1233;
+ }
}
}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Message/Facade/AmqpNmsMessageFacadeTest.cs b/test/Apache-NMS-AMQP-Test/Message/Facade/AmqpNmsMessageFacadeTest.cs
new file mode 100644
index 0000000..4aef2b4
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Message/Facade/AmqpNmsMessageFacadeTest.cs
@@ -0,0 +1,29 @@
+using System;
+using Apache.NMS.AMQP.Message;
+using Apache.NMS.AMQP.Provider.Amqp;
+using Apache.NMS.AMQP.Provider.Amqp.Message;
+using Apache.NMS.AMQP.Util;
+using Apache.NMS.Util;
+using Moq;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Message.Facade
+{
+ [TestFixture]
+ public class AmqpNmsMessageFacadeTest
+ {
+ [Test]
+ public void TestNMSDeliveryTime_Set_ShouldHaveMessageAnnotations()
+ {
+ AmqpNmsMessageFacade msg = new AmqpNmsMessageFacade();
+
+ Mock<IAmqpConnection> mockAmqpConnection = new Mock<IAmqpConnection>();
+
+ msg.Initialize(mockAmqpConnection.Object);
+ var deliveryTime = DateTime.UtcNow.AddMinutes(2);
+ msg.DeliveryTime = deliveryTime;
+
+ Assert.AreEqual(new DateTimeOffset(deliveryTime).ToUnixTimeMilliseconds(), msg.MessageAnnotations[SymbolUtil.NMS_DELIVERY_TIME]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Apache-NMS-AMQP-Test/Message/Foreign/ForeignNmsMessage.cs b/test/Apache-NMS-AMQP-Test/Message/Foreign/ForeignNmsMessage.cs
index e9f0c38..d25eaed 100644
--- a/test/Apache-NMS-AMQP-Test/Message/Foreign/ForeignNmsMessage.cs
+++ b/test/Apache-NMS-AMQP-Test/Message/Foreign/ForeignNmsMessage.cs
@@ -16,6 +16,7 @@
*/
using System;
+using System.Threading.Tasks;
using Apache.NMS;
using Apache.NMS.AMQP.Message;
using NMS.AMQP.Test.Message.Facade;
@@ -31,6 +32,11 @@
message.Acknowledge();
}
+ public Task AcknowledgeAsync()
+ {
+ return message.AcknowledgeAsync();
+ }
+
public void ClearBody()
{
message.ClearBody();
diff --git a/test/Apache-NMS-AMQP-Test/Provider/Mock/MockProvider.cs b/test/Apache-NMS-AMQP-Test/Provider/Mock/MockProvider.cs
index 9baccff..d6c26b6 100644
--- a/test/Apache-NMS-AMQP-Test/Provider/Mock/MockProvider.cs
+++ b/test/Apache-NMS-AMQP-Test/Provider/Mock/MockProvider.cs
@@ -72,6 +72,11 @@
}
}
+ public async Task CloseAsync()
+ {
+ Close();
+ }
+
public void SetProviderListener(IProviderListener providerListener)
{
listener = providerListener;
diff --git a/test/Apache-NMS-AMQP-Test/TestAmqp/TestAmqpPeerRunner.cs b/test/Apache-NMS-AMQP-Test/TestAmqp/TestAmqpPeerRunner.cs
index 21f0687..e0bb031 100644
--- a/test/Apache-NMS-AMQP-Test/TestAmqp/TestAmqpPeerRunner.cs
+++ b/test/Apache-NMS-AMQP-Test/TestAmqp/TestAmqpPeerRunner.cs
@@ -38,6 +38,8 @@
private SocketAsyncEventArgs args;
private Socket acceptSocket;
+ private bool pumpEnabled = true;
+
public TestAmqpPeerRunner(TestAmqpPeer testAmqpPeer, IPEndPoint ipEndPoint)
{
this.testAmqpPeer = testAmqpPeer;
@@ -107,13 +109,13 @@
{
try
{
- while (true)
+ while (pumpEnabled)
{
byte[] buffer = new byte[8];
Read(stream, buffer, 0, 8);
testAmqpPeer.OnHeader(stream, buffer);
- while (true)
+ while (pumpEnabled)
{
Read(stream, buffer, 0, 4);
int len = AmqpBitConverter.ReadInt(buffer, 0);
@@ -173,6 +175,8 @@
public void Close()
{
+ pumpEnabled = false;
+
acceptSocket?.Dispose();
acceptSocket = null;
diff --git a/test/Apache-NMS-AMQP-Test/Utils/Synchronization/NmsSynchronizationMonitorTest.cs b/test/Apache-NMS-AMQP-Test/Utils/Synchronization/NmsSynchronizationMonitorTest.cs
new file mode 100644
index 0000000..6ac8e66
--- /dev/null
+++ b/test/Apache-NMS-AMQP-Test/Utils/Synchronization/NmsSynchronizationMonitorTest.cs
@@ -0,0 +1,437 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Apache.NMS.AMQP.Util.Synchronization;
+using NUnit.Framework;
+
+namespace NMS.AMQP.Test.Utils.Synchronization
+{
+ [TestFixture]
+ public class NmsSynchronizationMonitorTest
+ {
+ public class EventList
+ {
+ public List<string> list = new List<string>();
+ public Dictionary<string, List<string>> listsByPrefix = new Dictionary<string, List<string>>();
+
+ public void Add(string ev)
+ {
+ lock (this)
+ {
+ list.Add(ev);
+ string prefix = new string(new[] {ev[0]});
+ if (!listsByPrefix.ContainsKey(prefix))
+ {
+ listsByPrefix[prefix] = new List<string>();
+ }
+ listsByPrefix[prefix].Add(ev);
+ }
+ }
+
+ public string ToString(string prefix)
+ {
+ return listsByPrefix.ContainsKey(prefix) ? string.Join("",listsByPrefix[prefix]) : string.Empty;
+ }
+
+ public override string ToString()
+ {
+ return string.Join("", list);
+ }
+ }
+
+
+ [Test]
+ public void TestNestedLockAndWait()
+ {
+ NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
+ ManualResetEvent lockedEvent = new ManualResetEvent(false);
+
+ EventList evList = new EventList();
+
+ var task = Task.Run(() =>
+ {
+ Thread.Sleep(5);
+ using (syncRoot.Lock())
+ {
+ evList.Add("A1");
+ Thread.Sleep(1);
+ evList.Add("A1");
+ Thread.Sleep(1);
+ evList.Add("A1");
+ Thread.Sleep(1);
+
+ using (syncRoot.Lock())
+ {
+ evList.Add("A1");
+ Thread.Sleep(1);
+ evList.Add("A1");
+ Thread.Sleep(1);
+ evList.Add("A1");
+ Thread.Sleep(1);
+
+ lockedEvent.Set();
+ syncRoot.Wait();
+
+ evList.Add("A2");
+ Thread.Sleep(1);
+ evList.Add("A2");
+ Thread.Sleep(1);
+ evList.Add("A2");
+ Thread.Sleep(1);
+ }
+
+ evList.Add("A2");
+ Thread.Sleep(1);
+ evList.Add("A2");
+ Thread.Sleep(1);
+ evList.Add("A2");
+ Thread.Sleep(1);
+ }
+ });
+
+ var taskB = Task.Run(() =>
+ {
+ while (!task.IsCompleted)
+ {
+ using (syncRoot.Lock())
+ {
+ evList.Add("B");
+ Thread.Sleep(1);
+ }
+ }
+ });
+
+ lockedEvent.WaitOne();
+ Thread.Sleep(10); // to give Task A Time to go to sleep and B to work
+
+
+ Task.Run(() => { syncRoot.Pulse(); });
+
+ task.Wait();
+ taskB.Wait();
+
+
+ // Now Asses that block A1 and A2 are not intersected by B, however A1 And A2 should have some B between (during sleep period)
+ var events = evList.list.Select((a, i) => new Tuple<string, int>(a, i));
+ var a1 = events.Where(a => a.Item1 == "A1").ToList();
+ var a2 = events.Where(a => a.Item1 == "A2").ToList();
+ Assert.AreEqual(6, a1.Count());
+ Assert.AreEqual(5, a1.Last().Item2 - a1.First().Item2); // not intersected by anything
+
+ Assert.AreEqual(6, a2.Count());
+ Assert.AreEqual(5, a2.Last().Item2 - a2.First().Item2); // not intersected by anything
+
+ Assert.Greater(Math.Abs(a1.Last().Item2 - a2.First().Item2), 1, "A1 and A2, should be intersected by B, and not happening one right after another");
+ }
+
+ [Test]
+ public void TestNestedLockAndWaitAsync()
+ {
+ NmsSynchronizationMonitor syncRoot = new NmsSynchronizationMonitor();
+ ManualResetEvent lockedEvent = new ManualResetEvent(false);
+
+ EventList evList = new EventList();
+
+ var task = Task.Run(async () =>
+ {
+ Thread.Sleep(5);
+ using (await syncRoot.LockAsync())
+ {
+ evList.Add("A1");
+ await Task.Delay(1);
+ await Task.Yield();
+ evList.Add("A1");
+ await Task.Delay(1);
+ await Task.Yield();
+ evList.Add("A1");
+ await Task.Delay(1);
+ await Task.Yield();
+
+ using (await syncRoot.LockAsync())
+ {
+ evList.Add("A1");
+ await Task.Delay(1);
+ await Task.Yield();
+ evList.Add("A1");
+ await Task.Delay(1);
+ await Task.Yield();
+ evList.Add("A1");
+ await Task.Delay(1);
+ await Task.Yield();
+
+ lockedEvent.Set();
+ await syncRoot.WaitAsync();
+
+ evList.Add("A2");
+ await Task.Delay(1);
+ await Task.Yield();
+ evList.Add("A2");
+ await Task.Delay(1);
+ await Task.Yield();
+ evList.Add("A2");
+ await Task.Delay(1);
+ await Task.Yield();
+ }
+
+ evList.Add("A2");
+ await Task.Delay(1);
+ await Task.Yield();
+ evList.Add("A2");
+ await Task.Delay(1);
+ await Task.Yield();
+ evList.Add("A2");
+ await Task.Delay(1);
+ await Task.Yield();
+ }
+ });
+
+ var taskB = Task.Run(async () =>
+ {
+ while (!task.IsCompleted)
+ {
+ using (await syncRoot.LockAsync())
+ {
+ evList.Add("B");
+ await Task.Delay(1);
+ }
+ }
+ });
+
+ lockedEvent.WaitOne();
+ Thread.Sleep(10); // to give Task A Time to go to sleep and B to work
+
+
+ Task.Run(() => { syncRoot.Pulse(); });
+
+ task.Wait();
+ taskB.Wait();
+
+
+ // Now Asses that block A1 and A2 are not intersected by B, however A1 And A2 should have some B between (during sleep period)
+ var events = evList.list.Select((a, i) => new Tuple<string, int>(a, i));
+ var a1 = events.Where(a => a.Item1 == "A1").ToList();
+ var a2 = events.Where(a => a.Item1 == "A2").ToList();
+ Assert.AreEqual(6, a1.Count());
+ Assert.AreEqual(5, a1.Last().Item2 - a1.First().Item2); // not intersected by anything
+
+ Assert.AreEqual(6, a2.Count());
+ Assert.AreEqual(5, a2.Last().Item2 - a2.First().Item2); // not intersected by anything
+
+ Assert.Greater(Math.Abs(a1.Last().Item2 - a2.First().Item2), 1, "A1 and A2, should be intersected by B, and not happening one right after another");
+ }
+
+
+ [TestCase(1,3500,6)]
+ [TestCase(0,2000,200)]
+ [Timeout(20_000)]
+ public void TestConcurrentProducersSyncAndAsync(int sleepTimeMs, int testTimeMs, int minimumOccurences)
+ {
+ EventList evListCommon = new EventList();
+
+ NmsSynchronizationMonitor syncRootA = new NmsSynchronizationMonitor();
+ NmsSynchronizationMonitor syncRootB = new NmsSynchronizationMonitor();
+ bool runTest = true;
+
+ // int sleepTimeMs = 1;
+
+ var task1 = Task.Run(async () =>
+ {
+ int counter = 0;
+ while (runTest)
+ {
+ var lockA = (counter % 2 == 0) ? syncRootA.Lock() : await syncRootA.LockAsync();
+ using (lockA)
+ {
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add("A1");
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add("A1");
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add("A1");
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+
+ var lockB = (counter % 2 == 0) ? syncRootB.Lock() : await syncRootB.LockAsync();
+ using (lockB)
+ {
+ evListCommon.Add("B1");
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add("B1");
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add("B1");
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ }
+
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add("A1");
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add("A1");
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add("A1");
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ }
+
+ counter++;
+ }
+ });
+
+ var task2 = Task.Run(async () =>
+ {
+ int counter = 0;
+
+ // Also test the reentrancy of lock and mix of async and non async
+ async Task ResourceAccess(string symbol, int level, NmsSynchronizationMonitor syncRoot)
+ {
+ var locked = (counter % 2 == 0) ? syncRoot.Lock() : await syncRoot.LockAsync();
+ using (locked)
+ {
+ int depth = counter % 4;
+ if (level == depth)
+ {
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add(symbol);
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add(symbol);
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ evListCommon.Add(symbol);
+ await Task.Delay(sleepTimeMs);
+ await Task.Yield();
+ }
+ else
+ {
+ await ResourceAccess(symbol, level + 1, syncRoot);
+ }
+ }
+ }
+
+ while (runTest)
+ {
+ await ResourceAccess("A2",0, syncRootA);
+ await ResourceAccess("B2",0,syncRootB);
+
+ counter++;
+ }
+ });
+
+ var task3 = Task.Run(() =>
+ {
+ int counter = 0;
+ while (runTest)
+ {
+ var lockA = (counter % 2 == 0) ? syncRootA.Lock() : syncRootA.LockAsync().GetAsyncResult();
+ using (lockA)
+ {
+ Thread.Sleep(sleepTimeMs);
+ evListCommon.Add("A3");
+ Thread.Sleep(sleepTimeMs);
+ evListCommon.Add("A3");
+ Thread.Sleep(sleepTimeMs);
+ evListCommon.Add("A3");
+ Thread.Sleep(sleepTimeMs);
+ }
+
+ var lockB = (counter % 2 == 0) ? syncRootB.Lock() : syncRootB.LockAsync().GetAsyncResult();
+ using (lockB)
+ {
+ evListCommon.Add("B3");
+ Thread.Sleep(sleepTimeMs);
+ evListCommon.Add("B3");
+ Thread.Sleep(sleepTimeMs);
+ evListCommon.Add("B3");
+ Thread.Sleep(sleepTimeMs);
+ }
+
+
+ counter++;
+ }
+ });
+
+ // Let it run for one sec
+ Thread.Sleep(testTimeMs);
+
+ runTest = false;
+ task1.Wait();
+ task2.Wait();
+ task3.Wait();
+
+ var sequenceCommon = evListCommon.ToString();
+ var sequenceA = evListCommon.ToString("A");
+ var sequenceB = evListCommon.ToString("B");
+
+ // remove B locks interfering with A sequence of rvalidating task1
+
+ Enumerable.Range(0,10).ToList().ForEach( (i) => sequenceCommon = sequenceCommon
+ .Replace("A1B2", "A1")
+ .Replace("A1B3", "A1")
+ .Replace("B2A1","A1")
+ .Replace("B3A1","A1")
+ );
+ sequenceCommon = sequenceCommon.Replace("A1A1A1B1B1B1A1A1A1", "");
+ // The only allowed sequence in common for task1 is nested sequence
+ Assert.IsFalse(sequenceCommon.Contains("A1"), "Sequence should only contain task 1 resource in right order:"+sequenceCommon);
+ Assert.IsFalse(sequenceCommon.Contains("B1"), "Sequence should only contain task 1 resource in right order:"+sequenceCommon);
+
+ int countA1 = sequenceA.Where(a => a == '1').Count();
+ int countA2 = sequenceA.Where(a => a == '2').Count();
+ int countA3 = sequenceA.Where(a => a == '3').Count();
+
+ int countB1 = sequenceB.Where(a => a == '1').Count();
+ int countB2 = sequenceB.Where(a => a == '2').Count();
+ int countB3 = sequenceB.Where(a => a == '3').Count();
+
+ // Assert that all of the threads had their fair share of action
+ Assert.GreaterOrEqual(countA1, minimumOccurences);
+ Assert.GreaterOrEqual(countA2, minimumOccurences);
+ Assert.GreaterOrEqual(countA3, minimumOccurences);
+ Assert.GreaterOrEqual(countB1, minimumOccurences);
+ Assert.GreaterOrEqual(countB2, minimumOccurences);
+ Assert.GreaterOrEqual(countB3, minimumOccurences);
+
+
+ sequenceA = sequenceA.Replace("A1A1A1", "");
+ sequenceA = sequenceA.Replace("A2A2A2", "");
+ sequenceA = sequenceA.Replace("A3A3A3", "");
+
+ sequenceB = sequenceB.Replace("B1B1B1", "");
+ sequenceB = sequenceB.Replace("B2B2B2", "");
+ sequenceB = sequenceB.Replace("B3B3B3", "");
+
+
+ Assert.AreEqual(0,sequenceA.Length, "There were illegal sequences of execution for resource A: "+sequenceA);
+ Assert.AreEqual(0,sequenceB.Length, "There were illegal sequences of execution for resource B: "+sequenceB);
+
+ }
+ }
+}
\ No newline at end of file