I am facing problem with client which cannot register callback channel on second start. I tried various things it seems I found a solution to remove Close() channel (ResetProxy() call in wrapper) call on shutdown of service. But this it turns out causing another problem: the server is crashing with error "The thread tried to read from or write to a virtual address for which it does not have the appropriate access". Additionally, there are network related issues which also result in same behavior. The solution is always to restart the server which is not really right option to fix callback channel registration. Could anyone suggest what could be the problem? I've done a lot of testing, and it seems this somehow relates to sudden stop of all clients (global stop of multiple services) and the number of services matters too. As result even new client cannot register with server until server is rebooted. This sounds like server is holding up closed channels.
Contract:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IAgentCallback))]
public interface IAgentManager
{
[OperationContract]
string RegisterAgent(string hostName);
[OperationContract]
bool Ping();
}
Client create callback code (truncated):
public class AgentCallbackDaemon
{
private void CreateManagerProxy()
{
Reset();
var isUnbound = true;
while (isUnbound)
{
try
{
ManagerProxy = new ProxyWrapper<AgentManagerProxy, IAgentManager>(CreateProxy);
ManagerProxy.ChannelStateChanged += HandleProxyConnectionStateChanged;
isUnbound = false;
}
catch (AddressAlreadyInUseException)
{
sLog.ErrorFormat("Port is already reserved, binding failed.");
}
catch (Exception error)
{
sLog.ErrorFormat($"No proxy due to {error}");
throw;
}
}
}
private AgentManagerProxy CreateProxy()
=> mCallbackChannelPortRange.IsCallbackPortRageSet() ? GetProxyWithPortRange() : GetProxyDefault();
private AgentManagerProxy GetProxyDefault()
=> new AgentManagerProxy(mAgentCallback, mManagerUri, GetServiceName());
private AgentManagerProxy GetProxyWithPortRange()
{
var minPort = mCallbackChannelPortRange.MinimumCallbackPortNumber;
var maxPort = mCallbackChannelPortRange.MaximumCallbackPortNumber;
return new AgentManagerProxy(mAgentCallback, mManagerUri, GetServiceName(), minPort, maxPort);
}
}
Client callback code (truncated):
public class AgentManagerProxy : DuplexClientBase<IAgentManager>, IAgentManager
{
public const string SERVICE_NAME = "AgentManager";
public AgentManagerProxy(IAgentCallback callback, string serviceAddress, string connectionId,
ushort minPort, ushort maxPort)
: base(callback, BindingAbstractFactory.DuplexServiceClientBindingFactory(connectionId,
minPort, maxPort), BindingUtility.CreateEndpoint(serviceAddress, SERVICE_NAME))
{
}
public string RegisterAgent(string hostName)
=> Channel.RegisterAgent(hostName);
public bool Ping() => return Channel.Ping();
}
public static class BindingAbstractFactory
{
public static Binding DuplexServiceClientBindingFactory(string connectionId, ushort minPort, ushort maxPort)
=> CreateDuplexServiceClientBinding(connectionId, minPort, maxPort);
private static Binding CreateDuplexServiceClientBinding(string connectionId, ushort minPort, ushort maxPort)
{
var binding = CreateDuplexServiceHostBinding();
if (binding is WSDualHttpBinding)
{
lock (sLock)
{
try
{
((WSDualHttpBinding)binding).ClientBaseAddress =
CreateClientBaseAddress(connectionId, minPort, maxPort);
}
catch (Exception error)
{
sLog.ErrorFormat("Unexpected exception: {0}", error);
throw error;
}
finally
{
Monitor.PulseAll(sLock);
}
}
}
return binding;
}
private static Binding CreateDuplexServiceHostBinding()
{
var binding = new WSDualHttpBinding
{
ReceiveTimeout = TimeSpan.MaxValue,
MaxReceivedMessageSize = int.MaxValue,
ReaderQuotas =
{
// Set the maximum sizes to allow big message sizes.
MaxArrayLength = int.MaxValue,
MaxBytesPerRead = int.MaxValue,
MaxDepth = int.MaxValue,
MaxNameTableCharCount = int.MaxValue,
MaxStringContentLength = int.MaxValue
},
Security =
{
// Disable security on control services.
// TODO: Once security concept has been clarified, update this to reflect security rules.
Mode = WSDualHttpSecurityMode.None,
Message = { ClientCredentialType = MessageCredentialType.None }
//// binding.Security.Mode = SecurityMode.None;
//// binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
}
};
return binding;
}
private static Uri CreateClientBaseAddress(string connectionId, ushort minPort, ushort maxPort)
{
var fullDnsName = Dns.GetHostEntry(Dns.GetHostName()).HostName;
int portNumber;
if (maxPort == ushort.MaxValue)
portNumber = LocalPorts.GetRandomAvailablePort(connectionId, minPort, maxPort);
else
portNumber = LocalPorts.GetNextAvailablePort(connectionId, minPort, maxPort);
return new Uri($@"http://{fullDnsName}:{portNumber}");
}
}
All this wrapped into proxy wrapper with Reset function which is called on service stop or any error (including network):
public class ProxyWrapper<TPROXY, TSERVICE> where TPROXY : ClientBase<TSERVICE> where TSERVICE : class
{
public delegate TPROXY CreateProxy();
private readonly object mProxyLock = new object();</summary>
private readonly CreateProxy mCreateProxy;
private TPROXY mProxy;
public ProxyWrapper(CreateProxy createProxyCallback)
{
mLog.Info.Write($"Creating Proxy for '{typeof(TPROXY).FullName}'");
mCreateProxy = createProxyCallback;
BuildProxy();
}
public event EventHandler<CommunicationState> ChannelStateChanged;
public TPROXY Proxy
{
get
{
lock (mProxyLock)
{
if (mProxy == null)
BuildProxy();
return mProxy;
}
}
}
public void ResetProxy()
{
mLog.Info.Write("Call ResetProxy()");
lock (mProxyLock)
{
try
{
if (mProxy == null)
return;
UnSubscribeFromChannelEvents();
CloseProxy();
}
catch (Exception ex)
{
// Catch all exceptions, and ignore them.
}
finally
{
mProxy = null;
}
}
}
private void RaiseChannelStateChanged(object sender, EventArgs e)
=> ChannelStateChanged?.Invoke(this, mProxy.InnerChannel.State);
private void RaiseChannelEnteredFaultyState()
=> ChannelStateChanged?.Invoke(this, CommunicationState.Faulted);
private void BuildProxy()
{
lock (mProxyLock)
{
if (mProxy != null)
ResetProxy();
mProxy = mCreateProxy();
SubscribeToChannelEvents();
}
}
private void SubscribeToChannelEvents()
{
if (mProxy?.InnerChannel == null)
return;
mProxy.InnerChannel.Faulted += OnInnerChannelFaulted;
mProxy.InnerChannel.Closed += RaiseChannelStateChanged;
mProxy.InnerChannel.Closing += RaiseChannelStateChanged;
mProxy.InnerChannel.Opened += RaiseChannelStateChanged;
mProxy.InnerChannel.Opening += RaiseChannelStateChanged;
}
private void UnSubscribeFromChannelEvents()
{
if (mProxy?.InnerChannel == null)
return;
mProxy.InnerChannel.Faulted -= OnInnerChannelFaulted;
mProxy.InnerChannel.Closed -= RaiseChannelStateChanged;
mProxy.InnerChannel.Closing -= RaiseChannelStateChanged;
mProxy.InnerChannel.Opened -= RaiseChannelStateChanged;
mProxy.InnerChannel.Opening -= RaiseChannelStateChanged;
}
private void OnInnerChannelFaulted(object sender, EventArgs e)
{
ResetProxy();
RaiseChannelEnteredFaultyState();
}
private void CloseProxy()
{
try
{
mProxy.Close();
}
catch (Exception ex)
{
try
{
mProxy.Abort();
}
catch (Exception abortException) { // ignored }
}
try
{
mProxy.ChannelFactory.Close();
}
catch (Exception ex)
{
try
{
mProxy.ChannelFactory.Abort();
}
catch (Exception abortException) { // ignored }
}
}
}
The server only maintains the callback list.
- Create Proxy for 'AgentManagerProxy'
- Call BuildProxy()
- mProxy.Open() [State=Created]
- call SubscribeToChannelEvents()
- ...............
- Service stopping...
- call ResetProxy()
- Call UnSubscribeFromChannelEvents()
- call CloseProxy(): mProxy.Close()
- call CloseProxy(): mProxy.ChannelFactory.Close()
Added logging for client shows this on first time start:
On second start service fails:
- Create Proxy for 'AgentManagerProxy'
- Call BuildProxy()
- mProxy.Open() [State=Created]
Error: No proxy due to System.TimeoutException: The request channel timed out attempting to send after 00:00:00. Increase the timeout value passed to the call to Request or increase the SendTimeout value on the Binding. The time allotted to this operation may have been a portion of a longer timeout. ---> System.TimeoutException: The HTTP request to 'http://xxxxx:9003/AgentManager' has exceeded the allotted timeout of 00:00:00. The time allotted to this operation may have been a portion of a longer timeout. at System.ServiceModel.Channels.HttpChannelUtilities.SetRequestTimeout(HttpWebRequest request, TimeSpan timeout) at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.SendRequest(Message message, TimeSpan timeout) at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout) --- End of inner exception stack trace ---