public void ChangeSkewBasedOnRequestEndpointAndNotConfigEndpoint()
        {
            Uri configEndpoint  = new Uri("https://s3.amazonaws.com");
            Uri requestEndpoint = new Uri("https://bucketname.s3.amazonaws.com");

            ReflectionHelpers.Invoke(typeof(CorrectClockSkew), "SetClockCorrectionForEndpoint",
                                     new object[] { configEndpoint.ToString(), TimeSpan.FromHours(-1) });
            ReflectionHelpers.Invoke(typeof(CorrectClockSkew), "SetClockCorrectionForEndpoint",
                                     new object[] { requestEndpoint.ToString(), TimeSpan.Zero });

            Assert.AreEqual(TimeSpan.Zero, CorrectClockSkew.GetClockCorrectionForEndpoint(requestEndpoint.ToString()));

            Tester.Reset();
            Tester.Action = (int callCount) =>
            {
                var timeString = DateTime.UtcNow.AddHours(-1).ToString(AWSSDKUtils.ISO8601BasicDateTimeFormat);
                var exception  = new AmazonS3Exception("(" + timeString + " - ");
                exception.ErrorCode = "RequestTimeTooSkewed";
                throw exception;
            };

            Utils.AssertExceptionExpected(() =>
            {
                var request = CreateTestContext();
                request.RequestContext.Request.Endpoint = requestEndpoint;
                RuntimePipeline.InvokeSync(request);
            },
                                          typeof(AmazonServiceException));

            // RetryPolicy should see that the clock skew for bucketname.s3.amazonaws.com is zero and change it to ~ -1 hour
            Assert.IsTrue(CorrectClockSkew.GetClockCorrectionForEndpoint(requestEndpoint.ToString()) < TimeSpan.FromMinutes(-55));
        }
Пример #2
0
        internal async Task ConsumeDelayedMessages(ReceiveMessageRequest request, CancellationToken token)
        {
            var receivedMessages = await sqsClient.ReceiveMessageAsync(request, token).ConfigureAwait(false);

            if (receivedMessages.Messages.Count == 0)
            {
                return;
            }

            var clockCorrection  = CorrectClockSkew.GetClockCorrectionForEndpoint(awsEndpointUrl);
            var preparedMessages = PrepareMessages(token, receivedMessages, clockCorrection);

            token.ThrowIfCancellationRequested();

            await BatchDispatchPreparedMessages(preparedMessages).ConfigureAwait(false);
        }
Пример #3
0
        async Task ProcessMessage(Message receivedMessage, CancellationToken token)
        {
            try
            {
                byte[]           messageBody      = null;
                TransportMessage transportMessage = null;
                Exception        exception        = null;
                var    nativeMessageId            = receivedMessage.MessageId;
                string messageId       = null;
                var    isPoisonMessage = false;

                try
                {
                    if (receivedMessage.MessageAttributes.TryGetValue(Headers.MessageId, out var messageIdAttribute))
                    {
                        messageId = messageIdAttribute.StringValue;
                    }
                    else
                    {
                        messageId = nativeMessageId;
                    }

                    transportMessage = SimpleJson.DeserializeObject <TransportMessage>(receivedMessage.Body);
                    messageBody      = await transportMessage.RetrieveBody(s3Client, configuration, token).ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    // shutting down
                    return;
                }
                catch (Exception ex)
                {
                    // Can't deserialize. This is a poison message
                    exception       = ex;
                    isPoisonMessage = true;
                }

                if (isPoisonMessage || messageBody == null || transportMessage == null)
                {
                    var logMessage = $"Treating message with {messageId} as a poison message. Moving to error queue.";

                    if (exception != null)
                    {
                        Logger.Warn(logMessage, exception);
                    }
                    else
                    {
                        Logger.Warn(logMessage);
                    }

                    await MovePoisonMessageToErrorQueue(receivedMessage, messageId).ConfigureAwait(false);

                    return;
                }

                if (!IsMessageExpired(receivedMessage, transportMessage.Headers, messageId, CorrectClockSkew.GetClockCorrectionForEndpoint(awsEndpointUrl)))
                {
                    // here we also want to use the native message id because the core demands it like that
                    await ProcessMessageWithInMemoryRetries(transportMessage.Headers, nativeMessageId, messageBody, token).ConfigureAwait(false);
                }

                // Always delete the message from the queue.
                // If processing failed, the onError handler will have moved the message
                // to a retry queue.
                await DeleteMessageAndBodyIfRequired(receivedMessage, transportMessage.S3BodyKey).ConfigureAwait(false);
            }
            finally
            {
                maxConcurrencySemaphore.Release();
            }
        }
Пример #4
0
        async Task ConsumeDelayedMessages(ReceiveMessageRequest request, CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                try
                {
                    var receivedMessages = await sqsClient.ReceiveMessageAsync(request, token).ConfigureAwait(false);

                    var clockCorrection = CorrectClockSkew.GetClockCorrectionForEndpoint(awsEndpointUrl);

                    foreach (var receivedMessage in receivedMessages.Messages)
                    {
                        long delaySeconds = 0;

                        if (receivedMessage.MessageAttributes.TryGetValue(TransportHeaders.DelaySeconds, out var delayAttribute))
                        {
                            long.TryParse(delayAttribute.StringValue, out delaySeconds);
                        }

                        var sent     = receivedMessage.GetAdjustedDateTimeFromServerSetAttributes("SentTimestamp", clockCorrection);
                        var received = receivedMessage.GetAdjustedDateTimeFromServerSetAttributes("ApproximateFirstReceiveTimestamp", clockCorrection);

                        if (Convert.ToInt32(receivedMessage.Attributes["ApproximateReceiveCount"]) > 1)
                        {
                            received = DateTime.UtcNow;
                        }

                        var elapsed = received - sent;

                        var remainingDelay = delaySeconds - (long)elapsed.TotalSeconds;

                        SendMessageRequest sendMessageRequest;

                        if (remainingDelay > configuration.DelayedDeliveryQueueDelayTime)
                        {
                            sendMessageRequest = new SendMessageRequest(delayedDeliveryQueueUrl, receivedMessage.Body)
                            {
                                MessageAttributes =
                                {
                                    [TransportHeaders.DelaySeconds] = new MessageAttributeValue
                                    {
                                    StringValue = remainingDelay.ToString(),
                                    DataType    = "String"
                                    }
                                }
                            };

                            var deduplicationId = receivedMessage.Attributes["MessageDeduplicationId"];

                            // this is only here for acceptance testing purpose. In real prod code this is always false.
                            if (configuration.DelayedDeliveryQueueDelayTime < TransportConfiguration.AwsMaximumQueueDelayTime)
                            {
                                deduplicationId = Guid.NewGuid().ToString();
                            }

                            sendMessageRequest.MessageDeduplicationId = sendMessageRequest.MessageGroupId = deduplicationId;
                        }
                        else
                        {
                            sendMessageRequest = new SendMessageRequest(queueUrl, receivedMessage.Body);

                            if (remainingDelay > 0)
                            {
                                sendMessageRequest.DelaySeconds = Convert.ToInt32(remainingDelay);
                            }
                        }

                        try
                        {
                            await sqsClient.SendMessageAsync(sendMessageRequest, CancellationToken.None)
                            .ConfigureAwait(false);
                        }
                        catch (Exception ex)
                        {
                            Logger.Debug("ConsumeDelayedMessages -> SendMessageAsync failed", ex);

                            await sqsClient.ChangeMessageVisibilityAsync(request.QueueUrl, receivedMessage.ReceiptHandle, 0, CancellationToken.None)
                            .ConfigureAwait(false);

                            continue;
                        }

                        try
                        {
                            await sqsClient.DeleteMessageAsync(delayedDeliveryQueueUrl, receivedMessage.ReceiptHandle, CancellationToken.None)
                            .ConfigureAwait(false);
                        }
                        catch (ReceiptHandleIsInvalidException ex)
                        {
                            Logger.Info($"Message receipt handle {receivedMessage.ReceiptHandle} no longer valid.", ex);
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    // ignore for graceful shutdown
                }
                catch (Exception ex)
                {
                    Logger.Error("Exception thrown when consuming delayed messages", ex);
                }
            }
        }
        async Task ProcessMessage(SQSEvent.SQSMessage receivedMessage, ILambdaContext lambdaContext, CancellationToken token)
        {
            byte[]           messageBody      = null;
            TransportMessage transportMessage = null;
            Exception        exception        = null;
            var    nativeMessageId            = receivedMessage.MessageId;
            string messageId       = null;
            var    isPoisonMessage = false;

            try
            {
                if (receivedMessage.MessageAttributes.TryGetValue(Headers.MessageId, out var messageIdAttribute))
                {
                    messageId = messageIdAttribute.StringValue;
                }
                else
                {
                    messageId = nativeMessageId;
                }

                transportMessage = SimpleJson.DeserializeObject <TransportMessage>(receivedMessage.Body);

                messageBody = await transportMessage.RetrieveBody(s3Client, s3BucketForLargeMessages, token).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                return;
            }
            catch (Exception ex)
            {
                // Can't deserialize. This is a poison message
                exception       = ex;
                isPoisonMessage = true;
            }

            if (isPoisonMessage || messageBody == null || transportMessage == null)
            {
                LogPoisonMessage(messageId, exception);

                await MovePoisonMessageToErrorQueue(receivedMessage, messageId).ConfigureAwait(false);

                return;
            }

            if (!IsMessageExpired(receivedMessage, transportMessage.Headers, messageId, CorrectClockSkew.GetClockCorrectionForEndpoint(awsEndpointUrl)))
            {
                // here we also want to use the native message id because the core demands it like that
                await ProcessMessageWithInMemoryRetries(transportMessage.Headers, nativeMessageId, messageBody, lambdaContext, token).ConfigureAwait(false);
            }

            // Always delete the message from the queue.
            // If processing failed, the onError handler will have moved the message
            // to a retry queue.
            await DeleteMessageAndBodyIfRequired(receivedMessage, transportMessage.S3BodyKey).ConfigureAwait(false);
        }
        async Task ConsumeDelayedMessages(ReceiveMessageRequest request, CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                try
                {
                    var receivedMessages = await sqsClient.ReceiveMessageAsync(request, token).ConfigureAwait(false);

                    var clockCorrection = CorrectClockSkew.GetClockCorrectionForEndpoint(awsEndpointUrl);

                    foreach (var receivedMessage in receivedMessages.Messages)
                    {
                        long delaySeconds = 0;

                        if (receivedMessage.MessageAttributes.TryGetValue(TransportHeaders.DelaySeconds, out var delayAttribute))
                        {
                            long.TryParse(delayAttribute.StringValue, out delaySeconds);
                        }

                        string messageId = null;
                        if (receivedMessage.MessageAttributes.TryGetValue(Headers.MessageId, out var messageIdAttribute))
                        {
                            messageId = messageIdAttribute.StringValue;
                        }

                        var sent     = receivedMessage.GetAdjustedDateTimeFromServerSetAttributes("SentTimestamp", clockCorrection);
                        var received = receivedMessage.GetAdjustedDateTimeFromServerSetAttributes("ApproximateFirstReceiveTimestamp", clockCorrection);

                        if (Convert.ToInt32(receivedMessage.Attributes["ApproximateReceiveCount"]) > 1)
                        {
                            received = DateTime.UtcNow;
                        }

                        var elapsed = received - sent;

                        var remainingDelay = delaySeconds - (long)elapsed.TotalSeconds;

                        SendMessageRequest sendMessageRequest;

                        if (remainingDelay > configuration.DelayedDeliveryQueueDelayTime)
                        {
                            sendMessageRequest = new SendMessageRequest(delayedDeliveryQueueUrl, receivedMessage.Body)
                            {
                                MessageAttributes =
                                {
                                    [TransportHeaders.DelaySeconds] = new MessageAttributeValue
                                    {
                                    StringValue = remainingDelay.ToString(),
                                    DataType    = "String"
                                    }
                                }
                            };

                            var deduplicationId = receivedMessage.Attributes["MessageDeduplicationId"];

                            // this is only here for acceptance testing purpose. In real prod code this is always false.
                            // it allows us to fake multiple cycles over the FIFO queue without being subjected to deduplication
                            if (configuration.DelayedDeliveryQueueDelayTime < TransportConfiguration.AwsMaximumQueueDelayTime)
                            {
                                deduplicationId = Guid.NewGuid().ToString();
                            }

                            sendMessageRequest.MessageDeduplicationId = sendMessageRequest.MessageGroupId = deduplicationId;
                        }
                        else
                        {
                            sendMessageRequest = new SendMessageRequest(queueUrl, receivedMessage.Body);

                            if (remainingDelay > 0)
                            {
                                sendMessageRequest.DelaySeconds = Convert.ToInt32(remainingDelay);
                            }
                        }

                        if (string.IsNullOrEmpty(messageId))
                        {
                            // for backward compatibility if we couldn't fetch the message id header from the attributes we use the message deduplication id
                            messageId = receivedMessage.Attributes["MessageDeduplicationId"];
                        }

                        // because message attributes are part of the content size restriction we want to prevent message size from changing thus we add it
                        // for native delayed deliver as well
                        sendMessageRequest.MessageAttributes[Headers.MessageId] = new MessageAttributeValue
                        {
                            StringValue = messageId,
                            DataType    = "String"
                        };

                        try
                        {
                            await sqsClient.SendMessageAsync(sendMessageRequest, CancellationToken.None)
                            .ConfigureAwait(false);
                        }
                        catch (Exception ex)
                        {
                            Logger.Debug("ConsumeDelayedMessages -> SendMessageAsync failed", ex);

                            await sqsClient.ChangeMessageVisibilityAsync(request.QueueUrl, receivedMessage.ReceiptHandle, 0, CancellationToken.None)
                            .ConfigureAwait(false);

                            continue;
                        }

                        try
                        {
                            await sqsClient.DeleteMessageAsync(delayedDeliveryQueueUrl, receivedMessage.ReceiptHandle, CancellationToken.None)
                            .ConfigureAwait(false);
                        }
                        catch (ReceiptHandleIsInvalidException ex)
                        {
                            Logger.Info($"Message receipt handle {receivedMessage.ReceiptHandle} no longer valid.", ex);
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    // ignore for graceful shutdown
                }
                catch (Exception ex)
                {
                    Logger.Error("Exception thrown when consuming delayed messages", ex);
                }
            }
        }