在某些服务器上安装 4.5 运行时后,我们遇到了许多与 winsock 相关的错误。当在 .Net 4.5 运行时(在 4.0 上不可重现)上使用带有 ReliableSessions 的 WCF 时,我将问题追溯到 \Device\Afd 的句柄泄漏。
我编写了一个测试程序来隔离/证明问题。
假设一个非常虚拟的 WCF 服务和客户端(可以从 VS 生成):
[ServiceContract]
interface IMyService
{
[OperationContract]
void foo();
}
class MyService : IMyService
{
public void foo() { }
}
//simulate a svcutil/VS-generated service reference
class MyServiceClient : ClientBase<IMyService>
{
public MyServiceClient(Binding binding, EndpointAddress remoteAddress)
: base(binding, remoteAddress) { }
}
它是托管和消费的。
class Program
{
static void Main(string[] args)
{
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None,false);
EndpointAddress address = new EndpointAddress("net.tcp://localhost:6660");
//Warmup (to stabilze handles for threads etc...)
PerformTest(binding, address, 1);
//Try without ReliableSession
binding.ReliableSession.Enabled = false;
PerformTest(binding, address, 1000);
//Try with ReliableSession
binding.ReliableSession.Enabled = true;
PerformTest(binding, address, 1000);
}
private static void PerformTest(NetTcpBinding binding, EndpointAddress address, int nbConnections)
{
int nbHandlesBefore = Process.GetCurrentProcess().HandleCount;
//Create & open Service
var host = new ServiceHost(typeof(MyService));
host.AddServiceEndpoint(typeof(IMyService), binding, address.Uri);
host.Open();
//Connect/Disconnect many times
for (int i = 1; i <= nbConnections; i++)
{
using (var proxy = new MyServiceClient(binding, address))
{
proxy.Open();
proxy.Close();
}
Console.Write("\r {0}/{1} ", i, nbConnections);
}
host.Close();
int nbHandlesAfter = Process.GetCurrentProcess().HandleCount;
Console.WriteLine("Handle increase: {0}", nbHandlesAfter - nbHandlesBefore);
}
}
测试程序的输出是:
1/1 Handle increase: 144
1000/1000 Handle increase: -25
1000/1000 Handle increase: 1739
评论:
- 我知道我应该做更好的错误处理,因为 Close() 可以抛出,但在这种情况下它不会,所以我更喜欢在这里粘贴更少的代码。
- 在这里,我记录了所有句柄(不仅是 \Device\Afd)的句柄计数。这使得快速测试变得更容易,但我使用 sysinternals 的 handle.exe 进行了许多检查以验证句柄的名称/类型
- 句柄是客户端泄露的,而不是服务泄露的(至少在关闭服务主机后它们被正确清理)。
变化:
- 将 Proxy.Close() 更改为 Abort() 时,其中的所有句柄都会正确释放(但是服务很难关闭 - 正如预期的那样)
- 创建通道通过
IClientChannel proxy = ChannelFactory<IMyService>.CreateChannel(binding, address) as IClientChannel
给出相同的结果 - 通过 channelfactory 实例创建通道时,句柄被正确释放:
IClientChannel proxy = new ChannelFactory<IMyService>(binding, address).CreateChannel() as IClientChannel
- 将 ClientBase<>.CacheSetting 设置为 AlwaysOn,可以解决问题,但并非总是可行。
有谁知道为什么这个程序会泄漏句柄?