Beispiel #1
0
        public void TestSerial_NormalProcessing()
        {
            // Arrange
            const int runCount = 100;
            var       client   = new CloudEnvironment();
            var       overflow = client.BlobClient.GetContainerReference("overflownqueues-1");
            var       queue    = client.QueueClient.GetQueueReference("test1");
            var       result   = string.Empty;
            var       expected = string.Empty;
            var       sw       = new Stopwatch();
            var       factory  = new AzureExtendedQueueFactory(new AzureBlobContainer(overflow), new ConsoleLogService());
            var       equeue   = factory.Create(new AzureQueue(queue));

            for (var i = 0; i < runCount; i++)
            {
                expected += i.ToString(CultureInfo.InvariantCulture);
            }

            using (var mre = new ManualResetEvent(false))
            {
                var options = new HandleMessagesSerialOptions(
                    TimeSpan.FromSeconds(0),
                    TimeSpan.FromMinutes(2),
                    TimeSpan.FromSeconds(30),
                    5,
                    new CancellationToken(),
                    message =>
                {
                    if (message.GetMessageContents <string>() == "END")
                    {
                        mre.Set();
                        return(Task.FromResult(true));
                    }

                    result += message.GetMessageContents <string>();
                    return(Task.FromResult(true));
                },
                    null,
                    ex => { throw ex; });

                // Act
                sw.Start();
                queue.CreateIfNotExists();
                overflow.CreateIfNotExists();
                queue.Clear();
                for (var i = 0; i < runCount; i++)
                {
                    equeue.AddMessageEntity(i.ToString(CultureInfo.InvariantCulture));
                }
                equeue.AddMessageEntity("END");
                equeue.HandleMessagesInSerialAsync(options);

                // Assert
                mre.WaitOne();
                sw.Stop();
                Trace.WriteLine("Total execution time (in seconds): " + sw.Elapsed.TotalSeconds.ToString(CultureInfo.InvariantCulture));
                Trace.WriteLine(equeue.Statistics);
                Assert.AreEqual(expected, result);
            }
        }
Beispiel #2
0
        public void TestErrorDuringRetrieval_GetOverflownContents()
        {
            // Arrange
            var          client   = new CloudEnvironment();
            var          queue    = client.QueueClient.GetQueueReference("test7");
            var          overflow = client.BlobClient.GetContainerReference("overflownqueues-7");
            var          rnd      = new Random();
            ComplexModel result   = null;
            var          expected = new ComplexModel {
                Name = new string(Enumerable.Range(1, 128 * 1024).Select(r => (char)rnd.Next(1024, 4096)).ToArray())
            };
            var sw         = new Stopwatch();
            var succeeded  = false;
            var factoryOne = new DefaultExtendedQueueFactory(
                new AzureQueueMessageProvider(),
                new AzureMaximumMessageSizeProvider(),
                new AzureMaximumMessagesPerRequestProvider(),
                new ChaosMonkeyOverflownMessageHandler(new AzureBlobContainer(overflow), ChaosMonkeyOverflownMessageHandler.FailureMode.GetOverflownContents),
                new ConsoleLogService()
                );

            new AzureExtendedQueueFactory(new AzureBlobContainer(overflow), new ConsoleLogService());
            var equeue = factoryOne.Create(new AzureQueue(queue));

            using (var mre = new ManualResetEvent(false))
            {
                var options = new HandleMessagesSerialOptions(
                    TimeSpan.FromSeconds(0),
                    TimeSpan.FromMinutes(2),
                    TimeSpan.FromSeconds(30),
                    1,
                    new CancellationToken(),
                    message =>
                {
                    result = message.GetMessageContents <ComplexModel>();
                    mre.Set();
                    return(Task.FromResult(true));
                },
                    null,
                    ex =>
                {
                    succeeded = true;
                    mre.Set();
                });

                // Act
                sw.Start();
                queue.CreateIfNotExists();
                overflow.CreateIfNotExists();
                queue.Clear();
                equeue.AddMessageEntity(expected);
                equeue.HandleMessagesInSerialAsync(options);

                // Assert
                mre.WaitOne();
                sw.Stop();
                Trace.WriteLine("Total execution time (in seconds): " + sw.Elapsed.TotalSeconds.ToString(CultureInfo.InvariantCulture));
                Assert.IsTrue(succeeded);
            }
        }
        /// <summary>
        ///     Handles poison messages by either delegating it to a handler or deleting it if no handler is provided.
        /// </summary>
        /// <param name="message">The message to operate on.</param>
        /// <param name="messageOptions">Initialisation options for this method.</param>
        /// <param name="asyncLock">An object that's responsible for synchronising access to shared resources in an asynchronous manner.</param>
        /// <param name="messageSpecificCancellationTokenSource">The message-specific cancellation token.</param>
        /// <returns>
        ///     True if the <paramref name="message" /> was deleted; <see langword="false" /> if it should be requeued and <see langword="checked" /> again.
        /// </returns>
        private async Task <bool> WasPoisonMessageAndRemoved(
            [NotNull] HandleMessagesSerialOptions messageOptions,
            [NotNull] QueueMessageWrapper message,
            [NotNull] AsyncLock asyncLock,
            [NotNull] CancellationTokenSource messageSpecificCancellationTokenSource)
        {
            Guard.NotNull(messageOptions, "messageOptions");
            Guard.NotNull(message, "message");
            Guard.NotNull(asyncLock, "asyncLock");
            Guard.NotNull(messageSpecificCancellationTokenSource, "messageSpecificCancellationTokenSource");

            if (message.ActualMessage.DequeueCount <= messageOptions.PoisonMessageThreshold)
            {
                return(false);
            }

            if (messageOptions.PoisonHandler != null && !(await messageOptions.PoisonHandler(message).ConfigureAwait(false)))
            {
                return(false);
            }

            this.Statistics.IncreasePoisonMessages();

            await this.Top.SyncDeleteMessage(asyncLock, message, messageSpecificCancellationTokenSource).ConfigureAwait(false);

            return(true);
        }
Beispiel #4
0
 protected internal override void SerialFinallyHandler(
     HandleMessagesSerialOptions messageOptions,
     Task keepAliveTask,
     IQueueMessage message,
     CancellationTokenSource messageSpecificCancellationTokenSource)
 {
     this.DecoratedQueue.SerialFinallyHandler(messageOptions, keepAliveTask, message, messageSpecificCancellationTokenSource);
 }
Beispiel #5
0
        /// <summary>
        ///     Error handler for cancelled task exception during serial message processing.
        /// </summary>
        /// <param name="messageOptions">The message options object.</param>
        protected internal virtual bool HandleTaskCancelled(HandleMessagesSerialOptions messageOptions)
        {
            this.Top.LogAction(LogSeverity.Info, "Attempting to cancel No messages found when an attempt was made to read from a queue", "Queue: {0}", this.Name);
            if (messageOptions.CancelToken.IsCancellationRequested)
            {
                return(true);
            }

            return(false);
        }
Beispiel #6
0
        public void TestSerializedMessages()
        {
            // Arrange
            var          client   = new CloudEnvironment();
            var          queue    = client.QueueClient.GetQueueReference("test5");
            var          overflow = client.BlobClient.GetContainerReference("overflownqueues-5");
            ComplexModel result   = null;
            var          expected = new ComplexModel {
                Name = "Test"
            };
            var sw      = new Stopwatch();
            var factory = new AzureExtendedQueueFactory(new AzureBlobContainer(overflow), new ConsoleLogService());
            var equeue  = factory.Create(new AzureQueue(queue));

            using (var mre = new ManualResetEvent(false))
            {
                var options = new HandleMessagesSerialOptions(
                    TimeSpan.FromSeconds(0),
                    TimeSpan.FromMinutes(2),
                    TimeSpan.FromSeconds(30),
                    5,
                    new CancellationToken(),
                    message =>
                {
                    result = message.GetMessageContents <ComplexModel>();
                    mre.Set();
                    return(Task.FromResult(true));
                },
                    null,
                    ex => { throw ex; });

                // Act
                sw.Start();
                queue.CreateIfNotExists();
                overflow.CreateIfNotExists();
                queue.Clear();
                equeue.AddMessageEntity(expected);
                equeue.HandleMessagesInSerialAsync(options);

                // Assert
                mre.WaitOne();
                sw.Stop();
                Trace.WriteLine("Total execution time (in seconds): " + sw.Elapsed.TotalSeconds.ToString(CultureInfo.InvariantCulture));
                Assert.AreEqual(expected.Name, result.Name);
                Assert.AreEqual(expected.ADictionary.First().Key, result.ADictionary.First().Key);
                Assert.AreEqual(expected.ADictionary.First().Value, result.ADictionary.First().Value);
                Assert.AreEqual(expected.AList.First(), result.AList.First());
            }
        }
        /// <summary>
        ///     Processes a queue message.
        /// </summary>
        /// <param name="message">The message to be processed.</param>
        /// <param name="messageOptions">Initialisation options for the method that handles the messages.</param>
        /// <param name="messageSpecificCancellationTokenSource">A cancellation token source that's specific to this message.</param>
        /// <param name="asyncLock">An object that's responsible for synchronising access to shared resources in an asynchronous manner.</param>
        private async Task <Task> ProcessMessageInternal(
            [NotNull] QueueMessageWrapper message,
            [NotNull] HandleMessagesSerialOptions messageOptions,
            [NotNull] CancellationTokenSource messageSpecificCancellationTokenSource)
        {
            Guard.NotNull(message, "message");
            Guard.NotNull(messageOptions, "messageOptions");
            Guard.NotNull(messageSpecificCancellationTokenSource, "messageSpecificCancellationTokenSource");

            var asynclock = new AsyncLock();


            // Very old message; delete it and move to the next one
            if (messageOptions.TimeWindow.TotalSeconds > 0 &&
                (!message.ActualMessage.InsertionTime.HasValue || message.ActualMessage.InsertionTime.Value.UtcDateTime.Add(messageOptions.TimeWindow) < DateTime.UtcNow))
            {
                await this.Top.SyncDeleteMessage(asynclock, message, messageSpecificCancellationTokenSource).ConfigureAwait(false);

                return(Task.FromResult <Task>(null));
            }

            // Handles poison messages by either delegating it to a handler or deleting it if no handler is provided.
            if (await this.Top.WasPoisonMessageAndRemoved(messageOptions, message, asynclock, messageSpecificCancellationTokenSource).ConfigureAwait(false))
            {
                return(Task.FromResult <Task>(null));
            }

            // Starts the background thread which ensures message leases stay fresh.
            var keepAliveTask = this.KeepMessageAlive(message.ActualMessage, messageOptions.MessageLeaseTime, messageSpecificCancellationTokenSource.Token, asynclock);

            using (var comboCancelToken = CancellationTokenSource.CreateLinkedTokenSource(messageSpecificCancellationTokenSource.Token, messageOptions.CancelToken))
            {
                // Execute the provided action and if successful, delete the message.
                if (await messageOptions.MessageHandler(message).ConfigureAwait(false))
                {
                    this.Statistics.IncreaseSuccessfulMessages();
                    await this.Top.SyncDeleteMessage(asynclock, message, comboCancelToken).ConfigureAwait(false);
                }
                else
                {
                    this.Statistics.IncreaseReenqueuesCount();
                }
            }


            messageSpecificCancellationTokenSource.Cancel();
            return(keepAliveTask);
        }
Beispiel #8
0
        public void TestOverflownMessages()
        {
            // Arrange
            var client   = new CloudEnvironment();
            var queue    = client.QueueClient.GetQueueReference("test4");
            var overflow = client.BlobClient.GetContainerReference("overflownqueues-4");
            var result   = string.Empty;
            var rnd      = new Random();
            var expected = new string(Enumerable.Range(1, 128 * 1024).Select(r => (char)rnd.Next(1024, 4096)).ToArray());
            var sw       = new Stopwatch();
            var factory  = new AzureExtendedQueueFactory(new AzureBlobContainer(overflow), new ConsoleLogService());
            var equeue   = factory.Create(new AzureQueue(queue));

            using (var mre = new ManualResetEvent(false))
            {
                var options = new HandleMessagesSerialOptions(
                    TimeSpan.FromSeconds(0),
                    TimeSpan.FromMinutes(2),
                    TimeSpan.FromSeconds(30),
                    5,
                    new CancellationToken(),
                    message =>
                {
                    result = message.GetMessageContents <string>();
                    mre.Set();
                    return(Task.FromResult(true));
                },
                    null,
                    ex => { throw ex; });

                // Act
                sw.Start();
                queue.CreateIfNotExists();
                overflow.CreateIfNotExists();
                queue.Clear();
                equeue.AddMessageEntity(expected);
                equeue.HandleMessagesInSerialAsync(options);

                // Assert
                mre.WaitOne();
                sw.Stop();
                Trace.WriteLine("Total execution time (in seconds): " + sw.Elapsed.TotalSeconds.ToString(CultureInfo.InvariantCulture));
                Assert.AreEqual(expected, result);
            }
        }
Beispiel #9
0
        /// <summary>
        ///     The finally handler in the try/catch/finally statement of HandleMessagesAsync.
        /// </summary>
        /// <param name="messageOptions">The message options object.</param>
        /// <param name="keepAliveTask">The <see cref="Task" /> that keeps the message "alive".</param>
        /// <param name="message">The message currently processing.</param>
        /// <param name="messageSpecificCancellationTokenSource">The cancellation token for the message.</param>
        protected internal virtual void SerialFinallyHandler(
            [CanBeNull] HandleMessagesSerialOptions messageOptions,
            [CanBeNull] Task keepAliveTask,
            [CanBeNull] IQueueMessage message,
            [NotNull] CancellationTokenSource messageSpecificCancellationTokenSource)
        {
            if (Guard.IsAnyNull(messageOptions, messageSpecificCancellationTokenSource))
            {
                return;
            }

            // Cancel any outstanding jobs due to the faulted operation (the keepalive task should have been cancelled)
            if (keepAliveTask != null && !keepAliveTask.IsCompleted)
            {
                if (message != null)
                {
                    this.Top.LogAction(LogSeverity.Warning, "Message was not processed successfully", "Queue's '{0}' message '{1}', processing faulted; cancelling related jobs", this.Name, message.Id);
                    messageSpecificCancellationTokenSource.Cancel();
                }
            }
        }
Beispiel #10
0
        public void TestSerial_SerialProcessingDelayed()
        {
            // Arrange
            const int runCount    = 3;
            var       client      = new CloudEnvironment();
            var       queue       = client.QueueClient.GetQueueReference("test10");
            var       overflow    = client.BlobClient.GetContainerReference("overflownqueues-10");
            var       locking     = new object();
            var       result      = string.Empty;
            var       expected    = string.Empty;
            var       sw          = new Stopwatch();
            long      actuallyRun = 0;
            var       factory     = new AzureExtendedQueueFactory(new AzureBlobContainer(overflow), new ConsoleLogService());
            var       equeue      = factory.Create(new AzureQueue(queue));
            var       lck         = new AsyncLock();


            for (var i = 1; i < runCount + 1; i++)
            {
                expected += ((char)(i)).ToString(CultureInfo.InvariantCulture);
            }

            using (var mre = new ManualResetEvent(false))
            {
                var options = new HandleMessagesSerialOptions(
                    TimeSpan.FromSeconds(0),
                    TimeSpan.FromSeconds(30),
                    TimeSpan.FromSeconds(30),
                    5,
                    new CancellationToken(),
                    async message =>
                {
                    using (await lck.LockAsync())
                    {
                        var innersw = new Stopwatch();
                        innersw.Start();
                        // Intentional spinning
                        while (true)
                        {
                            if (innersw.Elapsed > TimeSpan.FromSeconds(33))
                            {
                                break;
                            }
                        }


                        var character = message.GetMessageContents <string>();

                        result += character;
                    }


                    if (Interlocked.Increment(ref actuallyRun) == runCount)
                    {
                        mre.Set();
                    }

                    return(true);
                },
                    null,
                    ex => { throw ex; });

                // Act
                sw.Start();
                queue.CreateIfNotExists();
                overflow.CreateIfNotExists();
                queue.Clear();
                for (var i = 1; i < runCount + 1; i++)
                {
                    equeue.AddMessageEntity(((char)(i)).ToString(CultureInfo.InvariantCulture));
                }
                equeue.HandleMessagesInSerialAsync(options);

                // Assert
                mre.WaitOne();
                sw.Stop();
                Trace.WriteLine("Total execution time (in seconds): " + sw.Elapsed.TotalSeconds.ToString(CultureInfo.InvariantCulture));
                Assert.IsTrue(expected.All(c => result.Contains(c)));
            }
        }
Beispiel #11
0
 protected internal override bool HandleTaskCancelled(HandleMessagesSerialOptions messageOptions)
 {
     return(this.DecoratedQueue.HandleTaskCancelled(messageOptions));
 }
Beispiel #12
0
 protected internal override Task <IQueueMessage> GetMessageFromQueue(HandleMessagesSerialOptions messageOptions, CancellationTokenSource messageSpecificCancellationTokenSource)
 {
     return(this.DecoratedQueue.GetMessageFromQueue(messageOptions, messageSpecificCancellationTokenSource));
 }
Beispiel #13
0
        /// <summary>
        ///     Begins a task that receives messages serially and automatically manages their lifetime.
        /// </summary>
        /// <param name="messageOptions">An options object used to initialise the procedure.</param>
        /// <param name="invoker">The (optional) decorator that called this method.</param>
        /// <returns>
        ///     A cancellable task representing the message processing procedure.
        /// </returns>
        public async Task HandleMessagesInSerialAsync(HandleMessagesSerialOptions messageOptions)
        {
            Guard.NotNull(messageOptions, "messageOptions");

            this.Statistics.IncreaseListeners();
            this.Statistics.IncreaseAllMessageSlots();

            while (true)
            {
                if (messageOptions.CancelToken.IsCancellationRequested)
                {
                    this.Statistics.DecreaseListeners();
                    this.Statistics.DecreaseAllMessageSlots();
                    return;
                }

                // When set to true, the queue won't wait before it requests another message
                var receivedMessage = false;

                // Used to prevent a message operation from running on a specific message
                var messageSpecificCancellationTokenSource = new CancellationTokenSource();

                Task          keepAliveTask = null;
                IQueueMessage message       = null;

                try
                {
                    this.Top.LogAction(LogSeverity.Debug, "Attempting to read from a queue", "Queue: {0}", this.Name);

                    message = await this.Top.GetMessageFromQueue(messageOptions, messageSpecificCancellationTokenSource).ConfigureAwait(false);

                    if (message != null)
                    {
                        this.Statistics.IncreaseBusyMessageSlots();
                        this.Top.LogAction(LogSeverity.Debug, "One message found in the queue and will be processed", "Queue: {0}, Message ID: {1}", this.Name, message.Id);
                        receivedMessage = true;

                        keepAliveTask = await this.Top.ProcessMessageInternal(
                            new QueueMessageWrapper(this.Top, message),
                            messageOptions,
                            messageSpecificCancellationTokenSource).ConfigureAwait(false);
                    }
                    else
                    {
                        this.Top.LogAction(LogSeverity.Debug, "No messages found when an attempt was made to read from a queue", "Queue: {0}", this.Name);
                    }
                }
                catch (TaskCanceledException)
                {
                    if (this.Top.HandleTaskCancelled(messageOptions))
                    {
                        this.Statistics.DecreaseListeners();
                        this.Statistics.DecreaseAllMessageSlots();
                        this.Statistics.DecreaseBusyMessageSlots();
                        return;
                    }
                }
                catch (CloudToolsStorageException ex)
                {
                    this.Top.HandleStorageExceptions(messageOptions, ex);
                }
                catch (Exception ex)
                {
                    this.Top.HandleGeneralExceptions(messageOptions, ex);
                }
                finally
                {
                    this.Top.SerialFinallyHandler(messageOptions, keepAliveTask, message, messageSpecificCancellationTokenSource);
                }

                this.Statistics.DecreaseBusyMessageSlots();

                // Delay the next polling attempt for a new message, since no messages were received last time.
                if (!receivedMessage)
                {
                    await Task.Delay(messageOptions.PollFrequency, messageOptions.CancelToken).ConfigureAwait(false);
                }
            }
        }
Beispiel #14
0
 /// <summary>
 ///     Gets the next message from the queue, asynchronously.
 /// </summary>
 /// <param name="messageOptions">The message options object.</param>
 /// <param name="messageSpecificCancellationTokenSource">The message specific cancellation token source.</param>
 /// <returns>An <see cref="IQueueMessage" /> instance.</returns>
 protected internal virtual async Task <IQueueMessage> GetMessageFromQueue(HandleMessagesSerialOptions messageOptions, CancellationTokenSource messageSpecificCancellationTokenSource)
 {
     return(await this.GetMessageAsync(messageOptions.MessageLeaseTime, messageSpecificCancellationTokenSource.Token).ConfigureAwait(false));
 }