Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)

C# - 비동기 소켓 사용 시 메모리 해제가 finalizer 단계에서 발생하는 사례

아래와 같은 질문이 있어서,

Socket 관련 Leak (OverlappedAsyncResult, OverlappedData) 관련 문의
; https://www.sysnet.pe.kr/3/1/5826

한번 메모리 누수가 발생할 것 같은 사례를 정리해 봤습니다. 우선, 서버를 다음과 같이 간단하게 만드는데,

using System.Net;
using System.Net.Sockets;

internal class Program
{
    static void Main(string[] args)
    {
        Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        sock.Bind(new IPEndPoint(IPAddress.Any, 65300));
        sock.Listen(5);

        while (true)
        {
            Socket client = sock.Accept();
            Task.Run(() =>
            {
                Thread.Sleep(16);
                client.Close();
            });
        }
    }
}

보는 바와 같이 하는 일 없이 그냥 16ms 이후에 연결을 바로 종료해버립니다. 그리고 클라이언트는 이렇게 만들어 볼 텐데요,

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

internal class Program
{
    static void Main(string[] args)
    {
        string ipAddress = "127.0.0.1";
        int port = 65300;

        while (true)
        {
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Connect(new IPEndPoint(IPAddress.Parse(ipAddress), port));

            byte[] buffer = new byte[4096];
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socket);

            Thread.Sleep(16);
        }
    }

    private static void ReceiveCallback(IAsyncResult ar)
    {
        Socket socket = (Socket)ar.AsyncState;

        socket.EndReceive(ar);
    }
}

역시 하는 일은, 접속한 다음 서버 측이 데이터를 보내주기를 기다리는 것이 전부입니다. 그리고 예상할 수 있듯이, 위와 같이 하면 당분간 메모리 leak과 같은 현상이 발생합니다. 비록 BeginReceive/EndReceive 호출 쌍은 맞췄지만, 서버가 연결을 끊으면서 EndReceive가 0을 반환한 후 클라이언트 측이 Socket.Close를 하지 않아 소켓 자원이 해제되지 않으면서 비동기를 위한 버퍼가 살아 있게 된 것입니다.

실제로 이때 해당 프로세스의 메모리 덤프를 떠서 windbg로 살펴보면

0:000> !dumpheap
...[생략]...
00007ffad3b74d78     1504        96256 System.AsyncCallback
00007ffa89ff98a0     1504        96256 System.Net.Sockets.Socket+CacheSet
00007ffa89ff9dc8      565       103960 System.Net.Sockets.OverlappedAsyncResult
00007ffa89ff7d60     2068       115808 System.Net.IPAddress
00007ffad3b369b8     1460       163520 System.Threading.OverlappedData
00007ffad3b5ea78     2069       182072 System.Threading.ExecutionContext
00007ffad3b606f8     3673       208168 System.String
00007ffa89ff3010     1504       216576 System.Net.Sockets.Socket
000001bdabfec320     4553      1293860      Free
00007ffad3b657a0     3013      6281664 System.Byte[]

보는 바와 같이 OverlappedAsyncResult, OverlappedData 등의 개체와 함께 System.Byte[]가 꽤나 많이 쌓인 것을 볼 수 있습니다. 그럼 System.Byte[]의 경우 일부 데이터를 조사해 볼까요?

0:000> !gcroot 0000024441e09aa8 
Finalizer Queue:
    0000024441e09870
    -> 0000024441e09870 System.Net.Sockets.Socket
    -> 0000024441e09a80 System.Net.SocketAddress
    -> 0000024441e09aa8 System.Byte[]

0:000> !gcroot 0000018551a214b0 
HandleTable:
    000001854fa11e00 (async pinned handle)
    -> 0000018551685d08 System.Threading.OverlappedData
    -> 0000018551a214b0 System.Byte[]

Socket 개체 또는 pinned handle에 의해 잡혀 있어 GC가 되지 못하고 있습니다. 이와 마찬가지로, OverlappedCache 인스턴스 역시 비슷한 양상을 보입니다.

0:000> !gcroot 0000018551927108
HandleTable:
    000001854fa12348 (async pinned handle)
    -> 0000018551505378 System.Threading.OverlappedData
    -> 0000018551926ff8 System.Net.Sockets.OverlappedAsyncResult
    -> 0000018551927108 System.Net.Sockets.OverlappedCache

하지만, 이것이 직접적으로 메모리 누수까지는 이어지지 않습니다. 왜냐하면 Socket은 finalizer를 구현하고 있기 때문에 결국 GC가 반복되면서 비동기 수행 후 닫지 않았던 소켓은 자원이 해제되므로 그와 연관된 비관리 자원까지 모두 해제되기 때문입니다.

실제로, 위의 프로그램을 실행해 놓고 보면 메모리가 일정 수준 증가하다가 주춤하면서 유지되는 경향을 보입니다. 제 경우에는 위의 프로그램에서 대략 180,632K 정도 증가한 후에는 그 수준에서 약간의 변동폭을 보이기만 했습니다.

그나저나... finalizer 때문에 ^^; 완벽한(?) 메모리 누수가 나도록 만들 수가 없습니다.

그래도 쓰지도 않는 소켓이 열려 있는 것은 좋지 않으므로 꼭 Close 시키는 것이 좋습니다. "Socket 관련 Leak (OverlappedAsyncResult, OverlappedData) 관련 문의" 글의 소스 코드를 보면 Socket.Close가 되지 않을 코드 수행 경로가 있기 때문에 저 현상이 발생할 수도 있습니다.




그 외, "Socket 관련 Leak (OverlappedAsyncResult, OverlappedData) 관련 문의" 질문에 첨부한 소스 코드를 보고 언급할 만한 것이 있다면 ReceiveTimeOut의 설정입니다.

사실 Begin/End... 비동기에서 ReceiveTimeOut은 아무런 역할도 하지 않습니다. 예전에도 이와 비슷한 질문을 하신 분이 있는데,

소켓 비동기 ReceiveTimeOut 구현 
; https://www.sysnet.pe.kr/3/0/5282

비동기의 경우 별도로 (Timer 등을 활용해) timeout 설정을 해야 합니다.

마지막으로, Socket.Connecetd 속성으로 IsClientConnected 여부를 판정하고 있는데, 이것도 올바른 방법이 아닙니다. 예전에 이와 관련해 쓴 글에서 소개했으니 더 이상의 설명은 생략합니다. ^^

TCP 소켓 연결의 해제를 알 수 있는 방법
; https://www.sysnet.pe.kr/2/0/1825




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 3/15/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  69  [70]  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12187정성태3/12/202015842오류 유형: 605. NtpClient was unable to set a manual peer to use as a time source because of duplicate error on '...'.
12186정성태3/12/202017590오류 유형: 604. The SysVol Permissions for one or more GPOs on this domain controller and not in sync with the permissions for the GPOs on the Baseline domain controller.
12185정성태3/11/202018192오류 유형: 603. The browser service was unable to retrieve a list of servers from the browser master...
12184정성태3/11/202020081오류 유형: 602. Automatic certificate enrollment for local system failed (0x800706ba) The RPC server is unavailable. [3]
12183정성태3/11/202017840오류 유형: 601. Warning: DsGetDcName returned information for \\[...], when we were trying to reach [...].
12182정성태3/11/202019337.NET Framework: 901. C# Windows Forms - Vista/7 이후의 Progress Bar 업데이트가 느린 문제파일 다운로드1
12181정성태3/11/202019638기타: 76. 재현 가능한 최소한의 예제 프로젝트란? - 두 번째 예제파일 다운로드1
12180정성태3/10/202016104오류 유형: 600. "Docker Desktop for Windows" - EXPOSE 포트가 LISTENING 되지 않는 문제
12179정성태3/10/202027829개발 환경 구성: 481. docker - PostgreSQL 컨테이너 실행
12178정성태3/10/202019919개발 환경 구성: 480. Linux 운영체제의 docker를 위한 tcp 바인딩 추가 [1]
12177정성태3/9/202019112개발 환경 구성: 479. docker - MySQL 컨테이너 실행
12176정성태3/9/202018659개발 환경 구성: 478. 파일의 (sha256 등의) 해시 값(checksum) 확인하는 방법
12175정성태3/8/202018707개발 환경 구성: 477. "Docker Desktop for Windows"의 "Linux Container" 모드를 위한 tcp 바인딩 추가
12174정성태3/7/202018066개발 환경 구성: 476. DockerDesktopVM의 파일 시스템 접근 [3]
12173정성태3/7/202019458개발 환경 구성: 475. docker - SQL Server 2019 컨테이너 실행 [1]
12172정성태3/7/202023942개발 환경 구성: 474. docker - container에서 root 권한 명령어 실행(sudo)
12171정성태3/6/202019020VS.NET IDE: 143. Visual Studio - ASP.NET Core Web Application의 "Enable Docker Support" 옵션으로 달라지는 점 [1]
12170정성태3/6/202017027오류 유형: 599. "Docker Desktop is switching..." 메시지와 DockerDesktopVM CPU 소비 현상
12169정성태3/5/202019580개발 환경 구성: 473. Windows nanoserver에 대한 docker pull의 태그 사용 [1]
12168정성태3/5/202020768개발 환경 구성: 472. 윈도우 환경에서의 dockerd.exe("Docker Engine" 서비스)가 Linux의 것과 다른 점
12167정성태3/5/202019204개발 환경 구성: 471. C# - 닷넷 응용 프로그램에서 DB2 Express-C 데이터베이스 사용 (3) - ibmcom/db2express-c 컨테이너 사용
12166정성태3/4/202019647개발 환경 구성: 470. Windows Server 컨테이너 - DockerMsftProvider 모듈을 이용한 docker 설치
12165정성태3/2/202018543.NET Framework: 900. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 네 번째 이야기(Monitor.Enter 후킹)파일 다운로드1
12164정성태2/29/202019653오류 유형: 598. Surface Pro 6 - Windows Hello Face Software Device가 인식이 안 되는 문제
12163정성태2/27/202017992.NET Framework: 899. 익명 함수를 가리키는 delegate 필드에 대한 직렬화 문제
12162정성태2/26/202021911디버깅 기술: 166. C#에서 만든 COM 객체를 C/C++로 P/Invoke Interop 시 메모리 누수(Memory Leak) 발생 [6]파일 다운로드2
... 61  62  63  64  65  66  67  68  69  [70]  71  72  73  74  75  ...