起因是发现一个同事编写的程序运行两个月左右,占用了服务器20G左右的内存。用WinDbg查看发现存在大量的Async Pinned Handles,而它们的gcroot都来自于SocketAsyncEventArgs。下面是场景的简易模拟代码(为了说明问题添加了手动GC):

for (var i = ; i < ; ++i)
{
var endPoint = new IPEndPoint(IPAddress.Parse(host), port);
var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.ReceiveBufferSize = bufferSize;
socket.SendBufferSize = bufferSize; var iocp = new SocketAsyncEventArgs();
iocp.Completed += new EventHandler<SocketAsyncEventArgs>(OnIoSocketCompleted);
iocp.SetBuffer(new Byte[bufferSize], , bufferSize);
iocp.AcceptSocket = socket; try
{
socket.Connect(endPoint);
Console.WriteLine(i);
}
catch (SocketException ex)
{
Console.WriteLine(ex.Message);
} socket.Close();
//iocp.Dispose();
} GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

SocketAsyncEventArgs的SetBuffer函数内部会pin住buffer数据(查阅SetBufferInternal实现),它的析构函数会调用FreeOverlapped函数释放资源。而问题就是在于FreeOverlapped函数的实现和传递参数。来看一下Dispose、~SocketAsyncEventArgs的实现:

public void Dispose()
{
this.m_DisposeCalled = true;
if (Interlocked.CompareExchange(ref this.m_Operating, , ) != )
{
return;
}
this.FreeOverlapped(false);
GC.SuppressFinalize(this);
}
~SocketAsyncEventArgs()
{
this.FreeOverlapped(true);
}

两者都会调用FreeOverlapped函数释放,但是一个传递了false、一个传递了true参数。再来看FreeOverlapped实现:

private void FreeOverlapped(bool checkForShutdown)
{
if (!checkForShutdown || !NclUtilities.HasShutdownStarted)
{
if (this.m_PtrNativeOverlapped != null && !this.m_PtrNativeOverlapped.IsInvalid)
{
this.m_PtrNativeOverlapped.Dispose();
this.m_PtrNativeOverlapped = null;
this.m_Overlapped = null;
this.m_PinState = SocketAsyncEventArgs.PinState.None;
this.m_PinnedAcceptBuffer = null;
this.m_PinnedSingleBuffer = null;
this.m_PinnedSingleBufferOffset = ;
this.m_PinnedSingleBufferCount = ;
}
if (this.m_SocketAddressGCHandle.IsAllocated)
{
this.m_SocketAddressGCHandle.Free();
}
if (this.m_WSAMessageBufferGCHandle.IsAllocated)
{
this.m_WSAMessageBufferGCHandle.Free();
}
if (this.m_WSARecvMsgWSABufferArrayGCHandle.IsAllocated)
{
this.m_WSARecvMsgWSABufferArrayGCHandle.Free();
}
if (this.m_ControlBufferGCHandle.IsAllocated)
{
this.m_ControlBufferGCHandle.Free();
}
}
}

用WinDbg查看gchandles统计:

Statistics:
MT Count TotalSize Class Name
000007fb1bdb6ae8 System.Object
000007fb1bdb6b80 System.SharedStatics
000007fb1bdb7f58 System.Security.PermissionSet
000007fb1bdb6a10 System.ExecutionEngineException
000007fb1bdb6998 System.StackOverflowException
000007fb1bdb6920 System.OutOfMemoryException
000007fb1bdb6738 System.Exception
000007fb1bdb7b90 System.Threading.Thread
000007fb1bdb6c40 System.AppDomain
000007fb1bdb6a88 System.Threading.ThreadAbortException
000007fb1ae19770 System.Net.Logging+NclTraceSource
000007fb1bdbf958 System.RuntimeType+RuntimeTypeCache
000007fb1ae19900 System.Diagnostics.SourceSwitch
000007fb1bd64458 System.Object[]
000007fb1b6f5e40 System.Threading.OverlappedData
Total objects Handles:
Strong Handles:
Pinned Handles:
Async Pinned Handles:
Weak Long Handles:
Weak Short Handles:

确实存在1000个Async Pinned Handles,也就是说无法通过SocketAsyncEventArgs的析构函数释放SafeNativeOverlapped相关的资源。将示例代码的"iocp.Dispose();"注释去除,并重新执行再次查看gchandles:

Statistics:
MT Count TotalSize Class Name
000007fb1bdb6ae8 System.Object
000007fb1bdb6b80 System.SharedStatics
000007fb1bdb7f58 System.Security.PermissionSet
000007fb1bdb6a10 System.ExecutionEngineException
000007fb1bdb6998 System.StackOverflowException
000007fb1bdb6920 System.OutOfMemoryException
000007fb1bdb6738 System.Exception
000007fb1bdb7b90 System.Threading.Thread
000007fb1bdb6c40 System.AppDomain
000007fb1bdb6a88 System.Threading.ThreadAbortException
000007fb1ae19770 System.Net.Logging+NclTraceSource
000007fb1bdbf958 System.RuntimeType+RuntimeTypeCache
000007fb1ae19900 System.Diagnostics.SourceSwitch
000007fb1bd64458 System.Object[]
Total objects Handles:
Strong Handles:
Pinned Handles:
Weak Long Handles:
Weak Short Handles:

1000个Async Pinned Handles已不存在,但SocketAsyncEventArgs的析构函数从实现来看应该也可以完成释放,为什么失败了?

用!bpmd命令添加NclUtilities.HasShutdownStarted、 m_PtrNativeOverlapped.Dispose()的断点。

!bpmd System.dll System.Net.NclUtilities.get_HasShutdownStarted
!bpmd mscorlib.dll System.Runtime.InteropServices.SafeHandle.Dispose

以上函数无法命中,由于.NET Framework ngen的关系。实际可以看到SocketAsyncEventArgs的Finalize函数内联了FreeOverlapped。

:> !clrstack -a
OS Thread Id: 0x46c ()
Child SP IP Call Site
000000002656f940 000007ff138485e9 System.Net.Sockets.SocketAsyncEventArgs.FreeOverlapped(Boolean)
PARAMETERS:
this = <no data>
checkForShutdown = <no data> 000000002656f980 000007ff1385222d System.Net.Sockets.SocketAsyncEventArgs.Finalize()
PARAMETERS:
this (0x000000002656f9c0) = 0x000000000e140438 000000002656fd38 000007ff72c1c446 [DebuggerU2MCatchHandlerFrame: 000000002656fd38]

逐一单步执行可以发现NclUtilities.HasShutdownStarted也被内联,直接调用外部函数clr!SystemNative::HasShutdownStarted。

:> t
000007ff`138485ec 85c0 test eax,eax
:> t
000007ff`138485ee je 000007ff` [br=]
:> t
000007ff`138485f0 e8c798485f call clr!SystemNative::HasShutdownStarted (000007ff`72cd1ebc)

那问题就在于HasShutdownStarted的返回值了,如果为false则肯定会释放资源。

:> t
clr!SystemNative::HasShutdownStarted:
000007ff`72cd1ebc 4883ec28 sub rsp,28h
:> t
clr!SystemNative::HasShutdownStarted+0x4:
000007ff`72cd1ec0 f60501767a0004 test byte ptr [clr!g_fEEShutDown (000007ff`734794c8)], ds:000007ff`734794c8=
:> t
clr!SystemNative::HasShutdownStarted+0xb:
000007ff`72cd1ec7 751a jne clr!SystemNative::HasShutdownStarted+0x27 (000007ff`72cd1ee3) [br=]
:> t
clr!SystemNative::HasShutdownStarted+0x27:
000007ff`72cd1ee3 b901000000 mov ecx,
:> t
clr!SystemNative::HasShutdownStarted+0x2c:
000007ff`72cd1ee8 ebf2 jmp clr!SystemNative::HasShutdownStarted+0x20 (000007ff`72cd1edc)
:> t
clr!SystemNative::HasShutdownStarted+0x20:
000007ff`72cd1edc 8bc1 mov eax,ecx
:> t
clr!SystemNative::HasShutdownStarted+0x22:
000007ff`72cd1ede 4883c428 add rsp,28h
:> t
clr!SystemNative::HasShutdownStarted+0x26:
000007ff`72cd1ee2 c3 ret
:> t
000007ff`138485f5 0fb6c8 movzx ecx,al
:> t
000007ff`138485f8 85c9 test ecx,ecx

查看ecx的值发现竟然是1。

:> r ecx
ecx=
:> r al
al=

SocketAsyncEventArgs的释放问题的更多相关文章

  1. 【转】C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装

    http://blog.csdn.net/sqldebug_fan/article/details/17557341 1.SocketAsyncEventArgs介绍 SocketAsyncEvent ...

  2. 基于SocketAsyncEventArgs的版本

    文字水平差就慢慢开始练习,同时分享一下,项目中写的简单socket程序,不同方式的版本,今天上一个异步.可能实现高性能的处理方式.IOCP就不多说了,高性能的完成端口,可以实现套接字对象的复用,降低开 ...

  3. [转帖]译文:如何使用SocketAsyncEventArgs类(How to use the SocketAsyncEventArgs class)

    原文链接:http://norke.blog.163.com/blog/static/276572082011828104315941/ 引言 我一直在探寻一个高性能的Socket客户端代码.以前,我 ...

  4. 译文:如何使用SocketAsyncEventArgs类(How to use the SocketAsyncEventArgs class)

      转载自: http://blog.csdn.net/hulihui/article/details/3244520 原文:How to use the SocketAsyncEventArgs c ...

  5. (转)C#SocketAsyncEventArgs实现高效能多并发TCPSocket通信

    原文地址:http://freshflower.iteye.com/blog/2285272.http://freshflower.iteye.com/blog/2285286 一)服务器端 说到So ...

  6. 转 C#高性能Socket服务器SocketAsyncEventArgs的实现(IOCP)

    原创性申明 本文作者:小竹zz  博客地址:http://blog.csdn.net/zhujunxxxxx/article/details/43573879转载请注明出处引言 我一直在探寻一个高性能 ...

  7. C#高性能Socket服务器SocketAsyncEventArgs的实现(IOCP)

    网址:http://blog.csdn.net/zhujunxxxxx/article/details/43573879 引言 我一直在探寻一个高性能的Socket客户端代码.以前,我使用Socket ...

  8. C#高性能大容量SOCKET并发(十):SocketAsyncEventArgs线程模型

    原文:C#高性能大容量SOCKET并发(十):SocketAsyncEventArgs线程模型 线程模型 SocketAsyncEventArgs编程模式不支持设置同时工作线程个数,使用的NET的IO ...

  9. C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装

    原文:C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 1.SocketAsyncEventArgs介绍 SocketAsyncEventArgs是微软提供的高性能 ...

随机推荐

  1. iOS : 用 InterfaceBuilder 开始一个项目

    1.创建一个 xib 文件 : Main_iPhone.xib 更改 File's Owner 的 Class 为 UIApplication; 添加 1 个 Window .1 个 Object . ...

  2. 有限状态机(FSM)的Java 演示

    本文从简单的样例入手,逐步演变成很复杂的程序. 在简明 状态模式(5.8)中,状态之间的变换由外界控制,或者说.多种状态是切割的.无关的.状态模式最有趣的地方正是讨论其状态的变迁. 1.引子 空调(a ...

  3. Java学习之——Java Serializable

    1.什么是Serializable接口? http://en.wikipedia.org/wiki/Serialization Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个 ...

  4. Java 11正式发布,这几个逆天新特性教你写出更牛逼的代码

    就在前段时间,Oracle 官方宣布 Java 11 (18.9 LTS) 正式发布,可在生产环境中使用! 这无疑对我们来说是一大好的消息.作为一名java开发者来说,虽然又要去学习和了解java11 ...

  5. 4 云计算系列之Openstack简介与keystone安装

    preface KVM 是openstack虚拟化的基础, 再介绍了kvm虚拟化技术之后,我们介绍下openstack和如何搭建. Openstack组件 openstack架构图如下所示 那么我们就 ...

  6. Springboot @webfilter @order filter过滤器

    我们使用@WebFilter注解的时候发现注解里面没有提供可以控制执行顺序的参数 @WebFilter 的属性 属性名 类型 描述 filterName String 指定过滤器的 name 属性,等 ...

  7. Go并发编程(四)

        并发基础   多进程  多线程 基于回调的非阻塞/异步IO     协程  协程  与传统的系统级线程和进程相比,协程的最大优势在于其“轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭, ...

  8. js节流函数高级版

    节流函数其主要作用就是防止用户在短时间内多次触发该事件. <!DOCTYPE html> <html lang="en"> <head> < ...

  9. sublime + emmet(Zen Coding)

    今天接触sublime这个编辑器,一下子就喜欢上它了,以前我一直使用NOTEPAD++,果断换上sublime玩玩,呵呵 编辑功能啥的没话,作为前端开发的话,和emmet(原名叫Zen Coding) ...

  10. Nginx 过期时间

    客户端(浏览器)访问服务器上的资源后,会缓存在浏览器中,对于一些经常变更的静态资源,我们可以设置缓存时间,也就是设置静态资源的过期时间 [root@localhost ~]$ cat /usr/loc ...