async Task RunAsync() { await Task.Yield(); var prefixDot = CachePrefix + "."; try { for (;;) { var msg = _input.Read(Properties.All, TimeSpan.Zero) ?? await _input.ReadAsync(Properties.All); if (msg.Label.StartsWith(prefixDot, StringComparison.OrdinalIgnoreCase)) { switch ((MessageCacheAction)msg.AppSpecific) { case MessageCacheAction.Read: SendLastValue(msg); break; case MessageCacheAction.Remove: Invalidate(msg); break; } } else if (msg.Label.Equals(CachePrefix, StringComparison.OrdinalIgnoreCase)) { switch ((MessageCacheAction)msg.AppSpecific) { case MessageCacheAction.ListKeys: SendKeyList(msg); break; case MessageCacheAction.Clear: ClearCache(); break; } } else { StoreLastValue(msg); } } } catch (ObjectDisposedException) { // Stop was called } catch (QueueException ex) when(ex.ErrorCode == ErrorCode.OperationCanceled) { // Stop was called } }
/// <summary>Report any failure to send to destination queues</summary> async Task AdminTask() { await Task.Yield(); var props = Properties.Class | Properties.DestinationQueue | Properties.Label; try { for (;;) { var msg = _adminReader.Read(props, TimeSpan.Zero) ?? await _adminReader.ReadAsync(props); var ack = msg.Acknowledgement(); switch (ack) { case MessageClass.ReachQueueTimeout: case MessageClass.AccessDenied: case MessageClass.BadDestinationQueue: case MessageClass.BadEncryption: case MessageClass.BadSignature: case MessageClass.CouldNotEncrypt: case MessageClass.HopCountExceeded: case MessageClass.NotTransactionalMessage: case MessageClass.NotTransactionalQueue: case MessageClass.Deleted: case MessageClass.QueueDeleted: case MessageClass.QueuePurged: case MessageClass.QueueExceedQuota: case MessageClass.ReceiveTimeout: Console.Error.WriteLine($"WARNING {ack} sending '{msg.Label}' to {msg.DestinationQueue}"); break; } } } catch (QueueException ex) when(ex.ErrorCode == ErrorCode.OperationCanceled) { // stopped } catch (ObjectDisposedException) { // stopped } }
async Task AdminAsync() { try { for (;;) { const Properties adminProps = Properties.Class | Properties.Id | Properties.Label | Properties.DestinationQueue; var msg = _admin.Read(adminProps, TimeSpan.Zero) ?? await _admin.ReadAsync(adminProps); var ack = msg.Acknowledgement(); switch (ack) { case MessageClass.ReachQueueTimeout: case MessageClass.AccessDenied: case MessageClass.BadDestinationQueue: case MessageClass.BadEncryption: case MessageClass.BadSignature: case MessageClass.CouldNotEncrypt: case MessageClass.HopCountExceeded: case MessageClass.NotTransactionalMessage: case MessageClass.NotTransactionalQueue: case MessageClass.Deleted: case MessageClass.QueueDeleted: case MessageClass.QueuePurged: case MessageClass.QueueExceedQuota: Console.Error.WriteLine($"WARNING: message labelled '{msg.Label}' failed to reach '{msg.DestinationQueue}' because {ack}"); break; } } } catch (ObjectDisposedException) { // Stop was called } catch (QueueException ex) when(ex.ErrorCode == ErrorCode.OperationCanceled) { // Stop was called } }
async Task RunAsync() { await Task.Yield(); try { for (;;) { var msg = _adminQueue.Read(AdminProperties, TimeSpan.Zero) ?? await _adminQueue.ReadAsync(AdminProperties); var ack = msg.Acknowledgement(); switch (ack) { #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed case MessageClass.ReachQueue: var tcs = ReachQueueCompletionSource(new Tracking(msg.ResponseQueue, msg.CorrelationId)); Task.Run(() => tcs.TrySetResult(ack)); // set result is synchronous by default, make it async break; case MessageClass.ReachQueueTimeout: case MessageClass.AccessDenied: case MessageClass.BadDestinationQueue: case MessageClass.BadEncryption: case MessageClass.BadSignature: case MessageClass.CouldNotEncrypt: case MessageClass.HopCountExceeded: case MessageClass.NotTransactionalMessage: case MessageClass.NotTransactionalQueue: case MessageClass.Deleted: case MessageClass.QueueDeleted: case MessageClass.QueuePurged: case MessageClass.QueueExceedQuota: var tcs1 = ReachQueueCompletionSource(new Tracking(msg.ResponseQueue, msg.CorrelationId)); Task.Run(() => tcs1.TrySetException(new AcknowledgmentException(msg.ResponseQueue, ack))); // set result is synchronous by default, make it async break; case MessageClass.Received: var tcs2 = ReceiveCompletionSource(new Tracking(msg.ResponseQueue, msg.CorrelationId)); Task.Run(() => tcs2.TrySetResult(ack)); // set result is synchronous by default, make it async break; case MessageClass.ReceiveTimeout: var tcs3 = ReceiveCompletionSource(new Tracking(msg.ResponseQueue, msg.CorrelationId)); Task.Run(() => tcs3.TrySetException(new AcknowledgmentException(msg.ResponseQueue, ack))); // set result is synchronous by default, make it async break; #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed } } } catch (QueueException ex) when(ex.ErrorCode == ErrorCode.OperationCanceled) { // stopped } catch (ObjectDisposedException) { // stopped } }
/// <summary>Respond to requests to subscribe or unsubscribe</summary> /// <remarks> /// Add Subscription: label: tag, AppSpecific: Add, ResponseQueue: subscriber identity, Body: UTF-8 subjects, one per line /// Set Subscriptions: label: tag, AppSpecific: Set, ResponseQueue: subscriber identity, Body: UTF-8 subjects, one per line /// Remove Subscriptions: label: tag, AppSpecific: Remove, ResponseQueue: subscriber identity, Body: UTF-8 subjects, one per line /// Clear Subscriptions: label: tag, AppSpecific: Clear, ResponseQueue: subscriber identity /// List Subscriptions: label: tag, AppSpecific: List, ResponseQueue: subscriber identity /// </remarks> async Task SubscriptionLoop() { await Task.Yield(); Properties props = Properties.AppSpecific | Properties.Label | Properties.ResponseQueue | Properties.Body; try { for (;;) { var msg = _clientRequestReader.Read(props, TimeSpan.Zero) ?? await _clientRequestReader.ReadAsync(props); var tag = msg.Label; string responseQueue = msg.ResponseQueue; var labels = msg.BodyUTF8()?.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); if (responseQueue.Length == 0) { Console.Error.WriteLine("Request with no response queue, ignoring message: " + msg.Id); continue; } PubSubAction action = (PubSubAction)msg.AppSpecific; switch (action) { case PubSubAction.Add: lock (_subscriptions.SyncRoot) { foreach (var l in labels) { _subscriptions.Add(l, responseQueue, tag); } } break; case PubSubAction.Remove: lock (_subscriptions.SyncRoot) { foreach (var l in labels) { _subscriptions.Remove(l, responseQueue, tag); } } break; case PubSubAction.Set: var targetSubs = new HashSet <string>(labels); lock (_subscriptions.SyncRoot) { var currentSubs = _subscriptions.GetSubscriptions(responseQueue, tag); foreach (var l in targetSubs.Where(l => !currentSubs.Contains(l))) { _subscriptions.Add(l, responseQueue, tag); } foreach (var l in currentSubs.Where(l => !targetSubs.Contains(l))) { _subscriptions.Remove(l, responseQueue, tag); } } break; case PubSubAction.Clear: _subscriptions.Clear(responseQueue, tag); break; case PubSubAction.List: HashSet <string> current = _subscriptions.GetSubscriptions(responseQueue, tag); var reply = new Message { AdministrationQueue = _adminQueueFormatName, Label = tag, CorrelationId = msg.Id }; reply.BodyUTF8(string.Join(Environment.NewLine, current.OrderBy(l => l))); var q = _responseQueueCache.Open(responseQueue, QueueAccessMode.Send); q.Write(reply); break; default: Console.Error.WriteLine($"Request with invalid {nameof(msg.AppSpecific)} {action}, ignoring request, message id '{msg.Id}' for '{responseQueue}'"); break; } } } catch (QueueException ex) when(ex.ErrorCode == ErrorCode.OperationCanceled) { // stopped } catch (ObjectDisposedException) { // stopped } catch (Exception ex) { Console.Error.WriteLine("ERROR SubscriptionLoop: " + ex); } }
/// <summary>Read messages from the input queue and forward to subscribers</summary> async Task MulticastInputDispatcher() { await Task.Yield(); try { for (;;) { var msg = _inputReader.Read(Properties.All, TimeSpan.Zero) ?? await _inputReader.ReadAsync(Properties.All); // we could avoid the lock by using an immutable collection var subscribers = _subscriptions.GetSubscribers(msg.Label); if (subscribers.Count == 0) { continue; } var fn = string.Join(",", subscribers.Select(s => s.FormatName).Distinct()); // create a multi-element format name var q = _responseQueueCache.Open(fn, QueueAccessMode.Send); msg.AdministrationQueue = _adminQueueFormatName; q.Write(msg); } } catch (QueueException ex) when(ex.ErrorCode == ErrorCode.OperationCanceled) { // stopped } catch (ObjectDisposedException) { // stopped } catch (Exception ex) { Console.Error.WriteLine("ERROR MulticastInputDispatcher: " + ex); } }