起因是发现一个同事编写的程序运行两个月左右,占用了服务器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. GridView分页的实现 ASP.NET c#(转)特好用

    要在GridView中加入 //实现分页 AllowPaging="true" //一页数据10行 PageSize="10" // 分页时触发的事件OnPag ...

  2. php把数组、字符串 生成文件

    生成的代码 data/ss.php <?php return array ( ', ', ); php代码 $str = "<?php\nreturn \n"; $my ...

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

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

  4. LR进行接口测试

    其实无论用那种测试方法,接口测试的原理是通过测试程序模拟客户端向服务器发送请求报文,服务器接收请求报文后对相应的报文做出处理然后再把应答报文发送给客户端,客户端接收应答报文这一个过程. 方法一.用Lo ...

  5. Java编程思想学习笔记——访问权限修饰词

    几种访问权限修饰词 public,protected,private,friendly(Java中并无该修饰词,即包访问权限,不提供任何访问修饰词) 使用时,放置在类中成员(域或方法)的定义之前的,仅 ...

  6. centos 中文乱码解决途径

    在使用CentOS系统时,安装的时候可能你会遇到英文的CentOS系统,在这中情况下安装CentOS系统时是默认安装(即英文).安装完毕后,出现的各种中文乱码.那么,我们如何解决这种问题呢. 一.Ce ...

  7. convertView&setTag方法的一点理解

    前言 首先我们要知道setTag方法是干什么的,SDK解释为 Tags Unlike IDs, tags are not used to identify views. Tags are essent ...

  8. grid布局合并单元格

    参考:http://www.w3cplus.com/css3/css-grid-layout-merge-cells.html <!DOCTYPE html> <html lang= ...

  9. C mysql (C API Commands out of sync; you can't run this command now)

    错误出现在当一个用户使用查询,另一个用户再使用此sql连接进行查询的时候: 原因是因为上一次使用此sql连接进行查询时没有将所有的结果集给释放掉,在所有使用此sql连接进行查询的地方将所有的结果集给释 ...

  10. 基础SELECT实例

    SELECT查询语句 ---进行单条记录.多条记录.单表.多表.子查询…… SELECT [ALL | DISTINCT | DISTINCTROW ] [HIGH_PRIORITY] [MAX_ST ...