public void Exceptions_in_OnQueueEmpty_are_ignored()
        {
            // Arrange
            var onMessageInvokeCount    = 0;
            var onQueueEmptyInvokeCount = 0;
            var onErrorInvokeCount      = 0;

            var exceptionSimulated = false;
            var lockObject         = new Object();

            var queueName          = "myqueue";
            var mockQueue          = GetMockQueue(queueName);
            var mockQueueClient    = GetMockQueueClient(mockQueue);
            var mockBlobContainer  = GetMockBlobContainer();
            var mockBlobClient     = GetMockBlobClient(mockBlobContainer);
            var mockStorageAccount = GetMockStorageAccount(mockBlobClient, mockQueueClient);

            mockQueue.Setup(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).ReturnsAsync(Enumerable.Empty <CloudQueueMessage>());

            var messagePump = new AsyncMessagePump("myqueue", mockStorageAccount.Object, 1, TimeSpan.FromMinutes(1), 3);

            messagePump.OnMessage = (message, cancellationToken) =>
            {
                Interlocked.Increment(ref onMessageInvokeCount);
            };
            messagePump.OnQueueEmpty = cancellationToken =>
            {
                Interlocked.Increment(ref onQueueEmptyInvokeCount);

                // Simulate an exception (only the first time)
                lock (lockObject)
                {
                    if (!exceptionSimulated)
                    {
                        exceptionSimulated = true;
                        throw new Exception("This dummy exception should be ignored");
                    }
                }

                // Stop the message pump
                messagePump.Stop();
            };
            messagePump.OnError = (message, exception, isPoison) =>
            {
                Interlocked.Increment(ref onErrorInvokeCount);
            };

            // Act
            messagePump.Start();

            // Assert
            onMessageInvokeCount.ShouldBe(0);
            onQueueEmptyInvokeCount.ShouldBeGreaterThan(0);
            onErrorInvokeCount.ShouldBe(0);
            mockQueue.Verify(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(1));
        }
Exemple #2
0
        public void Start_without_OnMessage_throws()
        {
            // Arrange
            var mockStorageUri = new Uri("http://bogus/myaccount");
            var mockQueue      = new Mock <CloudQueue>(MockBehavior.Strict, mockStorageUri);
            var messagePump    = new AsyncMessagePump(mockQueue.Object, 1, 1, TimeSpan.FromMinutes(1), 3);

            // Act
            messagePump.Start();
        }
Exemple #3
0
        public void Exceptions_in_OnQueueEmpty_are_ignored()
        {
            // Arrange
            var onMessageInvokeCount    = 0;
            var onQueueEmptyInvokeCount = 0;
            var onErrorInvokeCount      = 0;

            var exceptionSimulated = false;
            var lockObject         = new Object();

            var mockStorageUri = new Uri("http://bogus/myaccount");
            var mockQueue      = new Mock <CloudQueue>(MockBehavior.Strict, mockStorageUri);

            mockQueue.Setup(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).Returns <CloudQueueMessage>(null);

            var messagePump = new AsyncMessagePump(mockQueue.Object, 1, 1, TimeSpan.FromMinutes(1), 3);

            messagePump.OnMessage = (message, cancellationToken) =>
            {
                Interlocked.Increment(ref onMessageInvokeCount);
            };
            messagePump.OnQueueEmpty = cancellationToken =>
            {
                Interlocked.Increment(ref onQueueEmptyInvokeCount);

                // Simulate an exception (only the first time)
                lock (lockObject)
                {
                    if (!exceptionSimulated)
                    {
                        exceptionSimulated = true;
                        throw new Exception("This dummy exception should be ignored");
                    }
                }

                // Run the 'OnStop' on a different thread so we don't block it
                Task.Run(() =>
                {
                    messagePump.Stop();
                }).ConfigureAwait(false);
            };
            messagePump.OnError = (message, exception, isPoison) =>
            {
                Interlocked.Increment(ref onErrorInvokeCount);
            };

            // Act
            messagePump.Start();

            // Assert
            Assert.AreEqual(0, onMessageInvokeCount);
            Assert.IsTrue(onQueueEmptyInvokeCount > 0);
            Assert.AreEqual(0, onErrorInvokeCount);
            mockQueue.Verify(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(1));
        }
Exemple #4
0
        public void No_message_processed_when_queue_is_empty()
        {
            // Arrange
            var onMessageInvokeCount    = 0;
            var onQueueEmptyInvokeCount = 0;
            var onErrorInvokeCount      = 0;

            var mockStorageUri = new Uri("http://bogus/myaccount");
            var mockQueue      = new Mock <CloudQueue>(MockBehavior.Strict, mockStorageUri);

            mockQueue.Setup(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).Returns <CloudQueueMessage>(null);

            var messagePump = new AsyncMessagePump(mockQueue.Object, 1, 1, TimeSpan.FromMinutes(1), 3);

            messagePump.OnMessage = (message, cancellationToken) =>
            {
                Interlocked.Increment(ref onMessageInvokeCount);
            };
            messagePump.OnQueueEmpty = cancellationToken =>
            {
                Interlocked.Increment(ref onQueueEmptyInvokeCount);

                // Run the 'OnStop' on a different thread so we don't block it
                Task.Run(() =>
                {
                    messagePump.Stop();
                }).ConfigureAwait(false);
            };
            messagePump.OnError = (message, exception, isPoison) =>
            {
                Interlocked.Increment(ref onErrorInvokeCount);
            };

            // Act
            messagePump.Start();

            // Assert
            Assert.AreEqual(0, onMessageInvokeCount);
            Assert.AreEqual(1, onQueueEmptyInvokeCount);
            Assert.AreEqual(0, onErrorInvokeCount);

            // You would expect the 'GetMessageAsync' method to be invoked only once, but unfortunately we can't be sure.
            // It will be invoked a small number of times (probably once or twice, maybe three times but not more than that).
            // However we can't be more precise because we stop the message pump on another thread and 'GetMessageAsync' may be invoked
            // a few times while we wait for the message pump to stop.
            //
            // What this means is that there is no way to precisely assert the number of times the method has been invoked. The only
            // thing we know for sure, is that 'GetMessageAsync' has been invoked at least once
            mockQueue.Verify(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(1));
        }
        public void No_message_processed_when_queue_is_empty()
        {
            // Arrange
            var onMessageInvokeCount    = 0;
            var onQueueEmptyInvokeCount = 0;
            var onErrorInvokeCount      = 0;

            var queueName          = "myqueue";
            var mockQueue          = GetMockQueue(queueName);
            var mockQueueClient    = GetMockQueueClient(mockQueue);
            var mockBlobContainer  = GetMockBlobContainer();
            var mockBlobClient     = GetMockBlobClient(mockBlobContainer);
            var mockStorageAccount = GetMockStorageAccount(mockBlobClient, mockQueueClient);

            mockQueue.Setup(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).ReturnsAsync(Enumerable.Empty <CloudQueueMessage>());

            var messagePump = new AsyncMessagePump("myqueue", mockStorageAccount.Object, 1, TimeSpan.FromMinutes(1), 3);

            messagePump.OnMessage = (message, cancellationToken) =>
            {
                Interlocked.Increment(ref onMessageInvokeCount);
            };
            messagePump.OnQueueEmpty = cancellationToken =>
            {
                Interlocked.Increment(ref onQueueEmptyInvokeCount);
                messagePump.Stop();
            };
            messagePump.OnError = (message, exception, isPoison) =>
            {
                Interlocked.Increment(ref onErrorInvokeCount);
            };

            // Act
            messagePump.Start();

            // Assert
            onMessageInvokeCount.ShouldBe(0);
            onQueueEmptyInvokeCount.ShouldBe(1);
            onErrorInvokeCount.ShouldBe(0);

            // You would expect the 'GetMessagesAsync' method to be invoked only once, but unfortunately we can't be sure.
            // It will be invoked a small number of times (probably once or twice, maybe three times but not more than that).
            // However we can't be more precise because we stop the message pump on another thread and 'GetMessagesAsync' may
            // be invoked a few times while we wait for the message pump to stop.
            //
            // What this means is that there is no way to precisely assert the number of times the method has been invoked.
            // The only thing we know for sure, is that 'GetMessagesAsync' has been invoked at least once
            mockQueue.Verify(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(1));
        }
        public void Start_without_OnMessage_throws()
        {
            // Arrange
            var queueName          = "myqueue";
            var mockQueue          = GetMockQueue(queueName);
            var mockQueueClient    = GetMockQueueClient(mockQueue);
            var mockBlobContainer  = GetMockBlobContainer();
            var mockBlobClient     = GetMockBlobClient(mockBlobContainer);
            var mockStorageAccount = GetMockStorageAccount(mockBlobClient, mockQueueClient);

            var messagePump = new AsyncMessagePump("myqueue", mockStorageAccount.Object, 1, TimeSpan.FromMinutes(1), 3);

            // Act
            Should.Throw <ArgumentNullException>(() => messagePump.Start());
        }
Exemple #7
0
        public static void ProcessSimpleMessages(string queueName, CloudStorageAccount storageAccount, ILogProvider logProvider, IMetrics metrics)
        {
            var       logger = logProvider.GetLogger("ProcessSimpleMessages");
            Stopwatch sw     = null;

            // Configure the message pump
            var messagePump = new AsyncMessagePump(queueName, storageAccount, 10, TimeSpan.FromMinutes(1), 3, metrics)
            {
                OnMessage = (message, cancellationToken) =>
                {
                    logger(Logging.LogLevel.Debug, () => message.Content.ToString());
                }
            };

            // Stop the message pump when the queue is empty.
            messagePump.OnQueueEmpty = cancellationToken =>
            {
                // Stop the timer
                if (sw.IsRunning)
                {
                    sw.Stop();
                }

                // Stop the message pump
                logger(Logging.LogLevel.Debug, () => "Asking the 'simple' message pump to stop");
                messagePump.Stop();
                logger(Logging.LogLevel.Debug, () => "The 'simple' message pump has been stopped");
            };

            // Start the message pump
            sw = Stopwatch.StartNew();
            logger(Logging.LogLevel.Debug, () => "The 'simple' message pump is starting");
            messagePump.Start();

            // Display summary
            logger(Logging.LogLevel.Info, () => $"\tDone in {sw.Elapsed.ToDurationString()}");
        }
        public void Poison_message_is_rejected()
        {
            // Arrange
            var onMessageInvokeCount    = 0;
            var onQueueEmptyInvokeCount = 0;
            var onErrorInvokeCount      = 0;

            var isRejected   = false;
            var retries      = 3;
            var lockObject   = new Object();
            var cloudMessage = new CloudQueueMessage("Message");

            var queueName          = "myqueue";
            var mockQueue          = GetMockQueue(queueName);
            var mockQueueClient    = GetMockQueueClient(mockQueue);
            var mockBlobContainer  = GetMockBlobContainer();
            var mockBlobClient     = GetMockBlobClient(mockBlobContainer);
            var mockStorageAccount = GetMockStorageAccount(mockBlobClient, mockQueueClient);

            mockQueue.Setup(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).ReturnsAsync((int messageCount, TimeSpan? visibilityTimeout, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) =>
            {
                if (cloudMessage != null)
                {
                    lock (lockObject)
                    {
                        if (cloudMessage != null)
                        {
                            // DequeueCount is a private property. Therefore we must use reflection to change its value
                            var dequeueCountProperty = cloudMessage.GetType().GetProperty("DequeueCount");
                            dequeueCountProperty.SetValue(cloudMessage, retries + 1);                               // intentionally set 'DequeueCount' to a value exceeding maxRetries to simulate a poison message

                            return(new[] { cloudMessage });
                        }
                    }
                }
                return(Enumerable.Empty <CloudQueueMessage>());
            });
            mockQueue.Setup(q => q.DeleteMessageAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).Returns((string messageId, string popReceipt, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) =>
            {
                lock (lockObject)
                {
                    cloudMessage = null;
                }
                return(Task.FromResult(true));
            });

            var messagePump = new AsyncMessagePump("myqueue", mockStorageAccount.Object, 1, TimeSpan.FromMinutes(1), retries);

            messagePump.OnMessage = (message, cancellationToken) =>
            {
                Interlocked.Increment(ref onMessageInvokeCount);
                throw new Exception("An error occured when attempting to process the message");
            };
            messagePump.OnQueueEmpty = cancellationToken =>
            {
                Interlocked.Increment(ref onQueueEmptyInvokeCount);
                messagePump.Stop();
            };
            messagePump.OnError = (message, exception, isPoison) =>
            {
                Interlocked.Increment(ref onErrorInvokeCount);
                if (isPoison)
                {
                    lock (lockObject)
                    {
                        isRejected   = true;
                        cloudMessage = null;
                    }
                }
            };

            // Act
            messagePump.Start();

            // Assert
            onMessageInvokeCount.ShouldBe(1);
            onQueueEmptyInvokeCount.ShouldBeGreaterThan(0);
            onErrorInvokeCount.ShouldBe(1);
            isRejected.ShouldBeTrue();
            mockQueue.Verify(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(2));
            mockQueue.Verify(q => q.DeleteMessageAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.Exactly(1));
        }
Exemple #9
0
        public void Poison_message_is_rejected()
        {
            // Arrange
            var onMessageInvokeCount    = 0;
            var onQueueEmptyInvokeCount = 0;
            var onErrorInvokeCount      = 0;

            var isRejected   = false;
            var retries      = 3;
            var lockObject   = new Object();
            var cloudMessage = new CloudQueueMessage("Message");

            var mockStorageUri = new Uri("http://bogus/myaccount");
            var mockQueue      = new Mock <CloudQueue>(MockBehavior.Strict, mockStorageUri);

            mockQueue.Setup(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).Returns((TimeSpan? visibilityTimeout, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) =>
            {
                if (cloudMessage == null)
                {
                    return(null);
                }

                lock (lockObject)
                {
                    if (cloudMessage != null)
                    {
                        // DequeueCount is a private property. Therefore we must use reflection to change its value
                        var t = cloudMessage.GetType();
                        t.InvokeMember("DequeueCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, cloudMessage, new object[] { cloudMessage.DequeueCount + 1 });
                    }
                    return(Task.FromResult(cloudMessage));
                }
            });
            mockQueue.Setup(q => q.DeleteMessageAsync(It.IsAny <CloudQueueMessage>())).Returns((CloudQueueMessage message) =>
            {
                lock (lockObject)
                {
                    cloudMessage = null;
                }
                return(Task.FromResult(true));
            });

            var messagePump = new AsyncMessagePump(mockQueue.Object, 1, 1, TimeSpan.FromMinutes(1), retries);

            messagePump.OnMessage = (message, cancellationToken) =>
            {
                Interlocked.Increment(ref onMessageInvokeCount);
                throw new Exception("An error occured when attempting to process the message");
            };
            messagePump.OnQueueEmpty = cancellationToken =>
            {
                Interlocked.Increment(ref onQueueEmptyInvokeCount);

                // Run the 'OnStop' on a different thread so we don't block it
                Task.Run(() =>
                {
                    messagePump.Stop();
                }).ConfigureAwait(false);
            };
            messagePump.OnError = (message, exception, isPoison) =>
            {
                Interlocked.Increment(ref onErrorInvokeCount);
                if (isPoison)
                {
                    lock (lockObject)
                    {
                        isRejected   = true;
                        cloudMessage = null;
                    }
                }
            };

            // Act
            messagePump.Start();

            // Assert
            Assert.AreEqual(retries + 1, onMessageInvokeCount);
            Assert.IsTrue(onQueueEmptyInvokeCount > 0);
            Assert.AreEqual(retries + 1, onErrorInvokeCount);
            Assert.IsTrue(isRejected);
            mockQueue.Verify(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(retries));
        }
Exemple #10
0
#pragma warning disable RECS0154 // Parameter is never used
        static void Main(string[] args)
#pragma warning restore RECS0154 // Parameter is never used
        {
            // Ensure the storage emulator is running
            AzureStorageEmulatorManager.StartStorageEmulator();

            // If you want to see tracing from the Picton libary, change the LogLevel to 'Trace'
            var minLogLevel = Logging.LogLevel.Debug;

            // Configure logging to the console
            var logProvider = new ColoredConsoleLogProvider(minLogLevel);
            var logger      = logProvider.GetLogger("IntegrationTests");

            LogProvider.SetCurrentLogProvider(logProvider);

            // Ensure the Console is tall enough
            Console.WindowHeight = 60;

            // Setup the message queue in Azure storage emulator
            var storageAccount   = CloudStorageAccount.DevelopmentStorageAccount;
            var cloudQueueClient = storageAccount.CreateCloudQueueClient();

            cloudQueueClient.DefaultRequestOptions.RetryPolicy = new NoRetry();
            var cloudQueue = cloudQueueClient.GetQueueReference("myqueue");

            cloudQueue.CreateIfNotExists();

            var       lockObject = new Object();
            var       stopping   = false;
            Stopwatch sw         = null;

            // Add messages to our testing queue
            for (var i = 0; i < 50; i++)
            {
                cloudQueue.AddMessage(new CloudQueueMessage($"Hello world {i}"));
            }

            // Configure the message pump
            var messagePump = new AsyncMessagePump(cloudQueue, 1, 25, TimeSpan.FromMinutes(1), 3);

            messagePump.OnMessage = (message, cancellationToken) =>
            {
                logger(Logging.LogLevel.Debug, () => message.AsString);
            };
            messagePump.OnQueueEmpty = cancellationToken =>
            {
                // Stop the message pump when the queue is empty.
                // However, ensure that we try to stop it only once (otherwise each concurrent task would try to stop it)
                if (!stopping)
                {
                    lock (lockObject)
                    {
                        if (sw.IsRunning)
                        {
                            sw.Stop();
                        }
                        if (!stopping)
                        {
                            // Indicate that the message pump is stopping
                            stopping = true;

                            // Log to console
                            logger(Logging.LogLevel.Debug, () => "Asking the message pump to stop");

                            // Run the 'OnStop' on a different thread so we don't block it
                            Task.Run(() =>
                            {
                                messagePump.Stop();
                                logger(Logging.LogLevel.Debug, () => "Message pump has been stopped");
                            }).ConfigureAwait(false);
                        }
                    }
                }
            };

            // Start the message pump
            sw = Stopwatch.StartNew();
            logger(Logging.LogLevel.Debug, () => "Message pump is starting");
            messagePump.Start();

            // Display summary
            logger(Logging.LogLevel.Info, () => "Elapsed Milliseconds: " + sw.Elapsed.ToDurationString());
            logger(Logging.LogLevel.Info, () => "Press any key to exit...");

            // Flush the console key buffer
            while (Console.KeyAvailable)
            {
                Console.ReadKey(true);
            }

            // Wait for user to press a key
            Console.ReadKey();
        }