起因是发现一个同事编写的程序运行两个月左右,占用了服务器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. Xcode : svn 无法上传静态库(.a)文件

    1.打开终端,输入cd,空格,然后将需要上传的.a文件所在的文件夹(不是.a文件)拖拽到终端(此办法无需输入繁琐的路径,快捷方便) ,回车:2.之后再输入如下命令:svn add xxx.a,回车:3 ...

  2. IE8下submit表单没反应

    当在IE8浏览器下,例如以下代码<input type="submit" value="sub" />点击没反应.通常是因为表单里面嵌套了表单造成的 ...

  3. C++ 中的constkeyword

    为什么使用const?採用符号常量写出的代码更easy维护:指针经常是边读边移动,而不是边写边移动:很多函数參数是仅仅读不写的.const最常见用途是作为数组的界和switch分情况标号(也能够用枚举 ...

  4. Android上基于libgdx的游戏开发资料

    本来之前想边学边写一个有关libgdx的游戏开发历程的,但是由于自己的懒惰和客观上的各种事情,一直没有搞下去.最近发现了一个大牛写的一本书<Beginning Android Games, 2n ...

  5. Redis Crackit漏洞利用和防护

    注意:本文只是阐述该漏洞的利用方式和如何预防.根据职业道德和<中华人民共和国计算机信息系统安全保护条例>,如果发现的别人的漏洞,千万不要轻易入侵,这个是明确的违法的哦!!! 目前Redis ...

  6. git恢复本地删除的文件夹取消增加的文件

    git项目中有时候会在本地增加或者删除了一些文件或者文件夹,但是又不想提交,一般情况下,我们取消本地所有修改: git checkout . 取消指定文件修改: git checkout filena ...

  7. Ubuntu Git安装与使用

    本系列文章由 @yhl_leo 出品.转载请注明出处. 文章链接: http://blog.csdn.net/yhl_leo/article/details/50760140 本文整理和归纳了关于Ub ...

  8. spring mvc处理方法返回方式

    Model: package org.springframework.ui; import java.util.Collection; import java.util.Map; public int ...

  9. ajax简单手写了一个猜拳游戏

    使用ajax简单写一个猜拳游戏 HTML代码 <!DOCTYPE HTML> <html lang="en-US"> <head> <me ...

  10. UITextField中文输入法输入时对字符长度的限制 输入时对字符类型的限制

    检索一个字符串的长度的话:直接用 length,去进行判断就行了, 如果要检索字符串是否是自己要限制的类型的话,可以用正则表达式: 举个例子:   匹配9-15个由字母/数字组成的字符串的正则表达式: ...