Exemple #1
0
        public async Task SubscribeAsync(Func <PunchyMessage, Task> onMessageAsync, string groupId, CancellationToken cancellationToken, params string[] topics)
        {
            if (onMessageAsync == null)
            {
                throw new ArgumentNullException(nameof(onMessageAsync));
            }

            if (groupId == null)
            {
                throw new ArgumentNullException(nameof(groupId));
            }

            if (!TopicNameHelpers.IsValid(groupId))
            {
                throw new ArgumentException(
                          $"The groupId must match regex pattern: '{TopicNameHelpers.TopicOrGroupIdValidationRegexPattern}'", nameof(groupId));
            }

            var messageHandler = new MessageHandler(onMessageAsync, pubSub, logger, configuration, groupId);
            var retryerTopics  = new List <string>();

            foreach (var topic in topics)
            {
                if (!TopicNameHelpers.IsValid(topic))
                {
                    throw new ArgumentException(
                              $"The topic name must match regex pattern: '{TopicNameHelpers.TopicOrGroupIdValidationRegexPattern}'. " +
                              $"Validation failed for '{topic}'.", nameof(topics));
                }

                if (!TopicNameHelpers.IsBadMessageTopic(topic))
                {
                    for (int i = 0; i < configuration.LevelDelaysInSeconds.Length; i++)
                    {
                        retryerTopics.Add(TopicNameHelpers.BuildBadMessageTopicName(topic, i, groupId));
                    }
                }
            }

            var subscription = pubSub.Subscribe(messageHandler, groupId, topics.Union(retryerTopics).ToArray());

            messageHandler.Subscription = subscription;
            await subscription.StartAsync(cancellationToken);
        }
        private IMessage GetSourceMessage(IMessage message, List <Attempt> attempts)
        {
            var badMessageCurrentLevel = TopicNameHelpers.ExtractBadMessageLevel(message.Topic);

            while (message != null && badMessageCurrentLevel >= 0)
            {
                var badMessageContents = JsonConvert.DeserializeObject <BadMessageContents>(
                    Encoding.UTF8.GetString(message.MessageBytes));

                var attemp = new Attempt()
                {
                    BadMessageContents = badMessageContents,
                    MessageAddress     = message.MessageAddress,
                    Topic     = message.Topic,
                    Timestamp = message.Timestamp
                };

                attempts.Insert(0, attemp);

                // retrying
                var messageTimestamp = message.Timestamp;

                var messageAgeMilliseconds = (int)Math.Round((DateTimeOffset.Now - messageTimestamp).TotalMilliseconds);
                var delayMilliseconds      = configuration.LevelDelaysInSeconds[badMessageCurrentLevel] * 1000;

                if (messageAgeMilliseconds >= delayMilliseconds)
                {
                    // the message is being replaced here with the original message
                    message = pubSub.ReadSingle(badMessageContents.SourceMessageAddress);

                    badMessageCurrentLevel = TopicNameHelpers.ExtractBadMessageLevel(message.Topic);
                }
                else
                {
                    var reloadAt = DateTimeOffset.Now.AddMilliseconds(delayMilliseconds - messageAgeMilliseconds);
                    logger.LogDebug("Retrying message but delay time did not ellapse yet, so ignoring it for now. Will reload PubSub at {ReloadAt}", reloadAt);
                    Subscription?.ReloadAt(reloadAt);
                    message = null; // retry time not ellapsed
                }
            }

            return(message);
        }
Exemple #3
0
 public async Task SubscribeToDeadLetterAsync(Func <PunchyMessage, Task> onMessageAsync, string groupId, CancellationToken cancellationToken, params string[] topics)
 {
     topics = topics.Select(topic => TopicNameHelpers.BuildDeadLetterTopicName(topic, groupId)).ToArray();
     await SubscribeAsync(onMessageAsync, groupId, cancellationToken, topics);
 }
        public async Task OnMessageAsync(IMessage message)
        {
            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            var badMessageNextLevel = TopicNameHelpers.ExtractBadMessageNextLevel(message.Topic);

            string badMessageNextLevelTopicName;

            if (badMessageNextLevel == badMessageMaxLevels)
            {
                badMessageNextLevelTopicName = TopicNameHelpers.BuildDeadLetterTopicName(message.Topic, groupId);
            }
            else
            {
                badMessageNextLevelTopicName = TopicNameHelpers.BuildBadMessageTopicName(message.Topic, badMessageNextLevel, groupId);
            }

            var attempts            = new List <Attempt>();
            var badMessageCandidate = message;

            message = GetSourceMessage(badMessageCandidate, attempts);

            // If message is null, retry time not ellapsed so will ignore this message for now.
            // It will be read again latter

            if (message != null)
            {
                try
                {
                    await onMessageAsync.Invoke(new PunchyMessage()
                    {
                        MessageAddress = message.MessageAddress,
                        MessageBytes   = message.MessageBytes,
                        Timestamp      = message.Timestamp,
                        Topic          = message.Topic,
                        Attempts       = attempts
                    });
                }
                catch (Exception exception)
                {
                    if (badMessageNextLevel < badMessageMaxLevels)
                    {
                        logger.LogWarning(exception, "Action failed for message, moving to bad message level '{BadMessageNextLevel}'. " +
                                          "Will try again in {RetrySeconds}. Topic: {Topic}; Original message address: {@MessageAddress}",
                                          badMessageNextLevel, configuration.LevelDelaysInSeconds[badMessageNextLevel],
                                          message.Topic, message.MessageAddress);
                    }
                    else // is final level
                    {
                        logger.LogError(exception, "Action failed for message, will NOT try again. Topic: {Topic}; Original message address: {@MessageAddress}",
                                        message.Topic, message.MessageAddress);
                    }

                    var badMessageContents = new BadMessageContents()
                    {
                        Exception            = exception,
                        SourceMessageAddress = badMessageCandidate.MessageAddress
                    };

                    await pubSub.PublishAsync(badMessageNextLevelTopicName,
                                              Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(badMessageContents)));
                }

                await badMessageCandidate.CommitAsync();
            }
        }