Пример #1
0
        public async Task Invoke(IMessage message, uint priority, uint timeToLiveSecs)
        {
            try
            {
                if (this.closed)
                {
                    throw new InvalidOperationException($"Endpoint executor for endpoint {this.Endpoint} is closed.");
                }

                // Get the checkpointer corresponding to the queue for this priority
                ImmutableDictionary <uint, EndpointExecutorFsm> snapshot = this.prioritiesToFsms;
                ICheckpointer checkpointer = snapshot[priority].Checkpointer;

                IMessage storedMessage = await this.messageStore.Add(MessageQueueIdHelper.GetMessageQueueId(this.Endpoint.Id, priority), message, timeToLiveSecs);

                checkpointer.Propose(storedMessage);
                Events.AddMessageSuccess(this, storedMessage.Offset, priority, timeToLiveSecs);

                this.hasMessagesInQueue.Set();
            }
            catch (Exception ex)
            {
                Routing.UserMetricLogger.LogIngressFailureMetric(1, this.Endpoint.IotHubName, message, "storage_failure");
                Events.AddMessageFailure(this, ex);
                throw;
            }
        }
Пример #2
0
        public void TestGetMessageQueueIdWithDefaultPriority()
        {
            // Message queue with default priority should remain as endpoint id
            var endpointId     = Guid.NewGuid().ToString();
            var messageQueueId = MessageQueueIdHelper.GetMessageQueueId(endpointId, Core.RouteFactory.DefaultPriority);

            Assert.Equal(endpointId, messageQueueId);
        }
Пример #3
0
        public async Task UpdatePriorities(IList <uint> priorities, Option <Endpoint> newEndpoint)
        {
            Preconditions.CheckArgument(priorities.Count > 0);
            Events.UpdatePriorities(this, priorities);

            if (this.closed)
            {
                throw new InvalidOperationException($"Endpoint executor for endpoint {this.Endpoint} is closed.");
            }

            // Update priorities by merging the new ones with the existing.
            // We don't ever remove stale priorities, otherwise stored messages
            // pending for a removed priority will never get sent.
            ImmutableDictionary <uint, EndpointExecutorFsm> snapshot        = this.prioritiesToFsms;
            Dictionary <uint, EndpointExecutorFsm>          updatedSnapshot = new Dictionary <uint, EndpointExecutorFsm>(snapshot);

            foreach (uint priority in priorities)
            {
                if (!updatedSnapshot.ContainsKey(priority))
                {
                    string id = MessageQueueIdHelper.GetMessageQueueId(this.Endpoint.Id, priority);

                    // Create a message queue in the store for every priority we have
                    await this.messageStore.AddEndpoint(id);

                    // Create a checkpointer and a FSM for every message queue
                    ICheckpointer checkpointer = await this.checkpointerFactory.CreateAsync(id, this.Endpoint.Id, priority);

                    EndpointExecutorFsm fsm = new EndpointExecutorFsm(this.Endpoint, checkpointer, this.config);

                    // Add it to our dictionary
                    updatedSnapshot.Add(priority, fsm);
                }
                else
                {
                    // Update the existing FSM with the new endpoint
                    EndpointExecutorFsm fsm = updatedSnapshot[priority];
                    await newEndpoint.ForEachAsync(e => fsm.RunAsync(Commands.UpdateEndpoint(e)));
                }
            }

            if (this.prioritiesToFsms.CompareAndSet(snapshot, updatedSnapshot.ToImmutableDictionary()))
            {
                Events.UpdatePrioritiesSuccess(this, updatedSnapshot.Keys.ToList());

                // Update the lastUsedFsm to be the initial one, we always
                // have at least one priority->FSM pair at this point.
                this.lastUsedFsm = updatedSnapshot[priorities[0]];
            }
            else
            {
                Events.UpdatePrioritiesFailure(this, updatedSnapshot.Keys.ToList());
            }
        }
Пример #4
0
        public void TestGetMessageQueueIdWithNonDefaultPriority()
        {
            // Message queue with non-default priority should combain endpoint id and priority with MessageQueueIdHelper.MessageQueueIdDelimiter
            var  endpointId           = Guid.NewGuid().ToString();
            uint priority             = 1234;
            var  expectMessageQueueId = $"{endpointId}{MessageQueueIdHelper.MessageQueueIdDelimiter}{priority}";

            var messageQueueId = MessageQueueIdHelper.GetMessageQueueId(endpointId, priority);

            Assert.Equal(expectMessageQueueId, messageQueueId);
        }
Пример #5
0
        public void TestGetMessageQueueId()
        {
            // Message queue with non-default priority should combain endpoint id and priority with MessageQueueIdHelper.MessageQueueIdDelimiter
            var  endpointId           = "abc";
            uint priority             = 1234;
            var  expectMessageQueueId = "abc_Pri1234";

            var messageQueueId = MessageQueueIdHelper.GetMessageQueueId(endpointId, priority);

            Assert.Equal(expectMessageQueueId, messageQueueId);
        }
Пример #6
0
        public void TestParseMessageQueueIdWithDefaultPriorityAndInvalidSuffixInEndpointId()
        {
            // Endpoint id with delimiter should be ignored
            var  expectedEndpointId = $"{Guid.NewGuid()}{MessageQueueIdHelper.MessageQueueIdDelimiter}4321a";
            var  messageQueueId     = expectedEndpointId;
            uint expectedPriority   = Core.RouteFactory.DefaultPriority;

            var(endpointId, priority) = MessageQueueIdHelper.ParseMessageQueueId(messageQueueId);

            Assert.Equal(expectedEndpointId, endpointId);
            Assert.Equal(expectedPriority, priority);
        }
Пример #7
0
        public void TestParseMessageQueueIdWithPriorityAndDelimiterInEndpointId()
        {
            // Endpoint id with delimiter should be ignored
            var  expectedEndpointId = $"{Guid.NewGuid()}{MessageQueueIdHelper.MessageQueueIdDelimiter}4321";
            uint expectedPriority   = 1234;
            var  messageQueueId     = $"{expectedEndpointId}{MessageQueueIdHelper.MessageQueueIdDelimiter}{expectedPriority}";

            var(endpointId, priority) = MessageQueueIdHelper.ParseMessageQueueId(messageQueueId);

            Assert.Equal(expectedEndpointId, endpointId);
            Assert.Equal(expectedPriority, priority);
        }
Пример #8
0
        public void TestParseMessageQueueIdWithPriority()
        {
            // Message queue id with priority should return its priority
            var  expectedEndpointId = Guid.NewGuid().ToString();
            uint expectedPriority   = 1234;
            var  messageQueueId     = $"{expectedEndpointId}{MessageQueueIdHelper.MessageQueueIdDelimiter}{expectedPriority}";

            var(endpointId, priority) = MessageQueueIdHelper.ParseMessageQueueId(messageQueueId);

            Assert.Equal(expectedEndpointId, endpointId);
            Assert.Equal(expectedPriority, priority);
        }
Пример #9
0
        public void TestParseMessageQueueIdWithoutPriority()
        {
            // Message queue id without priority should return default priority
            var expectedEndpointId = Guid.NewGuid().ToString();
            var messageQueueId     = expectedEndpointId;
            var expectedPriority   = Core.RouteFactory.DefaultPriority;

            var(endpointId, priority) = MessageQueueIdHelper.ParseMessageQueueId(messageQueueId);

            Assert.Equal(expectedEndpointId, endpointId);
            Assert.Equal(expectedPriority, priority);
        }
Пример #10
0
            private async Task CleanQueue(bool checkEntireQueueOnCleanup)
            {
                long totalCleanupCount      = 0;
                long totalCleanupStoreCount = 0;

                while (true)
                {
                    foreach (KeyValuePair <string, ISequentialStore <MessageRef> > endpointSequentialStore in this.messageStore.endpointSequentialStores)
                    {
                        var messageQueueId = endpointSequentialStore.Key;
                        try
                        {
                            if (this.cancellationTokenSource.IsCancellationRequested)
                            {
                                return;
                            }

                            var(endpointId, priority) = MessageQueueIdHelper.ParseMessageQueueId(messageQueueId);
                            Events.CleanupTaskStarted(messageQueueId);
                            CheckpointData checkpointData = await this.messageStore.checkpointStore.GetCheckpointDataAsync(messageQueueId, CancellationToken.None);

                            ISequentialStore <MessageRef> sequentialStore = endpointSequentialStore.Value;
                            Events.CleanupCheckpointState(messageQueueId, checkpointData);
                            int cleanupEntityStoreCount = 0;

                            // If checkEntireQueueOnCleanup is set to false, we only peek the head, message counts is tailOffset-headOffset+1
                            // otherwise count while iterating over the queue.
                            var headOffset   = 0L;
                            var tailOffset   = sequentialStore.GetTailOffset(CancellationToken.None);
                            var messageCount = 0L;

                            async Task <bool> DeleteMessageCallback(long offset, MessageRef messageRef)
                            {
                                var expiry = messageRef.TimeStamp + messageRef.TimeToLive;

                                if (offset > checkpointData.Offset && expiry > DateTime.UtcNow)
                                {
                                    // message is not sent and not expired, increase message counts
                                    messageCount++;
                                    return(false);
                                }

                                headOffset = Math.Max(headOffset, offset);
                                bool deleteMessage = false;

                                // Decrement ref count.
                                var message = await this.messageStore.messageEntityStore.Update(
                                    messageRef.EdgeMessageId,
                                    m =>
                                {
                                    if (m.RefCount > 0)
                                    {
                                        m.RefCount--;
                                    }

                                    if (m.RefCount == 0)
                                    {
                                        deleteMessage = true;
                                    }

                                    return(m);
                                });

                                if (deleteMessage)
                                {
                                    if (offset > checkpointData.Offset && expiry <= DateTime.UtcNow)
                                    {
                                        this.expiredCounter.Increment(1, new[] { "ttl_expiry", message?.Message.GetSenderId(), message?.Message.GetOutput(), bool.TrueString });
                                    }

                                    await this.messageStore.messageEntityStore.Remove(messageRef.EdgeMessageId);

                                    cleanupEntityStoreCount++;
                                }

                                return(true);
                            }

                            // With the addition of PriorityQueues, the CleanupProcessor assumptions change slightly:
                            // Previously, we could always assume that if a message at the head of the queue should not be deleted,
                            // then none of the other messages in the queue should be either. Now, because we can have different TTL's
                            // for messages within the same queue, there can be messages that have expired in the queue after the head.
                            // The checkEntireQueueOnCleanup flag is an environment variable for edgeHub. If it is set to true, we will
                            // check the entire queue every time cleanup processor runs. If it is set to false, we just remove the oldest
                            // items in the queue until we get to one that is not expired.
                            int cleanupCount = 0;
                            if (checkEntireQueueOnCleanup)
                            {
                                IEnumerable <(long, MessageRef)> batch;
                                long offset = sequentialStore.GetHeadOffset(this.cancellationTokenSource.Token);
                                do
                                {
                                    batch = await sequentialStore.GetBatch(offset, CleanupBatchSize);

                                    foreach ((long, MessageRef)messageWithOffset in batch)
                                    {
                                        if (await sequentialStore.RemoveOffset(DeleteMessageCallback, messageWithOffset.Item1, this.cancellationTokenSource.Token))
                                        {
                                            cleanupCount++;
                                        }
                                    }

                                    offset += CleanupBatchSize;
                                }while (batch.Any());
                            }
                            else
                            {
                                while (await sequentialStore.RemoveFirst(DeleteMessageCallback))
                                {
                                    cleanupCount++;
                                }

                                messageCount = tailOffset - headOffset + 1;
                            }

                            // update Metrics for message counts
                            Checkpointer.Metrics.QueueLength.Set(messageCount, new[] { endpointId, priority.ToString(), bool.TrueString });
                            totalCleanupCount      += cleanupCount;
                            totalCleanupStoreCount += cleanupEntityStoreCount;
                            Events.CleanupCompleted(messageQueueId, cleanupCount, cleanupEntityStoreCount, totalCleanupCount, totalCleanupStoreCount);
                            await Task.Delay(MinCleanupSleepTime, this.cancellationTokenSource.Token);
                        }
                        catch (Exception ex)
                        {
                            Events.ErrorCleaningMessagesForEndpoint(ex, messageQueueId);
                        }
                    }

                    await Task.Delay(this.GetCleanupTaskSleepTime());
                }
            }
Пример #11
0
        async Task SendMessagesPump()
        {
            try
            {
                Events.StartSendMessagesPump(this);
                int batchSize = this.options.BatchSize * this.Endpoint.FanOutFactor;

                // Keep the stores and prefetchers for each priority loaded
                // for the duration of the pump
                var messageProviderPairs = new Dictionary <uint, (IMessageIterator, StoreMessagesProvider)>();

                // Outer loop to maintain the message pump until the executor shuts down
                while (!this.cts.IsCancellationRequested)
                {
                    try
                    {
                        await this.hasMessagesInQueue.WaitAsync(this.options.BatchTimeout);

                        ImmutableDictionary <uint, EndpointExecutorFsm> snapshot = this.prioritiesToFsms;
                        bool haveMessagesRemaining = false;

                        uint[] orderedPriorities = snapshot.Keys.OrderBy(k => k).ToArray();
                        // Iterate through all the message queues in priority order
                        foreach (uint priority in orderedPriorities)
                        {
                            // Also check for cancellation in every inner loop,
                            // since it could take time to send a batch of messages
                            if (this.cts.IsCancellationRequested)
                            {
                                break;
                            }

                            EndpointExecutorFsm fsm = snapshot[priority];

                            // Update the lastUsedFsm to be the current FSM
                            this.lastUsedFsm = fsm;

                            (IMessageIterator, StoreMessagesProvider)pair;
                            if (!messageProviderPairs.TryGetValue(priority, out pair))
                            {
                                // Create and cache a new pair for the message provider
                                // so we can reuse it every loop
                                pair.Item1 = this.messageStore.GetMessageIterator(MessageQueueIdHelper.GetMessageQueueId(this.Endpoint.Id, priority), fsm.Checkpointer.Offset + 1);
                                pair.Item2 = new StoreMessagesProvider(pair.Item1, batchSize);
                                messageProviderPairs.Add(priority, pair);
                            }

                            StoreMessagesProvider storeMessagesProvider = pair.Item2;
                            IMessage[]            messages = await storeMessagesProvider.GetMessages();

                            if (messages.Length > 0)
                            {
                                // Tag the message with the priority that we're currently
                                // processing, so it can be used by metrics later
                                foreach (IMessage msg in messages)
                                {
                                    msg.ProcessedPriority = priority;
                                }

                                Events.ProcessingMessages(this, messages, priority);
                                await this.ProcessMessages(messages, fsm);

                                Events.SendMessagesSuccess(this, messages, priority);

                                // Only move on to the next priority if the queue for the current
                                // priority is empty. If we processed any messages, break out of
                                // the inner loop to restart at the beginning of the priorities list
                                // again. This is so we can catch and process any higher priority
                                // messages that came in while we were sending the current batch
                                haveMessagesRemaining = true;
                                break;
                            }
                        }

                        if (!haveMessagesRemaining)
                        {
                            // All the message queues have been drained, reset the hasMessagesInQueue flag.
                            this.hasMessagesInQueue.Reset();
                        }
                    }
                    catch (Exception ex)
                    {
                        Events.SendMessagesError(this, ex);
                        // Swallow exception and keep trying.
                    }
                }
            }
            catch (Exception ex)
            {
                Events.SendMessagesPumpFailure(this, ex);
            }
        }