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;
            }
        }
示例#2
0
        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();
            }
        }