3

在某些服务器上安装 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,可以解决问题,但并非总是可行。

有谁知道为什么这个程序会泄漏句柄?

4

0 回答 0