Example #1
0
        public async Task <IMessage> Add(string endpointId, IMessage message, uint timeToLiveSecs)
        {
            Preconditions.CheckNotNull(message, nameof(message));
            if (!this.endpointSequentialStores.TryGetValue(Preconditions.CheckNonWhiteSpace(endpointId, nameof(endpointId)), out ISequentialStore <MessageRef> sequentialStore))
            {
                throw new InvalidOperationException($"SequentialStore for endpoint {nameof(endpointId)} not found");
            }

            if (!message.SystemProperties.TryGetValue(SystemProperties.EdgeMessageId, out string edgeMessageId))
            {
                throw new InvalidOperationException("Message does not contain required system property EdgeMessageId");
            }

            TimeSpan timeToLive = timeToLiveSecs == 0 ? this.timeToLive : TimeSpan.FromSeconds(timeToLiveSecs);

            // First put the message in the entity store and then put it in the sequentialStore. This is because the pump can go fast enough that it
            // reads the message from the sequential store and tries to find the message in the entity store before the message has been added to the
            // entity store.
            // Note - if we fail to add the message to the sequential store (for some reason), then we will end up not cleaning up the message in the
            // entity store. But that should be rare enough that it might be okay. Also it is better than not being able to forward the message.
            // Alternative is to add retry logic to the pump, but that is more complicated, and could affect performance.
            // TODO - Need to support transactions for these operations. The underlying storage layers support it.
            using (MetricsV0.MessageStoreLatency(endpointId))
            {
                await this.messageEntityStore.PutOrUpdate(
                    edgeMessageId,
                    new MessageWrapper(message),
                    (m) =>
                {
                    m.RefCount++;
                    return(m);
                });
            }

            try
            {
                using (MetricsV0.SequentialStoreLatency(endpointId))
                {
                    long offset = await sequentialStore.Append(new MessageRef(edgeMessageId, timeToLive));

                    Events.MessageAdded(offset, edgeMessageId, endpointId);
                    return(new MessageWithOffset(message, offset));
                }
            }
            catch (Exception)
            {
                // If adding the message to the SequentialStore throws, then remove the message from the EntityStore as well, so that there is no leak.
                await this.messageEntityStore.Remove(edgeMessageId);

                throw;
            }
        }