/// <summary> /// <para>Deletes an <see cref="IQueueMessage" /></para> /// <para>under a synchronisation token and cancels related jobs (i.e. keep-alive operations).</para> /// </summary> /// <param name="asyncLock">An object that's responsible for synchronising access to shared resources in an asynchronous manner.</param> /// <param name="message">The message to delete.</param> /// <param name="messageSpecificCancellationTokenSource">The message-specific cancellation token.</param> private async Task SyncDeleteMessage( [NotNull] AsyncLock asyncLock, [NotNull] QueueMessageWrapper message, [CanBeNull] CancellationTokenSource messageSpecificCancellationTokenSource) { Guard.NotNull(message, "message"); Guard.NotNull(asyncLock, "asyncLock"); using (await asyncLock.LockAsync()) //messageSpecificCancellationTokenSource != null ? messageSpecificCancellationTokenSource.Token : CancellationToken.None)) { // Cancel all other waiting operations before deleting. this.Top.DeleteMessage(message.ActualMessage); if (await message.GetWasOverflownAsync().ConfigureAwait(false)) { try { await this.Top.RemoveOverflownContentsAsync(message, messageSpecificCancellationTokenSource.Token).ConfigureAwait(false); } catch (CloudToolsStorageException ex) { if (ex.StatusCode != 404 && ex.StatusCode != 409 && ex.StatusCode != 412) { throw; } } } if (messageSpecificCancellationTokenSource != null) { messageSpecificCancellationTokenSource.Cancel(); } } }
/// <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); }
/// <summary> /// This member is intended for internal usage only. Converts an incoming message to an entity. /// </summary> /// <typeparam name="T">The type of the object to attempt to deserialise to.</typeparam> /// <param name="message">The original message.</param> /// <param name="token">An optional cancellation token.</param> /// <returns>The contents of the message as an instance of type <typeparamref name="T" />.</returns> public virtual async Task <T> DecodeMessageAsync <T>(QueueMessageWrapper message, CancellationToken token) { var msgBytes = message.ActualMessage.AsBytes; var overflownId = (message.SetOverflowId(await this.Top.GetOverflownMessageId(message.ActualMessage).ConfigureAwait(false))); var wasOverflown = (message.SetWasOverflown(!string.IsNullOrWhiteSpace(overflownId))); msgBytes = await(wasOverflown ? this.Top.GetOverflownMessageContentsAsync(message.ActualMessage, overflownId, token) : this.Top.GetNonOverflownMessageContentsAsync(message.ActualMessage, token)).ConfigureAwait(false); var serialized = await this.Top.ByteArrayToSerializedMessageContents(msgBytes).ConfigureAwait(false); return(typeof(T) == typeof(string) ? (T)(object)serialized : this.Top.DeserializeToObject <T>(serialized)); }
/// <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); }
/// <summary> /// Cleans up the message contents that were stored outside of the message due to the contents being overflown. /// </summary> /// <param name="message">The message's contents.</param> /// <param name="token">An optional cancellation token.</param> protected internal override Task RemoveOverflownContentsAsync(QueueMessageWrapper message, CancellationToken token) { return(Task.FromResult(false)); }
/// <summary> /// Cleans up the message contents that were stored outside of the message due to the contents being overflown. /// </summary> /// <param name="message">The message's contents.</param> /// <param name="token">An optional cancellation token.</param> protected internal abstract Task RemoveOverflownContentsAsync(QueueMessageWrapper message, CancellationToken token);