// RabbitMQ retry that will publish a message to the retry queue. // Keep in mind that it might get processed by the other services using the same routing key and wildcards. private async Task <Acknowledgement> TryHandleWithRequeuingAsync <TMessage>(TMessage message, CorrelationContext correlationContext, Func <Task> handle, Func <TMessage, CustomException, IRejectedEvent> onError = null) { var messageName = message.GetType().Name; var retryMessage = correlationContext.Retries == 0 ? string.Empty : $"Retry: {correlationContext.Retries}'."; _logger.LogInformation($"Handling a message: '{messageName}' " + $"with correlation id: '{correlationContext.Id}'. {retryMessage}"); try { await handle(); _logger.LogInformation($"Handled a message: '{messageName}' " + $"with correlation id: '{correlationContext.Id}'. {retryMessage}"); return(new Ack()); } catch (Exception exception) { _logger.LogError(exception, exception.Message); if (exception is CustomException CustomException && onError != null) { var rejectedEvent = onError(message, CustomException); await _busClient.PublishAsync(rejectedEvent, ctx => ctx.UseMessageContext(correlationContext)); _logger.LogInformation($"Published a rejected event: '{rejectedEvent.GetType().Name}' " + $"for the message: '{messageName}' with correlation id: '{correlationContext.Id}'."); return(new Ack()); } if (correlationContext.Retries >= _retries) { await _busClient.PublishAsync(RejectedEvent.For(messageName), ctx => ctx.UseMessageContext(correlationContext)); throw new Exception($"Unable to handle a message: '{messageName}' " + $"with correlation id: '{correlationContext.Id}' " + $"after {correlationContext.Retries} retries.", exception); } _logger.LogInformation($"Unable to handle a message: '{messageName}' " + $"with correlation id: '{correlationContext.Id}', " + $"retry {correlationContext.Retries}/{_retries}..."); return(Retry.In(TimeSpan.FromSeconds(_retryInterval))); } }
// Internal retry for services that subscribe to the multiple events of the same types. // It does not interfere with the routing keys and wildcards (see TryHandleWithRequeuingAsync() below). private async Task <Acknowledgement> TryHandleAsync <TMessage>(TMessage message, CorrelationContext correlationContext, Func <Task> handle, Func <TMessage, CustomException, IRejectedEvent> onError = null) { var currentRetry = 0; var retryPolicy = Policy .Handle <Exception>() .WaitAndRetryAsync(_retries, i => TimeSpan.FromSeconds(_retryInterval)); var messageName = message.GetType().Name; //Use Polly for resiliency return(await retryPolicy.ExecuteAsync <Acknowledgement>(async() => { var scope = _tracer .BuildSpan("executing-handler") .AsChildOf(_tracer.ActiveSpan) .StartActive(true); using (scope) { var span = scope.Span; try { var retryMessage = currentRetry == 0 ? string.Empty : $"Retry: {currentRetry}'."; var preLogMessage = $"Handling a message: '{messageName}' " + $"with correlation id: '{correlationContext.Id}'. {retryMessage}"; _logger.LogInformation(preLogMessage); span.Log(preLogMessage); await handle(); var postLogMessage = $"Handled a message: '{messageName}' " + $"with correlation id: '{correlationContext.Id}'. {retryMessage}"; _logger.LogInformation(postLogMessage); span.Log(postLogMessage); return new Ack(); } catch (Exception exception) { currentRetry++; _logger.LogError(exception, exception.Message); span.Log(exception.Message); span.SetTag(Tags.Error, true); if (exception is CustomException CustomException && onError != null) { var rejectedEvent = onError(message, CustomException); await _busClient.PublishAsync(rejectedEvent, ctx => ctx.UseMessageContext(correlationContext)); _logger.LogInformation($"Published a rejected event: '{rejectedEvent.GetType().Name}' " + $"for the message: '{messageName}' with correlation id: '{correlationContext.Id}'."); span.SetTag("error-type", "domain"); // the reason is that in this case this is “domain exception” and retrying it will not do the //trick. For instance if user’s password is incorrect (because of same domain validation) // retrying the processing will not make it valid. Thus for this specific type of exceptions // we return explicit ACK. return new Ack(); } span.SetTag("error-type", "infrastructure"); throw new Exception($"Unable to handle a message: '{messageName}' " + $"with correlation id: '{correlationContext.Id}', " + $"retry {currentRetry - 1}/{_retries}..."); } } })); }