async Task ProcessFile(ILearningTransportTransaction transaction, string messageId) { string message; try { message = await AsyncFile.ReadText(transaction.FileToProcess) .ConfigureAwait(false); } catch (Exception e) { Console.WriteLine(e); throw; } var bodyPath = Path.Combine(bodyDir, $"{messageId}{BodyFileSuffix}"); var headers = HeaderSerializer.Deserialize(message); if (headers.TryGetValue(Headers.TimeToBeReceived, out var ttbrString)) { var ttbr = TimeSpan.Parse(ttbrString); //file.move preserves create time var sentTime = File.GetCreationTimeUtc(transaction.FileToProcess); if (sentTime + ttbr < DateTime.UtcNow) { await transaction.Commit() .ConfigureAwait(false); return; } } var tokenSource = new CancellationTokenSource(); var body = await AsyncFile.ReadBytes(bodyPath, cancellationToken) .ConfigureAwait(false); var transportTransaction = new TransportTransaction(); if (transactionMode == TransportTransactionMode.SendsAtomicWithReceive) { transportTransaction.Set(transaction); } var messageContext = new MessageContext(messageId, headers, body, transportTransaction, tokenSource, new ContextBag()); try { await onMessage(messageContext) .ConfigureAwait(false); } catch (Exception exception) { transaction.ClearPendingOutgoingOperations(); var processingFailures = retryCounts.AddOrUpdate(messageId, id => 1, (id, currentCount) => currentCount + 1); var errorContext = new ErrorContext(exception, headers, messageId, body, transportTransaction, processingFailures); // the transport tests assume that all transports use a circuit breaker to be resillient against exceptions // in onError. Since we don't need that robustness we just retry onError once should it fail. ErrorHandleResult actionToTake; try { actionToTake = await onError(errorContext) .ConfigureAwait(false); } catch (Exception) { actionToTake = await onError(errorContext) .ConfigureAwait(false); } if (actionToTake == ErrorHandleResult.RetryRequired) { transaction.Rollback(); return; } } if (tokenSource.IsCancellationRequested) { transaction.Rollback(); return; } await transaction.Commit() .ConfigureAwait(false); }
/// <returns>A <see cref="Dictionary{TKey, TValue}"/> of headers, and a <see cref="bool"/> indicating whether onMessage failed.</returns> async Task <(Dictionary <string, string>, bool)> ProcessFile(ILearningTransportTransaction transaction, string messageId, ContextBag processingContext, CancellationToken messageProcessingCancellationToken) { var message = await AsyncFile.ReadText(transaction.FileToProcess, messageProcessingCancellationToken) .ConfigureAwait(false); var bodyPath = Path.Combine(bodyDir, $"{messageId}{BodyFileSuffix}"); var headers = HeaderSerializer.Deserialize(message); var onMessageFailed = false; if (headers.TryGetValue(LearningTransportHeaders.TimeToBeReceived, out var ttbrString)) { headers.Remove(LearningTransportHeaders.TimeToBeReceived); var ttbr = TimeSpan.Parse(ttbrString); //file.move preserves create time var sentTime = File.GetCreationTimeUtc(transaction.FileToProcess); var utcNow = DateTime.UtcNow; if (sentTime + ttbr < utcNow) { await transaction.Commit(messageProcessingCancellationToken) .ConfigureAwait(false); log.InfoFormat("Dropping message '{0}' as the specified TimeToBeReceived of '{1}' expired since sending the message at '{2:O}'. Current UTC time is '{3:O}'", messageId, ttbrString, sentTime, utcNow); return(headers, onMessageFailed); } } var body = await AsyncFile.ReadBytes(bodyPath, messageProcessingCancellationToken) .ConfigureAwait(false); var transportTransaction = new TransportTransaction(); if (transactionMode == TransportTransactionMode.SendsAtomicWithReceive) { transportTransaction.Set(transaction); } var messageContext = new MessageContext(messageId, headers, body, transportTransaction, processingContext); try { await onMessage(messageContext, messageProcessingCancellationToken) .ConfigureAwait(false); } catch (OperationCanceledException ex) when(messageProcessingCancellationToken.IsCancellationRequested) { log.Info("Message processing cancelled. Rolling back transaction.", ex); transaction.Rollback(); return(headers, onMessageFailed); } catch (Exception exception) { onMessageFailed = true; transaction.ClearPendingOutgoingOperations(); var processingFailures = retryCounts.AddOrUpdate(messageId, id => 1, (id, currentCount) => currentCount + 1); headers = HeaderSerializer.Deserialize(message); headers.Remove(LearningTransportHeaders.TimeToBeReceived); var errorContext = new ErrorContext(exception, headers, messageId, body, transportTransaction, processingFailures, processingContext); ErrorHandleResult actionToTake; try { actionToTake = await onError(errorContext, messageProcessingCancellationToken) .ConfigureAwait(false); } catch (OperationCanceledException ex) when(messageProcessingCancellationToken.IsCancellationRequested) { log.Info("Message processing cancelled. Rolling back transaction.", ex); transaction.Rollback(); return(headers, onMessageFailed); } catch (Exception ex) { criticalErrorAction($"Failed to execute recoverability policy for message with native ID: `{messageContext.NativeMessageId}`", ex, CancellationToken.None); actionToTake = ErrorHandleResult.RetryRequired; } if (actionToTake == ErrorHandleResult.RetryRequired) { transaction.Rollback(); return(headers, onMessageFailed); } } await transaction.Commit(messageProcessingCancellationToken) .ConfigureAwait(false); return(headers, onMessageFailed); }