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); }
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); }