void AddToLookup(Subscriber subscriber, MessageType typeName, string messageId) { try { // note: ReaderWriterLockSlim has a thread affinity and cannot be used with await! rwLock.EnterWriteLock(); if (!lookup.TryGetValue(subscriber, out var dictionary)) { dictionary = new Dictionary <MessageType, string>(); } else { // replace existing subscriber lookup.Remove(subscriber); } dictionary[typeName] = messageId; lookup[subscriber] = dictionary; } finally { rwLock.ExitWriteLock(); } }
Dictionary <Subscriber, Dictionary <MessageType, string> > CreateLookup() { var output = new Dictionary <Subscriber, Dictionary <MessageType, string> >(SubscriberComparer); var messages = storageQueue.GetAllMessages() .OrderByDescending(m => m.ArrivedTime) .ThenBy(x => x.Id) // ensure same order of messages with same timestamp across all endpoints .ToArray(); foreach (var m in messages) { var messageTypeString = m.Body as string; var messageType = new MessageType(messageTypeString); //this will parse both 2.6 and 3.0 type strings var subscriber = Deserialize(m.Label); if (!output.TryGetValue(subscriber, out var endpointSubscriptions)) { output[subscriber] = endpointSubscriptions = new Dictionary <MessageType, string>(); } if (endpointSubscriptions.ContainsKey(messageType)) { // this message is stale and can be removed storageQueue.TryReceiveById(m.Id); } else { endpointSubscriptions[messageType] = m.Id; } } return(output); }
void ISubscriptionStorage.Init() { var path = MsmqUtilities.GetFullPath(Queue); q = new MessageQueue(path); bool transactional; try { transactional = q.Transactional; } catch (Exception ex) { throw new ArgumentException(string.Format("There is a problem with the subscription storage queue {0}. See enclosed exception for details.", Queue), ex); } if (!transactional && TransactionsEnabled) throw new ArgumentException("Queue must be transactional (" + Queue + ")."); var messageReadPropertyFilter = new MessagePropertyFilter { Id = true, Body = true, Label = true }; q.Formatter = new XmlMessageFormatter(new[] { typeof(string) }); q.MessageReadPropertyFilter = messageReadPropertyFilter; foreach (var m in q.GetAllMessages()) { var subscriber = Address.Parse(m.Label); var messageTypeString = m.Body as string; var messageType = new MessageType(messageTypeString); //this will parse both 2.6 and 3.0 type strings entries.Add(new Entry { MessageType = messageType, Subscriber = subscriber }); AddToLookup(subscriber, messageType, m.Id); } }
string RemoveFromLookup(Subscriber subscriber, MessageType typeName) { try { // note: ReaderWriterLockSlim has a thread affinity and cannot be used with await! rwLock.EnterWriteLock(); if (lookup.TryGetValue(subscriber, out var subscriptions)) { if (subscriptions.TryGetValue(typeName, out var messageId)) { subscriptions.Remove(typeName); if (subscriptions.Count == 0) { lookup.Remove(subscriber); } return(messageId); } } } finally { rwLock.ExitWriteLock(); } return(null); }
/// <summary> /// Adds a message to the subscription store. /// </summary> public void Add(Address subscriber, MessageType messageType) { var toSend = new Message {Formatter = q.Formatter, Recoverable = true, Label = subscriber.ToString(), Body = messageType.TypeName + ", Version=" + messageType.Version}; q.Send(toSend, GetTransactionType()); AddToLookup(subscriber, messageType, toSend.Id); }
/// <summary> /// Adds a message to the subscription store. /// </summary> public void Add(Address subscriber, MessageType messageType) { var toSend = new Message { Formatter = q.Formatter, Recoverable = true, Label = subscriber.ToString(), Body = messageType.TypeName + ", Version=" + messageType.Version }; q.Send(toSend, GetTransactionType()); AddToLookup(subscriber, messageType, toSend.Id); }
/// <summary> /// Removes a message from the subscription store. /// </summary> public void Remove(Address subscriber, MessageType messageType) { var messageId = RemoveFromLookup(subscriber, messageType); if (messageId == null) { return; } q.ReceiveById(messageId, GetTransactionType()); }
public async Task Subscribers_are_deduplicated_based_on_transport_address_comparison_case_invariant() { var queue = new FakeStorageQueue(); var storage = CreateAndInit(queue); var messageType = new MessageType(typeof(SomeMessage)); await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); await storage.Subscribe(new Subscriber("SUB1", null), messageType, new ContextBag()); var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag()); Assert.AreEqual(1, subscribers.Count()); }
public Task Subscribe(Subscriber subscriber, MessageType messageType, ContextBag context) { var body = $"{messageType.TypeName}, Version={messageType.Version}"; var label = Serialize(subscriber); var messageId = storageQueue.Send(body, label); AddToLookup(subscriber, messageType, messageId); log.DebugFormat($"Subscriber {subscriber.TransportAddress} added for message {messageType}."); return(TaskEx.CompletedTask); }
public Task Unsubscribe(Subscriber subscriber, MessageType messageType, ContextBag context) { var messageId = RemoveFromLookup(subscriber, messageType); if (messageId != null) { storageQueue.TryReceiveById(messageId); } log.Debug($"Subscriber {subscriber.TransportAddress} removed for message {messageType}."); return(TaskEx.CompletedTask); }
public async Task Can_have_multiple_subscribers_to_same_event_type() { var queue = new FakeStorageQueue(); var storage = CreateAndInit(queue); var messageType = new MessageType(typeof(SomeMessage)); await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); await storage.Subscribe(new Subscriber("sub2", null), messageType, new ContextBag()); var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag()); Assert.AreEqual(2, subscribers.Count()); }
/// <summary> /// Adds a message to the lookup to find message from /// subscriber, to message type, to message id /// </summary> private void AddToLookup(Address subscriber, MessageType typeName, string messageId) { lock (lookup) { if (!lookup.ContainsKey(subscriber)) { lookup.Add(subscriber, new Dictionary <MessageType, string>()); } if (!lookup[subscriber].ContainsKey(typeName)) { lookup[subscriber].Add(typeName, messageId); } } }
/// <summary> /// Adds a message to the lookup to find message from /// subscriber, to message type, to message id /// </summary> private void AddToLookup(Address subscriber, MessageType typeName, string messageId) { lock (lookup) { Dictionary <MessageType, string> dictionary; if (!lookup.TryGetValue(subscriber, out dictionary)) { lookup[subscriber] = dictionary = new Dictionary <MessageType, string>(); } if (!dictionary.ContainsKey(typeName)) { dictionary.Add(typeName, messageId); } } }
public async Task Unsubscribe_is_persistent() { var queue = new FakeStorageQueue(); var storage = CreateAndInit(queue); var messageType = new MessageType(typeof(SomeMessage)); await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); storage = CreateAndInit(queue); await storage.Unsubscribe(new Subscriber("sub1", "endpointA"), messageType, new ContextBag()); Assert.That(queue.GetAllMessages(), Is.Empty); storage = CreateAndInit(queue); var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag()); Assert.AreEqual(0, subscribers.Count()); }
public async Task Can_handle_legacy_and_new_format() { var queue = new FakeStorageQueue(); var storage = CreateAndInit(queue); var messageType = new MessageType(typeof(SomeMessage)); var messageTypes = new[] { messageType }; await storage.Subscribe(new Subscriber("legacy", null), messageType, new ContextBag()); await storage.Subscribe(new Subscriber("new", "endpoint"), messageType, new ContextBag()); var subscribers = (await storage.GetSubscriberAddressesForMessage(messageTypes, new ContextBag())).ToArray(); Assert.AreEqual(2, subscribers.Length); Assert.IsTrue(subscribers.Any(s => s.TransportAddress == "legacy" && s.Endpoint == null)); Assert.IsTrue(subscribers.Any(s => s.TransportAddress == "new" && s.Endpoint == "endpoint")); }
public async Task Subscribe_is_persistent() { var queue = new FakeStorageQueue(); var messageType = new MessageType(typeof(SomeMessage)); var storage = CreateAndInit(queue); await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); await storage.Subscribe(new Subscriber("sub2", "endpointA"), messageType, new ContextBag()); var storedMessages = queue.GetAllMessages().ToArray(); Assert.That(storedMessages.Length, Is.EqualTo(2)); storage = CreateAndInit(queue); var subscribers = (await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag())).ToArray(); Assert.That(subscribers, Has.Exactly(1).Matches<Subscriber>(s => s.TransportAddress == "sub1" && s.Endpoint == null)); Assert.That(subscribers, Has.Exactly(1).Matches<Subscriber>(s => s.TransportAddress == "sub2" && s.Endpoint == "endpointA")); }
public async Task Unsubscribing_removes_all_subscriptions_with_same_address_but_different_endpoint_names() { var queue = new FakeStorageQueue(); var storage = CreateAndInit(queue); var messageType = new MessageType(typeof(SomeMessage)); var messageTypes = new[] { messageType }; await storage.Subscribe(new Subscriber("sub1", "e1"), messageType, new ContextBag()); await storage.Subscribe(new Subscriber("sub1", "e2"), messageType, new ContextBag()); await storage.Unsubscribe(new Subscriber("sub1", "e3"), messageType, new ContextBag()); var subscribers = await storage.GetSubscriberAddressesForMessage(messageTypes, new ContextBag()); Assert.AreEqual(0, subscribers.Count()); }
public async Task Two_subscribers_with_same_address_but_different_endpoint_are_considered_duplicates() { var queue = new FakeStorageQueue(); var storage = CreateAndInit(queue); var messageType = new MessageType(typeof(SomeMessage)); var messageTypes = new[] { messageType }; await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); await storage.Subscribe(new Subscriber("sub1", "endpoint"), messageType, new ContextBag()); var subscribers = await storage.GetSubscriberAddressesForMessage(messageTypes, new ContextBag()); var subscriber = subscribers.Single(); Assert.AreEqual("sub1", subscriber.TransportAddress); Assert.AreEqual("endpoint", subscriber.Endpoint); }
public async Task Remove_outdated_subscriptions_on_initialization() { var queue = new FakeStorageQueue(); var storage = CreateAndInit(queue); var messageType = new MessageType(typeof(SomeMessage)); await storage.Subscribe(new Subscriber("sub1", "1"), messageType, new ContextBag()); await storage.Subscribe(new Subscriber("sub1", "2"), messageType, new ContextBag()); await storage.Subscribe(new Subscriber("sub1", "3"), messageType, new ContextBag()); storage = CreateAndInit(queue); var subscribers = (await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag())).ToArray(); Assert.That(subscribers.Length, Is.EqualTo(1)); Assert.That(subscribers[0].TransportAddress, Is.EqualTo("sub1")); Assert.That(subscribers[0].Endpoint, Is.EqualTo("3")); Assert.That(queue.GetAllMessages().Count(), Is.EqualTo(1)); }
public async Task Can_subscribe_to_multiple_events() { var queue = new FakeStorageQueue(); var storage = CreateAndInit(queue); var someMessageType = new MessageType(typeof(SomeMessage)); var otherMessageType = new MessageType(typeof(OtherMessage)); await storage.Subscribe(new Subscriber("sub1", null), someMessageType, new ContextBag()); await storage.Subscribe(new Subscriber("sub1", null), otherMessageType, new ContextBag()); var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { someMessageType }, new ContextBag()); Assert.AreEqual(1, subscribers.Count()); subscribers = await storage.GetSubscriberAddressesForMessage(new[] { otherMessageType }, new ContextBag()); Assert.AreEqual(1, subscribers.Count()); }
public async Task Same_subscriber_for_multiple_message_types_is_returned_only_once() { var queue = new FakeStorageQueue(); var storage = CreateAndInit(queue); var someMessageType = new MessageType(typeof(SomeMessage)); var otherMessageType = new MessageType(typeof(OtherMessage)); await storage.Subscribe(new Subscriber("sub1", null), someMessageType, new ContextBag()); await storage.Subscribe(new Subscriber("sub1", null), otherMessageType, new ContextBag()); var subscribers = await storage.GetSubscriberAddressesForMessage(new[] { someMessageType, otherMessageType }, new ContextBag()); Assert.AreEqual(1, subscribers.Count()); }
void ISubscriptionStorage.Init() { var path = MsmqUtilities.GetFullPath(Queue); q = new MessageQueue(path); bool transactional; try { transactional = q.Transactional; } catch (Exception ex) { throw new ArgumentException(string.Format("There is a problem with the subscription storage queue {0}. See enclosed exception for details.", Queue), ex); } if (!transactional && SettingsHolder.Get <bool>("Transactions.Enabled")) { throw new ArgumentException("Queue must be transactional (" + Queue + ")."); } var messageReadPropertyFilter = new MessagePropertyFilter { Id = true, Body = true, Label = true }; q.Formatter = new XmlMessageFormatter(new[] { typeof(string) }); q.MessageReadPropertyFilter = messageReadPropertyFilter; foreach (var m in q.GetAllMessages()) { var subscriber = Address.Parse(m.Label); var messageTypeString = m.Body as string; var messageType = new MessageType(messageTypeString); //this will parse both 2.6 and 3.0 type strings entries.Add(new Entry { MessageType = messageType, Subscriber = subscriber }); AddToLookup(subscriber, messageType, m.Id); } }
public async Task Subscribe_is_persistent() { var queue = new FakeStorageQueue(); var messageType = new MessageType(typeof(SomeMessage)); var storage = CreateAndInit(queue); await storage.Subscribe(new Subscriber("sub1", null), messageType, new ContextBag()); await storage.Subscribe(new Subscriber("sub2", "endpointA"), messageType, new ContextBag()); var storedMessages = queue.GetAllMessages().ToArray(); Assert.That(storedMessages.Length, Is.EqualTo(2)); storage = CreateAndInit(queue); var subscribers = (await storage.GetSubscriberAddressesForMessage(new[] { messageType }, new ContextBag())).ToArray(); Assert.That(subscribers, Has.Exactly(1).Matches <Subscriber>(s => s.TransportAddress == "sub1" && s.Endpoint == null)); Assert.That(subscribers, Has.Exactly(1).Matches <Subscriber>(s => s.TransportAddress == "sub2" && s.Endpoint == "endpointA")); }
string RemoveFromLookup(Address subscriber, MessageType typeName) { string messageId = null; lock (lookup) { Dictionary <MessageType, string> endpoints; if (lookup.TryGetValue(subscriber, out endpoints)) { if (endpoints.TryGetValue(typeName, out messageId)) { endpoints.Remove(typeName); if (endpoints.Count == 0) { lookup.Remove(subscriber); } } } } return(messageId); }
public void Init() { var messages = storageQueue.GetAllMessages() .OrderByDescending(m => m.ArrivedTime) .ThenBy(x => x.Id) // ensure same order of messages with same timestamp across all endpoints .ToArray(); try { rwLock.EnterWriteLock(); foreach (var m in messages) { var messageTypeString = m.Body as string; var messageType = new MessageType(messageTypeString); //this will parse both 2.6 and 3.0 type strings var subscriber = Deserialize(m.Label); if (!lookup.TryGetValue(subscriber, out var endpointSubscriptions)) { lookup[subscriber] = endpointSubscriptions = new Dictionary <MessageType, string>(); } if (endpointSubscriptions.ContainsKey(messageType)) { // this message is stale and can be removed storageQueue.TryReceiveById(m.Id); } else { endpointSubscriptions[messageType] = m.Id; } } } finally { rwLock.ExitWriteLock(); } }
/// <summary> /// Adds a message to the lookup to find message from /// subscriber, to message type, to message id /// </summary> private void AddToLookup(Address subscriber, MessageType typeName, string messageId) { lock (lookup) { if (!lookup.ContainsKey(subscriber)) lookup.Add(subscriber, new Dictionary<MessageType, string>()); if (!lookup[subscriber].ContainsKey(typeName)) lookup[subscriber].Add(typeName, messageId); } }
/// <summary> /// Removes a message from the subscription store. /// </summary> public void Remove(Address subscriber, MessageType messageType) { var messageId = RemoveFromLookup(subscriber, messageType); if (messageId == null) return; q.ReceiveById(messageId, GetTransactionType()); }
/// <summary> /// Adds a message to the lookup to find message from /// subscriber, to message type, to message id /// </summary> private void AddToLookup(Address subscriber, MessageType typeName, string messageId) { lock (lookup) { Dictionary<MessageType, string> dictionary; if (!lookup.TryGetValue(subscriber, out dictionary)) { lookup[subscriber] = dictionary = new Dictionary<MessageType, string>(); } if (!dictionary.ContainsKey(typeName)) { dictionary.Add(typeName, messageId); } } }
string RemoveFromLookup(Address subscriber, MessageType typeName) { string messageId = null; lock (lookup) { Dictionary<MessageType, string> endpoints; if (lookup.TryGetValue(subscriber, out endpoints)) { if (endpoints.TryGetValue(typeName, out messageId)) { endpoints.Remove(typeName); if (endpoints.Count == 0) { lookup.Remove(subscriber); } } } } return messageId; }