起因是发现一个同事编写的程序运行两个月左右,占用了服务器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. 有术:DIY代理服务器

    FQ有术:DIY代理服务器 公司HTTP代理穿透+手機ShadowSocks+SSH翻牆 利用SSH代理爬墙 http://bestvpnchina.net/

  2. js scrollIntoViewIfNeeded

    根据 MDN的描述,Element.scrollIntoView()方法让当前的元素滚动到浏览器窗口的可视区域内. 而Element.scrollIntoViewIfNeeded()方法也是用来将不在 ...

  3. windows reload()

    reload() 方法用于重新加载当前文档.如果该方法没有规定参数,或者参数是 false,它就会用 HTTP 头 If-Modified-Since 来检测服务器上的文档是否已改变.如果文档已改变, ...

  4. .net网站建设页面提交后css失效的问题

    问题描述:.net网站建设在提交后出现css部分失效,如div位置,字体大小. 问题解决:原因是,过去的提示语句我们一律使用了Response.write("<script>al ...

  5. winform利用itextsharp.dll实现图片文件转换PDF格式文件

    1.利用itextsharp.dll实现单个图片文件转换为PDF格式文件, 可以使用以下类: void ConvertJPG2PDF(string jpgfile, string pdf) { var ...

  6. iOS模拟(糟糕的)网络环境

    有时候为了模拟在糟糕的网络环境下app的表现,会故意拔网线(断wifi),苹果其实提供了专门的工具来精确地模拟你在几个预设的场景下的网络连接情况:Network Link Conditioner 点击 ...

  7. 在阿里云创建子域名,配置nginx,使用pm2部署node项目到ubuntu服务器

    配置域名 在阿里云找到主域名 进入主域名之后,右上角添加解析,添加子域名, 记录类型选择cname,主机记录填写子域名的名称,记录值为主域名,至此阿里云已经配置好了. 检查nginx安装 首先检查服务 ...

  8. level 1 -- unit 2 - what 引导的特殊疑问句

    特殊疑问句 what 引导的特殊疑问句 常见结构如下: 1. what be sb/sth? 1.1 如 谓语动词是单数is what is your phone number ? what is y ...

  9. 对SQL语句进行过滤的函数

    /// <summary> /// 过滤SQL非法字符串 /// </summary> /// <param name="value">< ...

  10. linux环境中iostat命令的安装,解决-bash: iostat: command not found问题

    需求说明: 今天在测试环境的主机上,准备通过iostat来查看系统的io情况,发现没有该命令 [root@testvm Packages]# iostat -bash: iostat: command ...