起因是发现一个同事编写的程序运行两个月左右,占用了服务器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. linux下redis的安装和集群搭建

    一.redis概述 1.1.目前redis支持的cluster特性: 1):节点自动发现. 2):slave->master 选举,集群容错. 3):Hot resharding:在线分片. 4 ...

  2. RabbitMQ学习笔记(一):安装及Springboot集成

    前言 MQ,即消息队列Message Queue的缩写. RabbitMQ 是MQ的一种,就像招商银行是银行的一种一样.主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用. 消息 ...

  3. Objc将数据写入iOS真机的plist文件里

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 假设认为写的不好请多提意见,假设认为不错请多多支持点赞.谢谢! hopy ;) 怎样写入模拟器的博文在 这里 可是这对真机无论用,由于在真机环 ...

  4. lkl风控.随机森林模型测试代码spark1.6

    /** * Created by lkl on 2017/10/9. */ import org.apache.spark.sql.hive.HiveContext import org.apache ...

  5. android中YUV转RGB的方法

    在一个外国网站上看到一段YUV转RGB的程序很不错,根据维基上的知识,方法应该是没问题的,自己也用过了,效果没问题. 首先说一下android上preview中每一帧的信息都是YUV420的,或者叫N ...

  6. 命令行编译C++程序

        使用命令行来编译C++程序,我们可以有两种方法:     方法一:     1. 依次打开开始程序->Visual Studio 2010 –>Visual Studio tool ...

  7. 禁止页面内按F5键进行刷新(扩展知识:禁止复制信息内容)

    禁止页面内按F5键进行刷新: //禁止页面内按F5键进行刷新 function f_DisableF5Refresh(event) { var e = event || window.event; v ...

  8. VS2015常用快捷键

    1.回到上一个光标位置/前进到下一个光标位置  1)回到上一个光标位置:使用组合键“Ctrl + -”: 2)前进到下一个光标位置:“Ctrl + Shift + - ”. 2.复制/剪切/删除整行代 ...

  9. spring aop的配置

    http://www.cnblogs.com/oumyye/p/4480196.html http://blog.csdn.net/hjm4702192/article/details/1727766 ...

  10. QT编译错误: multiple definition of `qMain(int, char**)'

    QT使用过程中来回添加修改代码,结果出现了编译错误:error: multiple definition of `qMain(int, char**)' 一直看我的源文件是都哪里有错误,最后发现是在p ...