I implemented my own code of Publish/subscribe pattern implementation with WCF with WSDualHttpBinding, but i’ve a little problem with timeouts that i explain later, for now let me show what i’m doing:
public interface IEventSubscriber
{
[OperationContract]
void NotifyEvent(EventNotification notification);
[OperationContract]
void NotifyServiceDisconnecting();
}
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IEventSubscriber))]
public interface IEventPublisherService
{
[OperationContract(IsOneWay = false, IsInitiating = true)]
void Subscribe(string culture);
[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = true)]
void Unsubscribe();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
internal class EventPublisherServiceImpl : IEventPublisherService
{
ServiceHost host;
public bool StartService()
{
bool ret = false;
try
{
Uri baseAddress = new Uri(ConfigurationManager.AppSettings[GlobalConstants.CfgKeyConfigEventPublishserServiceBaseAddress].ToString());
EventHelper.AddEvent(string.Format("Event Publisher Service on: {0}", baseAddress.ToString()));
host = new ServiceHost(this, baseAddress);
// duplex session enable http binding
WSDualHttpBinding httpBinding = new WSDualHttpBinding(WSDualHttpSecurityMode.None);
httpBinding.ReceiveTimeout = TimeSpan.FromMinutes(10);
httpBinding.ReliableSession = new ReliableSession();
httpBinding.ReliableSession.Ordered = true;
httpBinding.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(10);
host.AddServiceEndpoint(typeof(IEventPublisherService), httpBinding, baseAddress);
// Enable metadata publishing.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);
// Open the ServiceHost to start listening for messages.
host.Open();
ret = true;
}
catch (Exception e)
{
EventHelper.AddException(e.Message);
}
return ret;
}
...
}
now in my implementation class i have a list of subscribers that are stored in memory, when a new notification arrived the following code is performed for each subscriber:
...
/// <summary>
/// List of active subscribers
/// </summary>
private static Dictionary<IEventSubscriber, string> subscribers = new Dictionary<IEventSubscriber, string>();
...
that i use it like this:
internal void Subscribe(string culture)
{
lock (subscribers)
{
// Get callback contract as specified on the service definition
IEventSubscriber callback = OperationContext.Current.GetCallbackChannel<IEventSubscriber>();
// Add subscriber to the list of active subscribers
if (!subscribers.ContainsKey(callback))
{
subscribers.Add(callback, culture);
}
}
}
...
private void OnNotificationEvent(NormalEvent notification)
{
lock (subscribers)
{
List<IEventSubscriber> listToRemove = new List<IEventSubscriber>();
// Method signature: Parallel.ForEach(IEnumerable<TSource> source, Action<TSource> body)
Parallel.ForEach(subscribers, kvp =>
{
try
{
kvp.Key.NotifyEvent(new EventNotification(notification, kvp.Value));
}
catch (Exception ex)
{
EventHelper.AddException(string.Format("Error notifying event notification to client: {0} - removing this one", ex.Message));
listToRemove.Add(kvp.Key);
}
} //close lambda expression
); //close method invocation
Parallel.ForEach(listToRemove, subs =>
{
try
{
subs.NotifyServiceDisconnecting();
}
catch (Exception ex) {
EventHelper.AddException(string.Format("Failed to notify client that is to be removed: {0}",
ex.Message));
}
subscribers.Remove(subs);
}
);
}
}
What is the problem with this, when timeouts are achieved (note that i set 10 minutes for ReceiveTimeout and inactive timeout) the subscribers that are in the list go to fault state, and the following exception is catched in the OnNotificationEvent
*The operation ‘NotifyEvent’ could not be completed because the sessionful channel timed out waiting to receive a message. To increase the timeout, either set the receiveTimeout property on the binding in your configuration file, or set the ReceiveTimeout property on the Binding directly. *
Ok i can increase the timeout value, but if i do this it will happen some time, eventually.
My question are: i’m doing something wrong when trying to implement this pattern? or exists any other way of implementing this pattern a better way that avoid this problem? or exist any way of reconnecting the faulted callback channel (for what i’m reading it’s not possible, but due to that i can’t notify the client that is connection was lost, letting the client blind not knowing that the communication ended!? or is a way to give knowledge that he lost communication with the publisher!?)
Of course solution like ping messages are out of date 🙂 but ok, if nothing better appears look like i’ve to implement something like that…
Thanks
For now the solution was to change the timeouts to have a infinite value: