public async Task NotifyChange(SubscriptionChange changeType, Subscription subscription)
        {
            var redis = this.connection.GetDatabase();

            var message = string.Format("{0}|{1}", (int)changeType, subscription.Id);
            await redis.PublishAsync(this.subscriptionChannel, message);
        }
Exemple #2
0
        private TransportMessage BuildTransportMessage(Message message, Subscription subscription)
        {
            JObject jsonPayload = message.Payload is JObject ? 
                                    (JObject)message.Payload
                                    : JObject.FromObject(message.Payload);

            var transportMessage = new TransportMessage();
            // reuse existing Id or create a new one
            transportMessage.Id = message.Headers.GetOrDefault(MessageHeaders.Id) ?? Guid.NewGuid().ToString();
            transportMessage.Name = message.Name;
            transportMessage.Body = jsonPayload;

            // copy original headers
            foreach (var header in message.Headers)
            {
                transportMessage.Headers[header.Key] = header.Value;
            }
            
            // add subscription headers
            transportMessage.Headers[MessageHeaders.Endpoint] = subscription.Endpoint; // destination queue / processor
            transportMessage.Headers[MessageHeaders.Component] = subscription.Component; //handler within the processor

            Console.WriteLine("Sending {0} to {1}", transportMessage.Name, subscription.Endpoint);

            return transportMessage;
        }
 public void Remove(Subscription subscription)
 {
     if (this.subscriptions.Contains(subscription))
     {
         this.subscriptions.Remove(subscription);
         this.latestCopy = this.subscriptions.ToArray();
     }
 }
 public void Add(Subscription subscription)
 {
     if (!this.subscriptions.Contains(subscription))
     {
         this.subscriptions.Add(subscription);
         this.latestCopy = this.subscriptions.ToArray();
     }
 }
        public Task NotifyChange(SubscriptionChange changeType, Subscription subscription)
        {
            foreach (var handler in handlers)
            {
                NotifyHandler(handler, changeType, subscription);
            }

            return Task.FromResult(1);
        }
        public async Task CanAddSubscription()
        {
            // arrange
            var subscription = new Subscription("topic1", "endpoint1", "handler1");

            //act
            var success = await this.store.AddSubscription(subscription);

            //assert
            Assert.IsTrue(success);
        }
        private async Task RemoveSubscription(Subscription subscription)
        {
            var success = await this.store.RemoveSubscription(subscription);
            if (success)
            {
                // update current cache
                OnSubscriptionRemoved(subscription);

                //notify other processes of this change
                await this.broker.NotifyChange(SubscriptionChange.Remove, subscription);
            }
        }
        private async Task AddSubscription(Subscription subscription)
        {
            var success = await this.store.AddSubscription(subscription);
            if (success)
            {
                // update current cache without waiting for broker notification to be received
                OnSubscriptionAdded(subscription);

                //notify other processes of this change
                await this.broker.NotifyChange(SubscriptionChange.Add, subscription);
            }
        }
        public async Task CanRemoveSubscription()
        {
            // arrange
            var existingSubscription = new Subscription("topic1", "endpoint1", "handler1");
            await this.store.AddSubscription(existingSubscription);

            //act
            var subscriptionToRemove = new Subscription("topic1", "endpoint1", "handler1"); // duplicating object, to simulate real usage
            var success = await this.store.RemoveSubscription(subscriptionToRemove);

            //assert
            Assert.IsTrue(success);
        }
        public Task<bool> RemoveSubscription(Subscription subscription)
        {
            lock (this.subscriptionsLock)
            {
                if (this.subscriptions.Contains(subscription))
                {
                    this.subscriptions.Remove(subscription);

                    return Task.FromResult(true);
                }

                return Task.FromResult(false);
            }
        }
        public async Task CanNotifyChanges()
        {
            // arrange
            var subscription1 = new Subscription("t1", "e1", "c1");
            var subscription2 = new Subscription("t2", "e2", "c2");
            var subscription3 = new Subscription("t3", "e3", "c3");

            // will be published, should be received
            var notifications = new [] {
                new Tuple<SubscriptionChange, Subscription>(SubscriptionChange.Add, subscription1),
                new Tuple<SubscriptionChange, Subscription>(SubscriptionChange.Remove, subscription2),
                new Tuple<SubscriptionChange, Subscription>(SubscriptionChange.Add, subscription3),
                new Tuple<SubscriptionChange, Subscription>(SubscriptionChange.Remove, subscription1)
            };
            var pending = notifications.ToList();

            var done = new TaskCompletionSource<bool>();
            var expectedNotificationsReceived = pending.Count;
            int receivedNotifications = 0;

            // subscribe
            await this.broker.SubscribeChangeNotifications(
                (c, s) =>
                {
                    lock (pending)
                    {
                        // remove from pending list after we receive it, 
                        // to ensure we don't process the same notification twice
                        var item = pending.Single(n => n.Item1 == c && n.Item2.Equals(s));
                        pending.Remove(item);

                        if (Interlocked.Increment(ref receivedNotifications) == expectedNotificationsReceived)
                        {
                            done.SetResult(true);
                        }
                    }
                });

            // act
            foreach (var notification in notifications)
            {
                await this.broker.NotifyChange(notification.Item1, notification.Item2);
            }

            // awaits either for timeout or receivers completion
            await Task.WhenAny(done.Task, Task.Delay(TimeSpan.FromSeconds(2))); 

            // assert
            Assert.AreEqual(TaskStatus.RanToCompletion, done.Task.Status);
        }
        public async Task<bool> AddSubscription(Subscription subscription)
        {
            var redis = this.connection.GetDatabase();

            var subscriptionKey = GetSubscriptionKey(subscription); // to detect concurrent updates

            var topicKey = GetTopicKey(subscription.Topic);
            var endpointKey = GetEndpointKey(subscription.Endpoint);
            var allSubscriptionsKey = GetAllSubscriptionsKey();
            
            var transaction = redis.CreateTransaction();

            // uses subscriptionId key for optimistic concurrency
            // if another client happens to adding this key concurrently, only the first one will publish changes
            transaction.AddCondition(Condition.KeyNotExists(subscriptionKey));

            transaction.SetAddAsync(topicKey, subscription.Id);
            transaction.SetAddAsync(endpointKey, subscription.Id);
            transaction.SetAddAsync(allSubscriptionsKey, subscription.Id);
            transaction.StringSetAsync(subscriptionKey, ""); // don't really need to have a value here, it's just for optimistic concurrency, it either exists or not

            var success = await transaction.ExecuteAsync();
            return success;
        }
        public async Task<bool> RemoveSubscription(Subscription subscription)
        {
            var redis = this.connection.GetDatabase();

            var subscriptionKey = GetSubscriptionKey(subscription); // to detect concurrent updates

            var topicKey = GetTopicKey(subscription.Topic);
            var endpointKey = GetEndpointKey(subscription.Endpoint);
            var allSubscriptionsKey = GetAllSubscriptionsKey();

            var transaction = redis.CreateTransaction();

            // uses subscriptionId key for optimistic concurrency
            // if another client happens to be removing this key concurrently, only the first one will publish changes
            transaction.AddCondition(Condition.KeyExists(subscriptionKey));

            transaction.SetRemoveAsync(topicKey, subscription.Id);
            transaction.SetRemoveAsync(endpointKey, subscription.Id);
            transaction.SetRemoveAsync(allSubscriptionsKey, subscription.Id);
            transaction.KeyDeleteAsync(subscriptionKey);

            var success = await transaction.ExecuteAsync();
            return success;
        }
        public async Task WhenStoreHasDeprecatedSubscriptions_OnlyValidOnesAreReturned_AfterUpdatingThem()
        {
            // arrange
            var validSubscription = new Subscription("t1", "e1", "c1");
            var deprecatedSubscription = new Subscription("t1", "e1", "c3"); // deprecated because will not be updated
            await this.store.AddSubscription(validSubscription);
            await this.store.AddSubscription(deprecatedSubscription);

            // ensure that before updating, both subscriptions are returned
            var t1Subs = await this.manager.GetSubscriptions("t1");
            Assert.AreEqual(2, t1Subs.Count());

            // act
            // update c1 subscriptions, only with 'c1', meaning 'c3' should be removed
            await this.manager.UpdateEndpointSubscriptions(
                "e1",
                new[] { new Subscription("t1", "e1", "c1") });

            // assert
            t1Subs = await this.manager.GetSubscriptions("t1");
            // there is only one subscription and is the valid one
            Assert.AreEqual(1, t1Subs.Count());
            Assert.NotNull(t1Subs.SingleOrDefault(s => s.Equals(validSubscription)));
        }
        public async Task WhenDeprecatedSubscriptionsAreFound_BrokerIsNotified()
        {
            // mock broker
            var broker = CreateBrokerMock();
            this.manager = new SubscriptionManager(this.store, broker.Object);

            // arrange
            var deprecated = new Subscription("t2", "e1", "c1");
            await this.store.AddSubscription(new Subscription("t1", "e1", "c1"));
            await this.store.AddSubscription(deprecated);
            await this.store.AddSubscription(new Subscription("t3", "e1", "c1"));

            // act
            // the deprecated subscription isn't included here, meaning the broker must get notified
            await this.manager.UpdateEndpointSubscriptions(
                "e1",
                new[] { new Subscription("t1", "e1", "c1"), new Subscription("t3", "e1", "c1") });

            // assert
            //verifies that broker was notified only once and for the expected subscription
            broker.Verify(b =>
                b.NotifyChange(It.IsAny<SubscriptionChange>(), It.IsAny<Subscription>()),
                Times.Exactly(1));
            broker.Verify(b =>
                b.NotifyChange(SubscriptionChange.Remove, It.Is<Subscription>(s => s.Equals(deprecated))),
                Times.Once);
        }
 private string GetSubscriptionKey(Subscription subscription)
 {
     return string.Format("{0}.subscriptions.{1}", this.storeKeyPrefix, subscription.Id);
 }
 private void NotifyHandler(Action<SubscriptionChange, Subscription> handler, 
                            SubscriptionChange change, 
                            Subscription subscription)
 {
     handler(change, subscription);
 }
        public async Task WhenNewSubscriptionsAreFound_BrokerIsNotified()
        {
            // mock broker
            var broker = CreateBrokerMock();
            this.manager = new SubscriptionManager(this.store, broker.Object);

            // arrange
            var existingSubscription = new Subscription("t1", "e1", "c1");
            await this.store.AddSubscription(existingSubscription);

            // act
            var newSubscription1 = new Subscription("t2", "e1", "c1");
            var newSubscription2 = new Subscription("t3", "e1", "c1");
            await this.manager.UpdateEndpointSubscriptions(
                "e1",
                new[] { existingSubscription, newSubscription1, newSubscription2 });

            // assert
            //verifies that broker was notified two times and for the expected subscriptions
            broker.Verify(b =>
                b.NotifyChange(It.IsAny<SubscriptionChange>(), It.IsAny<Subscription>()),
                Times.Exactly(2));
            broker.Verify(b =>
                b.NotifyChange(SubscriptionChange.Add, It.Is<Subscription>(s => s.Equals(newSubscription1))),
                Times.Once);
            broker.Verify(b =>
                b.NotifyChange(SubscriptionChange.Add, It.Is<Subscription>(s => s.Equals(newSubscription2))),
                Times.Once);
        }
 private void OnSubscriptionChanged(SubscriptionChange change, Subscription subscription)
 {
     if (change == SubscriptionChange.Add)
     {
         OnSubscriptionAdded(subscription);
     }
     else
     {
         OnSubscriptionRemoved(subscription);
     }
 }
        public async Task CannotRemoveSubscriptionThatDoesntExist()
        {
            // arrange
            var subscription = new Subscription("topic", "endpoint", "component");

            // act
            var success = await this.store.RemoveSubscription(subscription);

            // assert
            Assert.IsFalse(success);
        }
        public async Task CannotAddTheSameSubscriptionTwice()
        {
            // arrange
            var subscription = new Subscription("topic", "endpoint", "component");
            await this.store.AddSubscription(subscription);

            // act
            var success = await this.store.AddSubscription(subscription);

            // assert
            Assert.IsFalse(success);
        }
 private void OnSubscriptionRemoved(Subscription subscription)
 {
     lock (this.cacheLock)
     {
         var set = this.cache.GetOrAdd(subscription.Topic, s => new SubscriptionSet());
         set.Remove(subscription);
     }
 }
        public async Task CanReceiveNotifications(int nSubscribers, int nSubscriptions)
        {
            // arrange
            var done = new TaskCompletionSource<bool>();
            var subscription = new Subscription("topic1", "endpoint1", "component1");

            int expectedNotificationsToBeReceived = nSubscribers * nSubscriptions;
            int notificationsReceived = 0;

            for (var i = 0; i < nSubscribers; ++i)
            {
                await this.broker.SubscribeChangeNotifications(
                    (c, s) =>
                    {
                        if (s.Equals(subscription))
                        {
                            if (Interlocked.Increment(ref notificationsReceived) == expectedNotificationsToBeReceived)
                            {
                                done.SetResult(true);
                            }
                        }
                    });
            }

            // act
            // publish the changes
            Parallel.For(0, nSubscriptions, 
                async i =>
                {
                    await this.broker.NotifyChange(SubscriptionChange.Add, subscription);
                });

            // awaits either for timeout or receivers completion
            await Task.WhenAny(done.Task, Task.Delay(TimeSpan.FromSeconds(2))); 

            // assert
            Assert.AreEqual(TaskStatus.RanToCompletion, done.Task.Status);
        }