Skip to content

Instantly share code, notes, and snippets.

@RobinHerbots
Last active October 11, 2023 20:25
Show Gist options
  • Save RobinHerbots/1308811 to your computer and use it in GitHub Desktop.
Save RobinHerbots/1308811 to your computer and use it in GitHub Desktop.
WCF ProxyBase with channel reuse
using System;
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading;
using System.Reflection;
namespace ServiceProxies
{
public class PooledProxyBase<TChannel> : IDisposable where TChannel : class
{
private class ChannelStruct : IDisposable
{
private readonly TChannel _channel;
private readonly IClientChannel _clientChannel;
private readonly long _originalOperationTimeoutTicks;
private readonly DateTime _creationTime;
public ChannelStruct(ChannelFactory<TChannel> channelFactory)
{
_channel = channelFactory.CreateChannel();
_clientChannel = _channel as IClientChannel;
_creationTime = DateTime.Now;
_originalOperationTimeoutTicks = ((IClientChannel)_channel).OperationTimeout.Ticks;
}
public CommunicationState State
{
get
{
return _clientChannel.State;
}
}
public TChannel Channel
{
get
{
if (State == CommunicationState.Created)
_clientChannel.Open();
return _channel;
}
}
public bool TimeShift()
{
TimeSpan currentTimeSpan = DateTime.Now - _creationTime;
var operationTimeLeft = _clientChannel.OperationTimeout.Ticks - currentTimeSpan.Ticks;
if (operationTimeLeft <= 100) //System.Timeout - proxy is idle or almost
{
Debug.WriteLine("Dispose Proxy " + typeof(TChannel) + " is idle " + operationTimeLeft);
Dispose(); //prevent exception
return false;
}
//add extra time
var extraTime = _originalOperationTimeoutTicks - (operationTimeLeft);
_clientChannel.OperationTimeout = new TimeSpan(_clientChannel.OperationTimeout.Ticks + extraTime);
Debug.WriteLine("Timeshift Proxy " + typeof(TChannel) + " - " + extraTime);
return true;
}
#region IDisposable Members
public void Dispose()
{
switch (_clientChannel.State)
{
case CommunicationState.Closed:
case CommunicationState.Closing:
break;
case CommunicationState.Opened:
try
{
_clientChannel.Close(); //gracefully close
}
catch (Exception)
{
_clientChannel.Abort(); //on exception kill the connection
}
break;
default:
_clientChannel.Abort();
break;
}
}
#endregion
}
private static ChannelFactory<TChannel> _channelFactory;
private static Queue<ChannelStruct> _freeClientChannels;
private ChannelStruct _channelStruct;
private int _communicationErrorRetries = 3;
private const int _COMMUNICATION_ERROR_SLEEP_TIME = 6000;
private ChannelStruct GetFreeChannel()
{
lock (_freeClientChannels)
{
if (_freeClientChannels.Count == 0)
{
_freeClientChannels.Enqueue(new ChannelStruct(_channelFactory));
Debug.WriteLine("New Proxy " + typeof(TChannel));
}
var channelStruct = _freeClientChannels.Dequeue();
switch (channelStruct.State)
{
case CommunicationState.Closed:
case CommunicationState.Closing:
break;
case CommunicationState.Faulted:
channelStruct.Dispose();
break;
default:
if (channelStruct.TimeShift())
{
Debug.WriteLine("Reuse Proxy " + typeof(TChannel));
return channelStruct;
}
break;
}
return GetFreeChannel();
}
}
#region Constructor
public PooledProxyBase()
: this(new ChannelFactory<TChannel>("*", null))
{
}
public PooledProxyBase(ChannelFactory<TChannel> channelFactory)
{
if (_freeClientChannels == null) _freeClientChannels = new Queue<ChannelStruct>();
if (_channelFactory == null) _channelFactory = channelFactory;
_channelStruct = GetFreeChannel();
}
#endregion
#region Channel invokers
protected T Channel<T>(string methodName, params object[] args)
{
try
{
var method = typeof(TChannel).GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
if (method == null) throw new NotSupportedException(methodName);
return (T)method.Invoke(_channelStruct.Channel, args);
}
catch (TargetInvocationException ex)
{
if (ex.InnerException is FaultException) //pass FaultExceptions
throw ex.InnerException;
var innerExceptionType = ex.InnerException.GetType();
var innerInnerExceptionType = ex.InnerException.InnerException != null ? ex.InnerException.InnerException.GetType() : null;
if (_communicationErrorRetries > 0 && (innerExceptionType == typeof(CommunicationException) && innerInnerExceptionType != typeof(SerializationException)))
{
_channelStruct.Dispose();
//give some 'think' time ;-)
Thread.Sleep(_COMMUNICATION_ERROR_SLEEP_TIME / _communicationErrorRetries--);
_channelStruct = GetFreeChannel();
return Channel<T>(methodName, args);
}
throw new ProxyException(ex.InnerException);
}
}
protected void Channel(string methodName, params object[] args)
{
try
{
var method = typeof(TChannel).GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
if (method == null) throw new NotSupportedException(methodName);
method.Invoke(_channelStruct.Channel, args);
}
catch (TargetInvocationException ex)
{
if (ex.InnerException is FaultException) //pass FaultExceptions
throw ex.InnerException;
var innerExceptionType = ex.InnerException.GetType();
var innerInnerExceptionType = ex.InnerException.InnerException.GetType();
if (_communicationErrorRetries > 0 && (innerExceptionType == typeof(CommunicationException) && innerInnerExceptionType != typeof(SerializationException)))
{
_channelStruct.Dispose();
//give some 'think' time ;-)
Thread.Sleep(_COMMUNICATION_ERROR_SLEEP_TIME / _communicationErrorRetries--);
_channelStruct = GetFreeChannel();
Channel(methodName, args);
}
throw new ProxyException(ex.InnerException);
}
}
#endregion
public void Dispose()
{
lock (_freeClientChannels)
{
_freeClientChannels.Enqueue(_channelStruct);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment