public async Task Dispatch(TransportOperations outgoingMessages, TransportTransaction transaction, ContextBag context) { try { foreach (var unicastMessage in outgoingMessages.UnicastTransportOperations) { var sqsTransportMessage = new SqsTransportMessage(unicastMessage.Message, unicastMessage.DeliveryConstraints); var serializedMessage = JsonConvert.SerializeObject(sqsTransportMessage); if (serializedMessage.Length > 256 * 1024) { if (string.IsNullOrEmpty(ConnectionConfiguration.S3BucketForLargeMessages)) { throw new InvalidOperationException("Cannot send large message because no S3 bucket was configured. Add an S3 bucket name to your configuration."); } var key = ConnectionConfiguration.S3KeyPrefix + "/" + unicastMessage.Message.MessageId; await S3Client.PutObjectAsync(new Amazon.S3.Model.PutObjectRequest { BucketName = ConnectionConfiguration.S3BucketForLargeMessages, InputStream = new MemoryStream(unicastMessage.Message.Body), Key = key }).ConfigureAwait(false); sqsTransportMessage.S3BodyKey = key; sqsTransportMessage.Body = String.Empty; serializedMessage = JsonConvert.SerializeObject(sqsTransportMessage); } try { await SendMessage(serializedMessage, unicastMessage.Destination, unicastMessage.DeliveryConstraints).ConfigureAwait(false); } catch (QueueDoesNotExistException) { await QueueCreator.CreateQueueIfNecessary(unicastMessage.Destination).ConfigureAwait(false); await SendMessage(serializedMessage, unicastMessage.Destination, unicastMessage.DeliveryConstraints).ConfigureAwait(false); } } } catch (Exception e) { Logger.Error("Exception from Send.", e); throw; } }
public void Send(TransportMessage message, SendOptions sendOptions) { try { var sqsTransportMessage = new SqsTransportMessage(message, sendOptions); var serializedMessage = JsonConvert.SerializeObject(sqsTransportMessage); if (serializedMessage.Length > 256 * 1024) { if (string.IsNullOrEmpty(ConnectionConfiguration.S3BucketForLargeMessages)) { throw new InvalidOperationException("Cannot send large message because no S3 bucket was configured. Add an S3 bucket name to your configuration."); } var key = ConnectionConfiguration.S3KeyPrefix + "/" + message.Id; S3Client.PutObject(new Amazon.S3.Model.PutObjectRequest { BucketName = ConnectionConfiguration.S3BucketForLargeMessages, InputStream = new MemoryStream(message.Body), Key = key }); sqsTransportMessage.S3BodyKey = key; sqsTransportMessage.Body = String.Empty; serializedMessage = JsonConvert.SerializeObject(sqsTransportMessage); } try { SendMessage(serializedMessage, sendOptions); } catch (QueueDoesNotExistException) { QueueCreator.CreateQueueIfNecessary(sendOptions.Destination, ""); SendMessage(serializedMessage, sendOptions); } } catch (Exception e) { Logger.Error("Exception from Send.", e); throw; } }
async Task DeleteMessage(IAmazonSQS sqs, IAmazonS3 s3, Message message, SqsTransportMessage sqsTransportMessage, IncomingMessage incomingMessage) { await sqs.DeleteMessageAsync(_queueUrl, message.ReceiptHandle, _cancellationTokenSource.Token).ConfigureAwait(false); if (sqsTransportMessage != null) { if (!String.IsNullOrEmpty(sqsTransportMessage.S3BodyKey)) { try { await s3.DeleteObjectAsync( new DeleteObjectRequest { BucketName = ConnectionConfiguration.S3BucketForLargeMessages, Key = ConnectionConfiguration.S3KeyPrefix + incomingMessage.MessageId }, _cancellationTokenSource.Token).ConfigureAwait(false); } catch (Exception ex) { // If deleting the message body from S3 fails, we don't // want the exception to make its way through to the _endProcessMessage below, // as the message has been successfully processed and deleted from the SQS queue // and effectively doesn't exist anymore. // It doesn't really matter, as S3 is configured to delete message body data // automatically after a certain period of time. Logger.Warn("Couldn't delete message body from S3. Message body data will be aged out by the S3 lifecycle policy when the TTL expires.", ex); } } } else { Logger.Warn("Couldn't delete message body from S3 because the SqsTransportMessage was null. Message body data will be aged out by the S3 lifecycle policy when the TTL expires."); } }
private void DeleteMessage(IAmazonSQS sqs, IAmazonS3 s3, Message message, SqsTransportMessage sqsTransportMessage, TransportMessage transportMessage) { sqs.DeleteMessage(_queueUrl, message.ReceiptHandle); if (!String.IsNullOrEmpty(sqsTransportMessage.S3BodyKey)) { // Delete the S3 body asynchronously. // We don't really care too much if this call succeeds or fails - if it fails, // the S3 bucket lifecycle configuration will eventually delete the message anyway. // So, we can get better performance by not waiting around for this call to finish. var s3DeleteTask = s3.DeleteObjectAsync( new DeleteObjectRequest { BucketName = ConnectionConfiguration.S3BucketForLargeMessages, Key = ConnectionConfiguration.S3KeyPrefix + transportMessage.Id }); s3DeleteTask.ContinueWith(t => { if (t.Exception != null) { // If deleting the message body from S3 fails, we don't // want the exception to make its way through to the _endProcessMessage below, // as the message has been successfully processed and deleted from the SQS queue // and effectively doesn't exist anymore. // It doesn't really matter, as S3 is configured to delete message body data // automatically after a certain period of time. Logger.Warn("Couldn't delete message body from S3. Message body data will be aged out at a later time.", t.Exception); } }); } }
async Task ConsumeMessages() { while (!_cancellationTokenSource.IsCancellationRequested) { try { var receiveResult = await SqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { MaxNumberOfMessages = 10, QueueUrl = _queueUrl, WaitTimeSeconds = 20, AttributeNames = new List <String> { "SentTimestamp" } }, _cancellationTokenSource.Token).ConfigureAwait(false); var tasks = receiveResult.Messages.Select(async message => { IncomingMessage incomingMessage = null; SqsTransportMessage sqsTransportMessage = null; TransportTransaction transportTransaction = new TransportTransaction(); ContextBag contextBag = new ContextBag(); var messageProcessedOk = false; var messageExpired = false; var isPoisonMessage = false; var errorHandled = false; try { sqsTransportMessage = JsonConvert.DeserializeObject <SqsTransportMessage>(message.Body); incomingMessage = await sqsTransportMessage.ToIncomingMessage(S3Client, ConnectionConfiguration, _cancellationTokenSource.Token).ConfigureAwait(false); } catch (Exception ex) { // Can't deserialize. This is a poison message Logger.Warn($"Deleting poison message with SQS Message Id {message.MessageId} due to exception {ex}"); isPoisonMessage = true; } if (incomingMessage == null || sqsTransportMessage == null) { Logger.Warn($"Deleting poison message with SQS Message Id {message.MessageId}"); isPoisonMessage = true; } if (isPoisonMessage) { await DeleteMessage(SqsClient, S3Client, message, sqsTransportMessage, incomingMessage).ConfigureAwait(false); } else { // Check that the message hasn't expired string rawTtbr; if (incomingMessage.Headers.TryGetValue(SqsTransportHeaders.TimeToBeReceived, out rawTtbr)) { incomingMessage.Headers.Remove(SqsTransportHeaders.TimeToBeReceived); var timeToBeReceived = TimeSpan.Parse(rawTtbr); if (timeToBeReceived != TimeSpan.MaxValue) { var sentDateTime = message.GetSentDateTime(); var utcNow = DateTime.UtcNow; if (sentDateTime + timeToBeReceived <= utcNow) { // Message has expired. Logger.Warn($"Discarding expired message with Id {incomingMessage.MessageId}"); messageExpired = true; } } } if (!messageExpired) { int immediateProcessingAttempts = 0; while (!errorHandled && !messageProcessedOk) { try { await _maxConcurrencySempahore .WaitAsync(_cancellationTokenSource.Token) .ConfigureAwait(false); using (var messageContextCancellationTokenSource = new CancellationTokenSource()) { var messageContext = new MessageContext( incomingMessage.MessageId, incomingMessage.Headers, incomingMessage.Body, transportTransaction, messageContextCancellationTokenSource, contextBag); await _onMessage(messageContext) .ConfigureAwait(false); messageProcessedOk = !messageContextCancellationTokenSource.IsCancellationRequested; } } catch (Exception ex) when(!(ex is OperationCanceledException && _cancellationTokenSource.IsCancellationRequested)) { immediateProcessingAttempts++; var errorHandlerResult = ErrorHandleResult.RetryRequired; try { errorHandlerResult = await _onError(new ErrorContext(ex, incomingMessage.Headers, incomingMessage.MessageId, incomingMessage.Body, transportTransaction, immediateProcessingAttempts)).ConfigureAwait(false); } catch (Exception onErrorEx) { Logger.Error("Exception thrown from error handler", onErrorEx); } errorHandled = errorHandlerResult == ErrorHandleResult.Handled; } finally { _maxConcurrencySempahore.Release(); } } } // Always delete the message from the queue. // If processing failed, the _onError handler will have moved the message // to a retry queue. await DeleteMessage(SqsClient, S3Client, message, sqsTransportMessage, incomingMessage).ConfigureAwait(false); } }); await Task.WhenAll(tasks).ConfigureAwait(false); } catch (Exception ex) when(!(ex is OperationCanceledException && _cancellationTokenSource.IsCancellationRequested)) { Logger.Error("Exception thrown when consuming messages", ex); } }// while }
void ConsumeMessages() { _tracksRunningThreads.Wait(TimeSpan.FromSeconds(1)); try { while (!_cancellationTokenSource.IsCancellationRequested) { Exception exception = null; var receiveTask = SqsClient.ReceiveMessageAsync(new ReceiveMessageRequest { MaxNumberOfMessages = ConnectionConfiguration.MaxReceiveMessageBatchSize, QueueUrl = _queueUrl, WaitTimeSeconds = 20, AttributeNames = new List <String> { "SentTimestamp" } }, _cancellationTokenSource.Token); receiveTask.Wait(_cancellationTokenSource.Token); foreach (var message in receiveTask.Result.Messages) { TransportMessage transportMessage = null; SqsTransportMessage sqsTransportMessage = null; var messageProcessedOk = false; var messageExpired = false; try { sqsTransportMessage = JsonConvert.DeserializeObject <SqsTransportMessage>(message.Body); transportMessage = sqsTransportMessage.ToTransportMessage(S3Client, ConnectionConfiguration); // Check that the message hasn't expired if (transportMessage.TimeToBeReceived != TimeSpan.MaxValue) { var sentDateTime = message.GetSentDateTime(); if (sentDateTime + transportMessage.TimeToBeReceived <= DateTime.UtcNow) { // Message has expired. Logger.Warn(String.Format("Discarding expired message with Id {0}", transportMessage.Id)); messageExpired = true; } } if (!messageExpired) { messageProcessedOk = _tryProcessMessage(transportMessage); } } catch (Exception ex) { exception = ex; } finally { var deleteMessage = !_isTransactional || (_isTransactional && messageProcessedOk); if (deleteMessage) { DeleteMessage(SqsClient, S3Client, message, sqsTransportMessage, transportMessage); } else { SqsClient.ChangeMessageVisibility(_queueUrl, message.ReceiptHandle, 0); } _endProcessMessage(transportMessage, exception); } } } } finally { _tracksRunningThreads.Release(); } }