//******************************************************************************************************
// ServiceBusService.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/eclipse-1.0.php
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 10/06/2010 - Pinal C. Patel
// Generated original version of source code.
// 10/26/2010 - Pinal C. Patel
// Added management operations GetClients(), GetQueues() and GetTopics().
// 11/23/2010 - Pinal C. Patel
// Added new BufferThreshold and ProcessingMode properties.
// Enhanced thread synchronization using ReaderWriterLockSlim for better performance.
// 11/24/2010 - Pinal C. Patel
// Updated the text returned by Status property.
// 01/07/2011 - Pinal C. Patel
// Fixed initialization bug that prevented the service from functioning when hosted inside ASP.NET.
// 02/03/2011 - Pinal C. Patel
// Added GetLatestMessage() operation that can be used to retrieve the latest message published
// to the subscribers of a topic.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
using System.Threading;
using GSF.Collections;
using GSF.Configuration;
using GSF.ServiceModel;
namespace GSF.ServiceBus
{
#region [ Enumerations ]
///
/// Indicates how the distribution of s is processed by the .
///
public enum MessageProcessingMode
{
///
/// distribution is processed in parallel for increased distribution performance.
///
Parallel,
///
/// distribution is processed sequentially to preserve ordering.
///
Sequential
}
#endregion
///
/// A service bus for event-based messaging between disjoint systems.
///
///
/// This example shows how to host inside a console application:
///
/// using System;
/// using System.ServiceModel;
/// using System.ServiceModel.Description;
/// using System.Threading;
/// using GSF;
/// using GSF.ServiceBus;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// // Prompt for security option.
/// Console.Write("Enable security (Y/N): ");
/// bool enableSecurity = Console.ReadLine().ParseBoolean();
///
/// // Initialize service bus.
/// ServiceBusService service = new ServiceBusService();
/// service.Singleton = true;
/// service.PublishMetadata = true;
/// service.PersistSettings = false;
/// service.Endpoints = "http.duplex://localhost:4501; net.tcp://locahost:4502";
/// if (enableSecurity)
/// service.SecurityPolicy = typeof(ServiceBusSecurityPolicy).FullName;
/// service.Initialize();
///
/// // Show service bus status.
/// if (service.ServiceHost.State == CommunicationState.Opened)
/// {
/// Console.WriteLine("\r\n{0} is running:", service.GetType().Name);
/// foreach (ServiceEndpoint endpoint in service.ServiceHost.Description.Endpoints)
/// {
/// Console.WriteLine("- {0} ({1})", endpoint.Address, endpoint.Binding.GetType().Name);
/// }
///
/// new Thread(delegate()
/// {
/// while (service.ServiceHost.State == CommunicationState.Opened)
/// {
/// Console.WriteLine();
/// Console.WriteLine();
/// Console.WriteLine("Status of {0}:", service.Name);
/// Console.WriteLine(service.Status);
/// Console.Write("Press Enter key to stop...");
///
/// Thread.Sleep(5000);
/// }
/// }).Start();
/// }
/// else
/// {
/// Console.WriteLine("\r\n{0} could not be started", service.GetType().Name);
/// }
///
/// // Shutdown.
/// Console.ReadLine();
/// service.Dispose();
/// }
/// }
///
/// This example shows how to host inside a web application:
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
/// This example shows how to publish s to :
///
/// using System;
/// using System.ServiceModel;
/// using System.Threading;
///
/// class Program : IServiceBusServiceCallback
/// {
/// static void Main(string[] args)
/// {
/// // NOTE: Service reference to the service bus service must be added to generate the service proxy.
///
/// // Initialize auto-generated service bus service proxy.
/// InstanceContext callbackContext = new InstanceContext(new Program());
/// ServiceBusServiceClient serviceBusService = new ServiceBusServiceClient(callbackContext, "NetTcpBinding_IServiceBusService");
///
/// // Create registration request for publishing messages.
/// RegistrationRequest registration = new RegistrationRequest();
/// registration.MessageType = MessageType.Topic;
/// registration.MessageName = "Topic.Frequency";
/// registration.RegistrationType = RegistrationType.Produce;
/// serviceBusService.Register(registration);
///
/// // Start publishing messages to the bus asynchronously.
/// new Thread(delegate()
/// {
/// Message message = new Message();
/// message.Type = registration.MessageType;
/// message.Name = registration.MessageName;
/// message.Format = "application/octet-stream";
///
/// Random random = new Random(59);
/// while (serviceBusService.State == CommunicationState.Opened)
/// {
/// message.Time = DateTime.UtcNow;
/// message.Content = BitConverter.GetBytes(random.Next(61));
/// serviceBusService.Publish(message);
///
/// Thread.Sleep(5000);
/// }
/// }).Start();
///
/// // Shutdown.
/// Console.Write("Press Enter key to stop...");
/// Console.ReadLine();
/// serviceBusService.Close();
/// }
///
/// public void ProcessMessage(Message message)
/// {
/// // This method will not be invoked since we are not consuming messages.
/// throw new NotSupportedException();
/// }
/// }
///
/// This example shows how to subscribe to for receiving s:
///
/// using System;
/// using System.ServiceModel;
///
/// class Program : IServiceBusServiceCallback
/// {
/// static void Main(string[] args)
/// {
/// // NOTE: Service reference to the service bus service must be added to generate the service proxy.
///
/// // Initialize auto-generated service bus service proxy.
/// InstanceContext callbackContext = new InstanceContext(new Program());
/// ServiceBusServiceClient serviceBusService = new ServiceBusServiceClient(callbackContext, "NetTcpBinding_IServiceBusService");
///
/// // Subscribe with service bus service to receive messages.
/// RegistrationRequest registration = new RegistrationRequest();
/// registration.MessageType = MessageType.Topic;
/// registration.MessageName = "Topic.Frequency";
/// registration.RegistrationType = RegistrationType.Consume;
/// serviceBusService.Register(registration);
///
/// // Shutdown.
/// Console.WriteLine("Press Enter key to stop...");
/// Console.WriteLine();
/// Console.ReadLine();
/// serviceBusService.Close();
/// }
///
/// public void ProcessMessage(Message message)
/// {
/// if (message.Format != "application/octet-stream")
/// Console.WriteLine("Message format '{0}' is not supported", message.Format);
/// else
/// Console.WriteLine("Message received: {0} Hz", BitConverter.ToInt32(message.Content, 0));
/// }
/// }
///
/// This example shows how to monitor remotely:
///
/// using System;
/// using System.ServiceModel;
/// using System.Threading;
/// using GSF;
///
/// class Program : IServiceBusServiceCallback
/// {
/// static void Main(string[] args)
/// {
/// // NOTE: Service reference to the service bus service must be added to generate the service proxy.
///
/// // Initialize auto-generated service bus service proxy.
/// InstanceContext callbackContext = new InstanceContext(new Program());
/// ServiceBusServiceClient serviceBusService = new ServiceBusServiceClient(callbackContext, "NetTcpBinding_IServiceBusService");
/// serviceBusService.ChannelFactory.Open();
///
/// // Start querying service bus service status asynchronously.
/// new Thread(delegate()
/// {
/// while (serviceBusService.State == CommunicationState.Opened)
/// {
/// Console.Clear();
/// Console.WriteLine(new string('-', 79));
/// Console.WriteLine("|" + "Service Bus Status".CenterText(77) + "|");
/// Console.WriteLine(new string('-', 79));
/// Console.WriteLine();
///
/// // Show clients.
/// Console.Write("Client ID".PadRight(25));
/// Console.Write(" ");
/// Console.Write("Connected".PadRight(21));
/// Console.Write(" ");
/// Console.Write("Msg. Produced".PadRight(15));
/// Console.Write(" ");
/// Console.Write("Msg. Consumed".PadRight(15));
/// Console.WriteLine();
/// Console.Write(new string('-', 25));
/// Console.Write(" ");
/// Console.Write(new string('-', 21));
/// Console.Write(" ");
/// Console.Write(new string('-', 15));
/// Console.Write(" ");
/// Console.Write(new string('-', 15));
/// Console.WriteLine();
/// foreach (ClientInfo client in serviceBusService.GetClients())
/// {
/// Console.Write(client.SessionId.TruncateRight(25).PadRight(25));
/// Console.Write(" ");
/// Console.Write(client.ConnectedAt.ToString("MM/dd/yy hh:mm:ss tt").PadRight(21));
/// Console.Write(" ");
/// Console.Write(client.MessagesProduced.ToString().PadRight(15));
/// Console.Write(" ");
/// Console.Write(client.MessagesConsumed.ToString().PadRight(15));
/// Console.WriteLine();
/// }
/// Console.WriteLine();
///
/// // Show queues.
/// Console.Write("Queue Name".PadRight(25));
/// Console.Write(" ");
/// Console.Write("Producers".PadRight(10));
/// Console.Write(" ");
/// Console.Write("Consumers".PadRight(10));
/// Console.Write(" ");
/// Console.Write("Msg. Received".PadRight(15));
/// Console.Write(" ");
/// Console.Write("Msg. Processed".PadRight(15));
/// Console.WriteLine();
/// Console.Write(new string('-', 25));
/// Console.Write(" ");
/// Console.Write(new string('-', 10));
/// Console.Write(" ");
/// Console.Write(new string('-', 10));
/// Console.Write(" ");
/// Console.Write(new string('-', 15));
/// Console.Write(" ");
/// Console.Write(new string('-', 15));
/// Console.WriteLine();
/// foreach (RegistrationInfo queue in serviceBusService.GetQueues())
/// {
/// Console.Write(queue.MessageName.PadRight(25));
/// Console.Write(" ");
/// Console.Write(queue.Producers.Length.ToString().PadRight(15));
/// Console.Write(" ");
/// Console.Write(queue.Consumers.Length.ToString().PadRight(15));
/// Console.Write(" ");
/// Console.Write(queue.MessagesReceived.ToString().PadRight(15));
/// Console.Write(" ");
/// Console.Write(queue.MessagesProcessed.ToString().PadRight(15));
/// Console.WriteLine();
/// }
/// Console.WriteLine();
///
/// // Show topics.
/// Console.Write("Topic Name".PadRight(25));
/// Console.Write(" ");
/// Console.Write("Producers".PadRight(10));
/// Console.Write(" ");
/// Console.Write("Consumers".PadRight(10));
/// Console.Write(" ");
/// Console.Write("Msg. Received".PadRight(15));
/// Console.Write(" ");
/// Console.Write("Msg. Processed".PadRight(15));
/// Console.WriteLine();
/// Console.Write(new string('-', 25));
/// Console.Write(" ");
/// Console.Write(new string('-', 10));
/// Console.Write(" ");
/// Console.Write(new string('-', 10));
/// Console.Write(" ");
/// Console.Write(new string('-', 15));
/// Console.Write(" ");
/// Console.Write(new string('-', 15));
/// Console.WriteLine();
/// foreach (RegistrationInfo topic in serviceBusService.GetTopics())
/// {
/// Console.Write(topic.MessageName.PadRight(25));
/// Console.Write(" ");
/// Console.Write(topic.Producers.Length.ToString().PadRight(15));
/// Console.Write(" ");
/// Console.Write(topic.Consumers.Length.ToString().PadRight(15));
/// Console.Write(" ");
/// Console.Write(topic.MessagesReceived.ToString().PadRight(15));
/// Console.Write(" ");
/// Console.Write(topic.MessagesProcessed.ToString().PadRight(15));
/// Console.WriteLine();
/// }
/// Console.WriteLine();
/// Console.Write("Press Enter key to stop...");
///
/// Thread.Sleep(5000);
/// }
/// }).Start();
///
/// // Shutdown.
/// Console.ReadLine();
/// serviceBusService.Close();
/// }
///
/// public void ProcessMessage(Message message)
/// {
/// // This method will not be invoked since we are not consuming messages.
/// throw new NotSupportedException();
/// }
/// }
///
///
///
///
///
///
///
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class ServiceBusService : SelfHostingService, IServiceBusService
{
#region [ Members ]
// Nested Types
private class PublishContext
{
public PublishContext(Message message, RegistrationInfo registration)
{
Message = message;
Registration = registration;
}
public readonly Message Message;
public readonly RegistrationInfo Registration;
}
// Constants
///
/// Specifies the default value for the property.
///
public const int DefaultBufferThreshold = -1;
///
/// Specifies the default value for the property.
///
public const MessageProcessingMode DefaultProcessingMode = MessageProcessingMode.Sequential;
// Fields
private int m_bufferThreshold;
private MessageProcessingMode m_processingMode;
private readonly Dictionary m_clients;
private readonly Dictionary m_queues;
private readonly Dictionary m_topics;
private readonly ReaderWriterLockSlim m_clientsLock;
private readonly ReaderWriterLockSlim m_queuesLock;
private readonly ReaderWriterLockSlim m_topicsLock;
private ProcessList m_publishQueue;
private long m_discardedMessages;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of the class.
///
public ServiceBusService()
{
// Override base class settings.
Singleton = true;
PublishMetadata = true;
PersistSettings = true;
// Initialize member variables.
m_bufferThreshold = DefaultBufferThreshold;
m_processingMode = DefaultProcessingMode;
m_clients = new Dictionary(StringComparer.CurrentCultureIgnoreCase);
m_queues = new Dictionary(StringComparer.CurrentCultureIgnoreCase);
m_topics = new Dictionary(StringComparer.CurrentCultureIgnoreCase);
m_clientsLock = new ReaderWriterLockSlim();
m_queuesLock = new ReaderWriterLockSlim();
m_topicsLock = new ReaderWriterLockSlim();
}
#endregion
#region [ Properties ]
///
/// Gets or sets a boolean value that indicates whether the is currently enabled.
///
public override bool Enabled
{
get
{
return (m_publishQueue != null && m_publishQueue.Enabled);
}
set
{
if (value && m_publishQueue == null)
Initialize();
m_publishQueue.Enabled = value;
}
}
///
/// Gets the descriptive status of the .
///
public override string Status
{
get
{
StringBuilder status = new StringBuilder();
status.Append(" Buffer threshold: ");
status.Append(m_bufferThreshold == -1 ? "Disabled" : m_bufferThreshold.ToString());
status.AppendLine();
status.Append(" Processing mode: ");
status.Append(m_processingMode);
status.AppendLine();
m_clientsLock.EnterReadLock();
try
{
status.AppendFormat(" Number of clients: {0}", m_clients.Count);
}
finally
{
m_clientsLock.ExitReadLock();
}
status.AppendLine();
m_queuesLock.EnterReadLock();
try
{
status.AppendFormat(" Number of queues: {0}", m_queues.Count);
}
finally
{
m_queuesLock.ExitReadLock();
}
status.AppendLine();
m_topicsLock.EnterReadLock();
try
{
status.AppendFormat(" Number of topics: {0}", m_topics.Count);
}
finally
{
m_topicsLock.ExitReadLock();
}
status.AppendLine();
if (m_publishQueue != null)
{
ProcessQueueStatistics statistics = m_publishQueue.CurrentStatistics;
status.Append(" Messages received: ");
status.Append(statistics.QueueCount + statistics.TotalProcessedItems + statistics.ItemsBeingProcessed + m_discardedMessages);
status.AppendLine();
status.Append(" Messages discarded: ");
status.Append(m_discardedMessages);
status.AppendLine();
status.Append(" Messages processed: ");
status.Append(statistics.TotalProcessedItems);
status.AppendLine();
status.Append(" Messages being processed: ");
status.Append(statistics.ItemsBeingProcessed);
status.AppendLine();
}
return status.ToString();
}
}
///
/// Gets or sets the maximum number of s that can be buffered for distribution by the before the
/// the oldest buffered s are discarded to keep memory consumption in check by avoiding flooding.
///
/// Set to -1 to disable discarding of s.
public int BufferThreshold
{
get
{
return m_bufferThreshold;
}
set
{
if (value < 0)
value = -1;
m_bufferThreshold = value;
}
}
///
/// Gets or sets the used by the for processing distribution.
///
public MessageProcessingMode ProcessingMode
{
get
{
return m_processingMode;
}
set
{
m_processingMode = value;
}
}
#endregion
#region [ Methods ]
///
/// Initializes the .
///
/// The specified is not supported.
public override void Initialize()
{
base.Initialize();
if (m_publishQueue == null)
{
// Instantiate the process queue.
if (m_processingMode == MessageProcessingMode.Parallel)
m_publishQueue = ProcessList.CreateAsynchronousQueue(PublishMessages);
else if (m_processingMode == MessageProcessingMode.Sequential)
m_publishQueue = ProcessList.CreateRealTimeQueue(PublishMessages);
else
throw new NotSupportedException(string.Format("Processing mode '{0}' is not supported", m_processingMode));
// Start the process queue.
m_publishQueue.Start();
}
}
///
/// Saves settings to the config file if the property is set to true.
///
public override void SaveSettings()
{
base.SaveSettings();
if (PersistSettings)
{
// Save settings under the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory];
settings["BufferThreshold", true].Update(m_bufferThreshold);
settings["ProcessingMode", true].Update(m_processingMode);
config.Save();
}
}
///
/// Loads saved settings from the config file if the property is set to true.
///
public override void LoadSettings()
{
base.LoadSettings();
if (PersistSettings)
{
// Load settings from the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory];
settings.Add("BufferThreshold", m_bufferThreshold, "Maximum number of messages that can be queued for distribution before the oldest ones are discarded.");
settings.Add("ProcessingMode", m_processingMode, "Processing mode (Parallel; Sequential) to be used for the distribution of messages.");
BufferThreshold = settings["BufferThreshold"].ValueAs(m_bufferThreshold);
ProcessingMode = settings["ProcessingMode"].ValueAs(m_processingMode);
}
}
///
/// Registers with the to produce or consume s.
///
/// An containing registration data.
public virtual void Register(RegistrationRequest request)
{
// Initialize if uninitialized.
Initialize();
// Save client information if not already present.
ClientInfo client;
m_clientsLock.EnterUpgradeableReadLock();
try
{
if (!m_clients.TryGetValue(OperationContext.Current.SessionId, out client))
{
m_clientsLock.EnterWriteLock();
try
{
client = new ClientInfo(OperationContext.Current);
m_clients.Add(client.SessionId, client);
client.OperationContext.Channel.Faulted += OnChannelFaulted;
client.OperationContext.Channel.Closing += OnChannelClosing;
}
finally
{
m_clientsLock.ExitWriteLock();
}
}
}
finally
{
m_clientsLock.ExitUpgradeableReadLock();
}
// Retrieve registration information.
RegistrationInfo registration;
if (request.MessageType == MessageType.Queue)
{
// Queue
m_queuesLock.EnterUpgradeableReadLock();
try
{
if (!m_queues.TryGetValue(request.MessageName, out registration))
{
m_queuesLock.EnterWriteLock();
try
{
registration = new RegistrationInfo(request);
m_queues.Add(request.MessageName, registration);
}
finally
{
m_queuesLock.ExitWriteLock();
}
}
}
finally
{
m_queuesLock.ExitUpgradeableReadLock();
}
}
else if (request.MessageType == MessageType.Topic)
{
// Topic
m_topicsLock.EnterUpgradeableReadLock();
try
{
if (!m_topics.TryGetValue(request.MessageName, out registration))
{
m_topicsLock.EnterWriteLock();
try
{
registration = new RegistrationInfo(request);
m_topics.Add(request.MessageName, registration);
}
finally
{
m_topicsLock.ExitWriteLock();
}
}
}
finally
{
m_topicsLock.ExitUpgradeableReadLock();
}
}
else
{
// Unsupported
throw new NotSupportedException(string.Format("Message type '{0}' is not supported by this operation", request.MessageType));
}
// Update registration information.
if (registration != null)
{
List clients = (request.RegistrationType == RegistrationType.Produce ? registration.Producers : registration.Consumers);
lock (clients)
{
if (!clients.Contains(client))
clients.Add(client);
}
}
}
///
/// Unregisters a previous registration with the to produce or consume s
///
/// The used when registering.
public virtual void Unregister(RegistrationRequest request)
{
// Retrieve registration information.
RegistrationInfo registration;
if (request.MessageType == MessageType.Queue)
{
// Queue
m_queuesLock.EnterReadLock();
try
{
m_queues.TryGetValue(request.MessageName, out registration);
}
finally
{
m_queuesLock.ExitReadLock();
}
}
else if (request.MessageType == MessageType.Topic)
{
// Topic
m_topicsLock.EnterReadLock();
try
{
m_topics.TryGetValue(request.MessageName, out registration);
}
finally
{
m_topicsLock.ExitReadLock();
}
}
else
{
// Unsupported
throw new NotSupportedException(string.Format("Message type '{0}' is not supported by this operation", request.MessageType));
}
// Update registration information.
if (registration != null)
{
List clients = (request.RegistrationType == RegistrationType.Produce ? registration.Producers : registration.Consumers);
lock (clients)
{
clients.RemoveAt(clients.FindIndex(client => client.SessionId == OperationContext.Current.SessionId));
}
}
}
///
/// Sends the to the for distribution amongst its registered consumers.
///
/// The that is to be distributed.
public virtual void Publish(Message message)
{
// Retrieve publisher information.
ClientInfo client;
m_clientsLock.EnterReadLock();
try
{
// Update statistics data.
if (m_clients.TryGetValue(OperationContext.Current.SessionId, out client))
Interlocked.Increment(ref client.MessagesProduced);
}
finally
{
m_clientsLock.ExitReadLock();
}
// Retrieve registration information.
RegistrationInfo registration;
if (message.Type == MessageType.Queue)
{
// Queue
m_queuesLock.EnterReadLock();
try
{
m_queues.TryGetValue(message.Name, out registration);
}
finally
{
m_queuesLock.ExitReadLock();
}
}
else if (message.Type == MessageType.Topic)
{
// Topic
m_topicsLock.EnterReadLock();
try
{
m_topics.TryGetValue(message.Name, out registration);
}
finally
{
m_topicsLock.ExitReadLock();
}
}
else
{
// Unsupported
throw new NotSupportedException(string.Format("Message type '{0}' is not supported by this operation", message.Type));
}
// Queue message for distribution.
if (registration != null && m_publishQueue != null)
{
Interlocked.Increment(ref registration.MessagesReceived);
m_publishQueue.Add(new PublishContext(message, registration));
}
}
///
/// Gets the latest distributed to the subscribers of the specified .
///
/// The topic used when registering.
/// The latest distributed to the subscribers.
public virtual Message GetLatestMessage(RegistrationRequest topic)
{
// Retrieve registration information.
RegistrationInfo registration;
m_topicsLock.EnterReadLock();
try
{
m_topics.TryGetValue(topic.MessageName, out registration);
}
finally
{
m_topicsLock.ExitReadLock();
}
// Retrieve the latest message.
if (registration != null)
{
lock (registration.Consumers)
{
if (registration.Consumers.Exists(consumer => consumer.SessionId == OperationContext.Current.SessionId))
// Requestor has subscribed to the topic.
return registration.LatestMessage;
else
// Requestor didn't subscribe to the topic.
return null;
}
}
else
{
return null;
}
}
///
/// Gets a list of all clients connected to the .
///
/// An of objects.
public virtual ICollection GetClients()
{
return m_clients.Values;
}
///
/// Gets a list of all s registered on the .
///
/// An of objects.
public virtual ICollection GetQueues()
{
return m_queues.Values;
}
///
/// Gets a list of all s registered on the .
///
/// An of objects.
public virtual ICollection GetTopics()
{
return m_topics.Values;
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
// This will be done regardless of whether the object is finalized or disposed.
if (disposing)
{
// This will be done only when the object is disposed by calling Dispose().
// Disconnect all clients.
if (m_clients != null)
{
List clientIds;
m_clientsLock.EnterReadLock();
try
{
clientIds = new List(m_clients.Keys);
}
finally
{
m_clientsLock.ExitReadLock();
}
foreach (string clientId in clientIds)
{
DisconnectClient(clientId);
}
}
// Remove queue registrations.
if (m_queues != null)
{
m_queuesLock.EnterWriteLock();
try
{
foreach (RegistrationInfo registration in m_queues.Values)
{
registration.Dispose();
}
m_queues.Clear();
}
finally
{
m_queuesLock.ExitWriteLock();
}
}
// Remove topic registrations.
if (m_topics != null)
{
m_topicsLock.EnterWriteLock();
try
{
foreach (RegistrationInfo registration in m_topics.Values)
{
registration.Dispose();
}
m_topics.Clear();
}
finally
{
m_topicsLock.ExitWriteLock();
}
}
if (m_publishQueue != null)
m_publishQueue.Dispose();
if (m_clientsLock != null)
m_clientsLock.Dispose();
if (m_queuesLock != null)
m_queuesLock.Dispose();
if (m_topicsLock != null)
m_topicsLock.Dispose();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
}
private void PublishMessages(PublishContext[] contexts)
{
// Process distribution of all the messages.
int requeCursor = 0;
foreach (PublishContext context in contexts)
{
lock (context.Registration.Consumers)
{
if (context.Registration.Consumers.Count > 0)
{
// Distribute message to all subscribed clients.
foreach (ClientInfo client in context.Registration.Consumers)
{
try
{
client.OperationContext.GetCallbackChannel().ProcessMessage(context.Message);
Interlocked.Increment(ref client.MessagesConsumed);
if (context.Message.Type == MessageType.Queue)
// Queue messages gets delivered to the first client only.
break;
}
catch
{
// Disconnect the subscriber if an error is encountered during transmission.
try
{
if (client.OperationContext.Channel.State == CommunicationState.Opened)
client.OperationContext.Channel.Close();
}
catch
{
}
}
}
PublishComplete(context);
}
else
{
// No clients are subscribed to the queue or topic.
if (context.Message.Type == MessageType.Queue)
// Preserve queue messages since their delivery is guaranteed.
m_publishQueue.Insert(requeCursor++, context);
else
// Discard topic messages since their delivery is not guaranteed.
PublishComplete(context);
}
}
}
// Keep message buffer in check if specified.
if (m_bufferThreshold > 0 && m_publishQueue.Count > m_bufferThreshold)
{
int discardCount = m_publishQueue.Count - m_bufferThreshold;
m_publishQueue.RemoveRange(0, discardCount);
Interlocked.Add(ref m_discardedMessages, discardCount);
}
}
private void PublishComplete(PublishContext context)
{
// Save the message for on-demand request of subscribers.
context.Registration.LatestMessage = context.Message;
// Update the count for the number of messages processed.
Interlocked.Increment(ref context.Registration.MessagesProcessed);
}
private void DisconnectClient(string clientId)
{
// Retrieve client information.
ClientInfo client;
m_clientsLock.EnterUpgradeableReadLock();
try
{
if (m_clients.TryGetValue(clientId, out client))
{
// Remove client.
m_clientsLock.EnterWriteLock();
try
{
m_clients.Remove(clientId);
client.OperationContext.Channel.Faulted -= OnChannelFaulted;
client.OperationContext.Channel.Closing -= OnChannelClosing;
}
finally
{
m_clientsLock.ExitWriteLock();
}
// Close channel.
try
{
if (client.OperationContext.Channel.State == CommunicationState.Opened)
client.OperationContext.Channel.Close();
}
catch
{
}
}
}
finally
{
m_clientsLock.ExitUpgradeableReadLock();
}
// Remove client registrations.
if (client != null)
{
// Remove any queue registrations.
m_queuesLock.EnterReadLock();
try
{
foreach (RegistrationInfo registration in m_queues.Values)
{
lock (registration.Producers)
{
registration.Producers.Remove(client);
}
lock (registration.Consumers)
{
registration.Consumers.Remove(client);
}
}
}
finally
{
m_queuesLock.ExitReadLock();
}
// Remove any topic registrations.
m_topicsLock.EnterReadLock();
try
{
foreach (RegistrationInfo registration in m_topics.Values)
{
lock (registration.Producers)
{
registration.Producers.Remove(client);
}
lock (registration.Consumers)
{
registration.Consumers.Remove(client);
}
}
}
finally
{
m_topicsLock.ExitReadLock();
}
}
}
private void SaveMessages()
{
// For use later when enabling message persistance.
}
private void LoadMessages()
{
// For use later when enabling message persistance.
}
private void OnChannelClosing(object sender, EventArgs e)
{
DisconnectClient(((IContextChannel)sender).SessionId);
}
private void OnChannelFaulted(object sender, EventArgs e)
{
DisconnectClient(((IContextChannel)sender).SessionId);
}
#endregion
}
}