/// <summary> /// Adds a message to the queue /// </summary> /// <param name="message">The message to add</param> /// <param name="principal">The principal that sent the message or from whom /// the message was received</param> /// <param name="cancellationToken">A cancellation token that can be used by the caller /// to cancel the enqueueing operation</param> /// <returns>Returns a task that completes when the message has been added to the queue</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="message"/> is /// <c>null</c></exception> /// <exception cref="ObjectDisposedException">Thrown if this SQL message queue instance /// has already been disposed</exception> public virtual async Task Enqueue(Message message, IPrincipal principal, CancellationToken cancellationToken = default(CancellationToken)) { if (message == null) { throw new ArgumentNullException(nameof(message)); } CheckDisposed(); var queuedMessage = new QueuedMessage(message, principal, 0); var handler = MessageEnqueued; if (handler != null) { var args = new MessageQueueEventArgs(QueueName, queuedMessage); await MessageEnqueued(this, args); } await _queuedMessages.SendAsync(queuedMessage, cancellationToken); // TODO: handle accepted == false await DiagnosticService.EmitAsync( new DiagnosticEventBuilder(this, DiagnosticEventType.MessageEnqueued) { Detail = "New message enqueued", Message = message, Queue = QueueName }.Build(), cancellationToken); }
/// <summary> /// Called by the message processing loop to process an individual message /// </summary> /// <param name="queuedMessage">The queued message to process</param> /// <param name="cancellationToken">A cancellation token that can be used by the caller /// to cancel message processing operation</param> /// <returns>Returns a task that completes when the queued message is processed</returns> protected virtual async Task ProcessQueuedMessage(QueuedMessage queuedMessage, CancellationToken cancellationToken) { try { queuedMessage = queuedMessage.NextAttempt(); var acknowledged = await HandleMessageReceived(queuedMessage, cancellationToken); if (acknowledged) { await HandleMessageAcknowledged(queuedMessage, cancellationToken); return; } await DiagnosticService.EmitAsync( new DiagnosticEventBuilder(this, DiagnosticEventType.MessageNotAcknowledged) { Message = queuedMessage.Message, Queue = QueueName }.Build(), cancellationToken); if (queuedMessage.Attempts >= _maxAttempts) { await HandleMaximumAttemptsExceeded(queuedMessage, cancellationToken); return; } await HandleAcknowledgementFailure(queuedMessage); DiagnosticService.Emit( new DiagnosticEventBuilder(this, DiagnosticEventType.QueuedMessageRetry) { Detail = "Message not acknowledged; retrying in " + _retryDelay, Message = queuedMessage.Message, Queue = QueueName }.Build()); ScheduleRetry(queuedMessage, cancellationToken); } catch (Exception ex) { // Catch all. Do not let any exceptions propagate outside of this method! DiagnosticService.Emit( new DiagnosticEventBuilder(this, DiagnosticEventType.QueuedMessageProcessingError) { Detail = "Unhandled exception processing queued message", Message = queuedMessage.Message, Queue = QueueName, Exception = ex }.Build()); } }
private async Task <bool> HandleMessageReceived(QueuedMessage queuedMessage, CancellationToken cancellationToken) { Message message = null; var acknowledged = false; IPrincipal originalThreadPrincipal = null; try { originalThreadPrincipal = Thread.CurrentPrincipal; message = queuedMessage.Message; queuedMessage = queuedMessage.NextAttempt(); await DiagnosticService.EmitAsync( new DiagnosticEventBuilder(this, DiagnosticEventType.QueuedMessageAttempt) { Detail = "Processing queued message (attempt " + queuedMessage.Attempts + " of " + _maxAttempts + ")", Message = message, Queue = QueueName }.Build(), cancellationToken); var context = new QueuedMessageContext(message, queuedMessage.Principal); Thread.CurrentPrincipal = context.Principal; cancellationToken.ThrowIfCancellationRequested(); await _listener.MessageReceived(message, context, cancellationToken); if (_autoAcknowledge && !context.Acknowledged) { await context.Acknowledge(); } acknowledged = context.Acknowledged; } catch (Exception ex) { DiagnosticService.Emit(new DiagnosticEventBuilder(this, DiagnosticEventType.UnhandledException) { Detail = "Unhandled exception handling queued message", Message = message, Queue = QueueName, Exception = ex }.Build()); } finally { Thread.CurrentPrincipal = originalThreadPrincipal; } return(acknowledged); }
private void ScheduleRetry(QueuedMessage queuedMessage, CancellationToken cancellationToken) { Task.Factory.StartNew(() => { Task.Delay(_retryDelay, cancellationToken).Wait(cancellationToken); if (cancellationToken.IsCancellationRequested) { return; } _queuedMessages.Post(queuedMessage); DiagnosticService.EmitAsync( new DiagnosticEventBuilder(this, DiagnosticEventType.MessageRequeued) { Detail = "Message requeued (retry)", Message = queuedMessage.Message, Queue = QueueName }.Build(), cancellationToken); }, TaskCreationOptions.LongRunning); }
private async Task HandleAcknowledgementFailure(QueuedMessage queuedMessage) { try { var acknowledgementFailureHandlers = AcknowledgementFailure; if (acknowledgementFailureHandlers != null) { var eventArgs = new MessageQueueEventArgs(QueueName, queuedMessage, null); await AcknowledgementFailure(this, eventArgs); } } catch (Exception ex) { DiagnosticService.Emit(new DiagnosticEventBuilder(this, DiagnosticEventType.MessageAcknowledgementError) { Detail = "Unexpected error handling message acknowledgement failure", Message = queuedMessage.Message, Queue = QueueName, Exception = ex }.Build()); } }
/// <summary> /// Called by the message processing loop to process an individual message /// </summary> /// <param name="queuedMessage">The queued message to process</param> /// <param name="cancellationToken">A cancellation token that can be used by the caller /// to cancel message processing operation</param> /// <returns>Returns a task that completes when the queued message is processed</returns> protected virtual async Task ProcessQueuedMessage(QueuedMessage queuedMessage, CancellationToken cancellationToken) { Exception exception = null; var message = queuedMessage.Message; var principal = queuedMessage.Principal; // ReSharper disable once ConditionIsAlwaysTrueOrFalse queuedMessage = queuedMessage.NextAttempt(); await DiagnosticService.EmitAsync( new DiagnosticEventBuilder(this, DiagnosticEventType.QueuedMessageAttempt) { Detail = "Processing queued message (attempt " + queuedMessage.Attempts + " of " + _maxAttempts + ")", Message = message, Queue = QueueName }.Build(), cancellationToken); var context = new QueuedMessageContext(message, principal); Thread.CurrentPrincipal = context.Principal; cancellationToken.ThrowIfCancellationRequested(); try { await _listener.MessageReceived(message, context, cancellationToken); if (_autoAcknowledge && !context.Acknowledged) { await context.Acknowledge(); } } catch (Exception ex) { exception = ex; DiagnosticService.Emit(new DiagnosticEventBuilder(this, DiagnosticEventType.UnhandledException) { Detail = "Unhandled exception handling queued message", Message = message, Queue = QueueName, Exception = ex }.Build()); } var eventArgs = new MessageQueueEventArgs(QueueName, queuedMessage, exception); if (context.Acknowledged) { await DiagnosticService.EmitAsync( new DiagnosticEventBuilder(this, DiagnosticEventType.MessageAcknowledged) { Message = message, Queue = QueueName }.Build(), cancellationToken); var messageAcknowledgedHandlers = MessageAcknowledged; if (messageAcknowledgedHandlers != null) { await messageAcknowledgedHandlers(this, eventArgs); } return; } await DiagnosticService.EmitAsync( new DiagnosticEventBuilder(this, DiagnosticEventType.MessageNotAcknowledged) { Message = message, Queue = QueueName }.Build(), cancellationToken); if (queuedMessage.Attempts >= _maxAttempts) { await DiagnosticService.EmitAsync( new DiagnosticEventBuilder(this, DiagnosticEventType.MaxAttemptsExceeded) { Detail = "Maximum attempts exceeded (" + _maxAttempts + ")", Message = message, Queue = QueueName }.Build(), cancellationToken); var maxAttemptsExceededHandlers = MaximumAttemptsExceeded; if (maxAttemptsExceededHandlers != null) { await MaximumAttemptsExceeded(this, eventArgs); } return; } var acknowledgementFailureHandlers = AcknowledgementFailure; if (acknowledgementFailureHandlers != null) { await AcknowledgementFailure(this, eventArgs); } await DiagnosticService.EmitAsync( new DiagnosticEventBuilder(this, DiagnosticEventType.QueuedMessageRetry) { Detail = "Message not acknowledged; retrying in " + _retryDelay, Message = message, Queue = QueueName }.Build(), cancellationToken); ScheduleRetry(queuedMessage, cancellationToken); }
/// <summary> /// Initializes a new <see cref="MessageQueueEventArgs"/> /// </summary> /// <param name="queue">The name of the queue from which the message was read</param> /// <param name="queuedMessage">The queued message for which the handling attempt was made</param> /// <param name="exception">The exception that was caught</param> public MessageQueueEventArgs(QueueName queue, QueuedMessage queuedMessage, Exception exception) { Queue = queue; QueuedMessage = queuedMessage; Exception = exception; }
/// <summary> /// Initializes a new <see cref="MessageQueueEventArgs"/> /// </summary> /// <param name="queue">The name of the queue from which the message was read</param> /// <param name="queuedMessage">The queued message for which the handling attempt was made</param> public MessageQueueEventArgs(QueueName queue, QueuedMessage queuedMessage) { Queue = queue; QueuedMessage = queuedMessage; }