private async Task <bool> ExecuteConcrete(ConsumableEvent ce) { ConsumeResult result = null; bool mustRollback = false; try { LogTrace("Processing event with id {Id} and functional key {FunctionalKey}.", ce.Id, ce.FunctionalKey != null ? ce.FunctionalKey : "n/a"); result = await _consumeAction(ce).ConfigureAwait(false); } catch (Exception procEx) { // Unhandled exception is translated to Failed result = ConsumeResult.Failed(procEx.ToString()); } LogTrace("Processing done for event with id {Id} and functional key {FunctionalKey}. Result: {result}", ce.Id, ce.FunctionalKey != null ? ce.FunctionalKey : "n/a", result); switch (result.ResultType) { case ConsumeResultType.Succeeded: { bool markedConsumed = false; try { await _eventConsumer.MarkConsumedAsync(ce.Id, ce.DeliveryKey).ConfigureAwait(false); markedConsumed = true; LogTrace("Event consumption succeeded for event with id {Id} and functional key {FunctionalKey}.", ce.Id, ce.FunctionalKey != null ? ce.FunctionalKey : "n/a"); } catch (Exception ex) { LogError("Failed to mark event consumed with id {Id} and functional key {FunctionalKey}, cause event to be processes again! Details: {Exception}.", ce.Id, ce.FunctionalKey != null ? ce.FunctionalKey : "n/a", ex); // mustRollback doesn't actually need to be set, since markedConsumed will no longer be true mustRollback = true; } if (!markedConsumed) { mustRollback = true; } } break; case ConsumeResultType.Failed: { mustRollback = true; LogError("Exception occurred while processing event with id {Id} and functional key {FunctionalKey}: {Reason}.", ce.Id, ce.FunctionalKey != null ? ce.FunctionalKey : "n/a", result.Reason); try { await _eventConsumer.MarkFailedAsync(ce.Id, ce.DeliveryKey, Reason.Other(result.Reason)).ConfigureAwait(false); } catch (Exception) { } // Swallow, since it's not a disaster if this gets processed again } break; case ConsumeResultType.MustSuspend: { mustRollback = true; var suspendedUntilUtc = this.Suspend(result.SuspendDuration.GetValueOrDefault(TimeSpan.FromSeconds(60))); LogError("Event consumption failed for event with id {Id} and functional key {FunctionalKey}. Processing should (continue to) suspend. Details: {Reason}.", ce.Id, ce.FunctionalKey != null ? ce.FunctionalKey : "n/a", result.Reason); } break; case ConsumeResultType.MustRetry: { mustRollback = true; LogWarning("Retry requested while processing event with id {Id} and functional key {FunctionalKey}: {Reason}.", ce.Id, ce.FunctionalKey != null ? ce.FunctionalKey : "n/a", result.Reason); // Logged as warning, since a requested retry is not (yet) an error } break; default: // No special handling for other cases break; } return(result.ResultType == ConsumeResultType.Succeeded && !mustRollback); }
private async Task <bool> ExecuteConcrete(IEnumerable <ConsumableEvent> ces) { IDictionary <Int64, ConsumeResult> result = null; try { LogTrace("Processing batch of {batchSize} events", ces.Count()); result = await _consumeBatchAction(ces).ConfigureAwait(false); if (result == null) { throw new InvalidOperationException("consumeBatchAction returned invalid result (null)"); } } catch (Exception procEx) { // Unhandled exception is translated to Failed for the whole batch result = ces.ToDictionary(ce => ce.Id, ce => ConsumeResult.Failed(procEx.ToString())); } var succeededResults = result .Where(r => r.Value.ResultType == ConsumeResultType.Succeeded) .Select(r => ces.Single(ce => ce.Id == r.Key)) .Cast <ConsumableEventId>(); if (succeededResults.Count() > 0) { LogTrace("Event consumption succeeded for {succeededCount} event(s) in the batch of {totalCount}.", succeededResults.Count(), ces.Count()); try { await _eventConsumer.MarkConsumedAsync(succeededResults, transactional : !_subscription.Ordered).ConfigureAwait(false); // Transactional is not required when processing is ordered: the events will be retried and cannot be overtaken. } catch (Exception ex) { LogError("Failed to mark set of {succeededCount} consumed, cause event(s) to be processes again! Details: {Exception}.", succeededResults.Count(), ex); } } var failedResults = result .Where(r => r.Value.ResultType == ConsumeResultType.Failed) .Select(r => new { ConsumableEvent = ces.Single(ce => ce.Id == r.Key), ConsumeResult = r.Value, }); if (failedResults.Count() > 0) { foreach (var failedResult in failedResults) { LogError("Exception occurred while processing event with id {Id} and functional key {FunctionalKey}: {Reason}.", failedResult.ConsumableEvent.Id, failedResult.ConsumableEvent.FunctionalKey != null ? failedResult.ConsumableEvent.FunctionalKey : "n/a", failedResult.ConsumeResult.Reason); try { await _eventConsumer.MarkFailedAsync(failedResult.ConsumableEvent.Id, failedResult.ConsumableEvent.DeliveryKey, Reason.Other(failedResult.ConsumeResult.Reason)); } catch (Exception) { } // Swallow, since it's not a disaster if this gets processed again } } var suspendResultsCount = result .Where(r => r.Value.ResultType == ConsumeResultType.MustSuspend) .Count(); if (suspendResultsCount > 0) { var firstSuspendResult = result.First(r => r.Value.ResultType == ConsumeResultType.MustSuspend); var suspendedUntilUtc = this.Suspend(firstSuspendResult.Value.SuspendDuration.GetValueOrDefault(TimeSpan.FromSeconds(60))); LogError("Event consumption failed for {suspendResultsCount} event(s). Processing should (continue to) suspend. Details: {Reason}.", suspendResultsCount, firstSuspendResult.Value.Reason); } var retryResultsCount = result .Where(r => r.Value.ResultType == ConsumeResultType.MustRetry) .Count(); if (retryResultsCount > 0) { var firstRetryResult = result.First(r => r.Value.ResultType == ConsumeResultType.MustRetry); LogWarning("Retry requested for {retryResultsCount} event(s). Reason for first event: {Reason}.", retryResultsCount, firstRetryResult.Value.Reason); // Logged as warning, since a requested retry is not (yet) an error } return(succeededResults.Count() < ces.Count()); // False when not all have succeeded }