Microsoft MVP성태의 닷넷 이야기
.NET Framework: 814. Critical Finalizer와 SafeHandle의 사용 의미 [링크 복사], [링크+제목 복사],
조회: 20219
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

Critical Finalizer와 SafeHandle의 사용 의미

다음의 글에 보면, 이에 대한 설명이 자세하게 나와 있습니다.

2005년 10월 MSDN Magazine
Keep Your Code Running with the Reliability Features of the .NET Framework
; https://docs.microsoft.com/en-us/archive/msdn-magazine/2005/october/using-the-reliability-features-of-the-net-framework

CHM
; http://download.microsoft.com/download/3/a/7/3a7fa450-1f33-41f7-9e6d-3aa95b5a6aea/MSDNMagazineOctober2005en-us.chm

우선 Critical Finalizer를 이해하기 위해서는 "application domain unload"의 "graceful" 방식과 "rude" 방식을 알아야 합니다. 그리고 그것에 대한 이해를 하기 위해서는 다시 "thread abort"에 대해 "graceful" 방식과 "rude" 방식을 알아야 합니다. 위의 문서에 따라 설명해 보면, ThreadAbortException 예외가 던져지면 스레드 종료(abort) 작업에 들어가게 됩니다. 하지만 해당 스레드는 thread abort 관련한 작업을 우선 처리해야 하기 때문에 그 작업으로 인해 실제적인 스레드 종료 작업이 안 될 수도 있습니다. 이런 경우를 위해 CLR은 보다 거친(rude) 방식으로 수행하는 스레드 종료 작업을 제공하는데 그걸 일컬어서 "rude thread abort"라고 합니다. 이 방식의 스레드 종료 작업은 일반적인 thread abort에 관련된 작업을 처리하지 않고 무조건 스레드 실행을 중지시킨다고 합니다. 따라서 CLR은 CER 내에서 코드가 실행되지 않는 한 해당 스레드에서 실행 중이던 back-out 코드에 대해 어떠한 안전성도 제공할 수 없는 것입니다.

그리고 "application domain unload" 방식에 있어 해당 AppDomain과 관련된 스레드를 "graceful"하게 종료한다면 그것이 "graceful application domain unload"이고, 반면 rude하게 스레드를 종료시킨다면 "rude application domain unload"가 됩니다. "graceful" application domain unload의 경우 (CER로 지정되지 않았어도) finally 블록과 AppDomain에 관여된 finalizer를 처리하는 반면 "rude" 방식에서는 그런 것들이 보장되지 않습니다.

물론, 일반적인 .NET 응용 프로그램에서 "rude" 방식의 thread abort나 application domain unload 작업이 수행되는 것은 아니고, CLR의 정책이 그렇게 설정된 경우에 한해 동작을 하는 것입니다. 그리고 이런 정책이 설정된 환경이 바로 SQL Server 2005에서부터 지원하는 CLR 호스팅으로, 여기서는 graceful하게 수행되는 thread abort 작업이 SQL 서버가 지정한 시간 내에 스레드가 종료되지 않으면 강제로 "rude"하게 thread abort를 처리합니다. 마찬가지로 application domain unload 작업도 graceful하게 진행할 시간이 지나면 강제로 "rude"하게 진행하게 됩니다.

.NET 2.0부터, CLR의 "graceful" thread abort는 다음의 경우에 대해 종료 작업을 지연시킵니다.

  • CER 영역 내의 코드 수행 중,
  • finally 블록 내의 코드 수행 중,
  • catch block 내의 코드 수행 중,
  • 정적 생성자 내의 코드 수행 중,
  • unmanaged 코드 수행 중

하지만, "rude" thread abort는 위의 경우에서 CER과 unmanaged 코드 수행 중을 제외하고는 강제로 스레드를 종료시킬 수 있습니다.




"rude" thread abort가 일반적인 finalizer에 대한 수행을 보장하지 않는 문제는 .NET 2.0에서 새롭게 도입된 "critical finalizer"로 해결이 됩니다. 이 유형의 finalizer는 CER 내에서 수행되고 따라서 "rude application domain unload"에서도 실행을 보장받게 됩니다.

critical finalizer를 구현하려면 해당 클래스를 CriticalFinalizerObject 타입으로부터 상속해야 합니다.

namespace System.Runtime.ConstrainedExecution
{
    [ComVisible(true)]
    [SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
    public abstract class CriticalFinalizerObject
    {
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        protected CriticalFinalizerObject()
        {
        }

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        ~CriticalFinalizerObject()
        {
        }
    }
}

일단 CriticalFinalizerObject로부터 상속받은 타입에서 finalizer를 동일하게 "CriticalFinalizerObject.~CriticalFinalizerObject" 메서드에 지정한 ReliabilityContract를 부여하면 그 메서드는 CER에서 실행됩니다.

CriticalFinalizerObject를 상속받는 대표적인 사례가 바로 SafeHandle 타입입니다. 달리 말하면 SafeHandle로 처리한 경우 rude thread abort 시에도 종료 작업을 보장받게 됩니다. SafeHandle로 인한 특별한 장점이 있다면 2가지 정도가 있습니다.

첫 번째는, .NET 1.x 시절에는 IntPtr을 이용한 네이티브 리소스를 관리하면서 Finalizer를 구현해야 했지만 SafeHandle을 사용함으로써 사용자 업무 클래스에서는 더 이상 Finalizer를 구현해야 할 이유가 사라졌습니다. 왜냐하면 어차피 사용자 클래스는 SafeHandle을 소유하고 있을 테고, SafeHandle은 CriticalFinalizerObject로부터 상속한 타입이므로 GC는 그와 관련된 네이티브 자원을 해제하기 위한 Finalizer를 호출할 것이기 때문입니다.

두 번째는, SafeHandle의 스레드 abort에 대한 안정성입니다. 가령, .NET 1.x 시절에 작성한 다음의 코드는,

private static void AllocIntPtr()
{
    IntPtr memPtr = Marshal.AllocHGlobal(0x100);
    try
    {
    }
    finally { Marshal.FreeHGlobal(memPtr); }
}

AllocHGlobal에서 메모리 할당을 한 후, memPtr에 대입하는 시점에 thread abort가 발생하면 리소스 누수 현상으로 이어집니다. 하지만, SafeHandle은 다음과 같이 사용하게 되므로,

[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public sealed class SafeLocalAllocArrayHandle :
    SafeHandleZeroOrMinusOneIsInvalid
{
    [DllImport("kernel32.dll")]
    private static extern SafeLocalAllocArrayHandle LocalAlloc(LMEM uFlags, IntPtr sizetdwBytes);

    int _size = 0;

    private SafeLocalAllocArrayHandle() : base(true)
    {
    }

    public static SafeLocalAllocArrayHandle Alloc(int size)
    {
        SafeLocalAllocArrayHandle handle = LocalAlloc(LMEM.ZEROINIT, new IntPtr(size));

        if (handle.IsInvalid == false)
        {
            handle._size = size;
        }

        return handle;
    }

    protected override bool ReleaseHandle()
    {
        return LocalFree(handle) == IntPtr.Zero;
    }

    [SuppressUnmanagedCodeSecurity]
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr LocalFree(IntPtr handle);

    public enum LMEM
    {
        LHND = 0x42,

        FIXED = 0,
        MOVEABLE = 0x2,
        ZEROINIT = 0x40,
    }
}

애초에 SafeHandle로 반환받게 되고 CLR은 그 과정에 thread abort가 개입하지 못하도록 합니다. 따라서 LocalAlloc이 메모리 할당에 성공했다면 반환받은 SafeHandle에는 언제나 값을 담고 있게 되는 것입니다.

(첨부 파일은 이 글의 예제 프로젝트를 포함합니다.)




정리해 보면, SafeHandle의 등장으로 인해 응용 프로그램 개발자는 더 이상 Finalizer를 사용자 클래스에 구현할 필요가 없어졌습니다. 자원 해제가 필요한 네이티브 리소스는 무조건 SafeHandle로 감싸서 처리하면 되기 때문입니다.





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

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/12/2021]

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

비밀번호

댓글 작성자
 




... 151  152  153  154  155  156  157  [158]  159  160  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1100정성태8/17/201128922.NET Framework: 236. SqlDbType - DateTime, DateTime2, DateTimeOffset의 차이점파일 다운로드1
1099정성태8/15/201128348오류 유형: 132. 어느 순간 갑자기 접속이 안 되는 TFS 서버
1098정성태8/15/201150357웹: 24. 네이버는 어떻게 로그인 처리를 할까요? [2]
1097정성태8/15/201121657.NET Framework: 235. 메서드의 메타 데이터 토큰 값으로 클래스를 찾아내는 방법
1096정성태8/15/201125797디버깅 기술: 42. Watson Bucket 정보를 이용한 CLR 응용 프로그램 예외 분석 - (2)
1095정성태8/14/201126241디버깅 기술: 41. Windbg - 비정상 종료된 닷넷 프로그램의 StackTrace에서 보이는 offset 값 의미
1094정성태8/14/201130614오류 유형: 131. Fiddler가 강제 종료된 경우, 웹 사이트 방문이 안되는 현상
1093정성태7/27/201124238오류 유형: 130. Unable to connect to the Microsoft Visual Studio Remote Debugging Monitor ... Access is denied.
1092정성태7/22/201126640Team Foundation Server: 46. 코드 이외의 파일에 대해 소스 제어에서 제외시키는 방법
1091정성태7/21/201125679개발 환경 구성: 128. WP7 Emulator 실행 시 audiodg.exe의 CPU 소모율 증가 [2]
1089정성태7/18/201131258.NET Framework: 234. 왜? Button 컨트롤에는 MouseDown/MouseUp 이벤트가 발생하지 않을까요?파일 다운로드1
1088정성태7/16/201124329.NET Framework: 233. Entity Framework 4.1 - 윈도우 폰 7에서의 CodeFirst 순환 참조 문제파일 다운로드1
1087정성태7/15/201127024.NET Framework: 232. Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 - 두 번째 이야기파일 다운로드1
1086정성태7/14/201128453.NET Framework: 231. Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 [1]파일 다운로드1
1085정성태7/14/201128894.NET Framework: 230. Entity Framework 4.1 - Code First + WCF 서비스 시 EndpointNotFoundException 오류 - 두 번째 이야기파일 다운로드1
1084정성태7/11/201134177.NET Framework: 229. SQL 서버 - DB 테이블의 데이터 변경에 대한 알림 처리 [4]파일 다운로드1
1083정성태7/11/201128222.NET Framework: 228. Entity Framework 4.1 - Code First + WCF 서비스 시 EndpointNotFoundException 오류
1082정성태7/10/201127799.NET Framework: 227. basicHttpBinding + 사용자 정의 인증 구현 [2]파일 다운로드1
1081정성태7/9/201127114VC++: 53. Windows 7에서 gcc.exe 실행 시 Access denied 오류 [2]
1080정성태7/8/201125609웹: 23. Sysnet 웹 사이트의 HTML5 변환 기록 - 두 번째 이야기파일 다운로드1
1079정성태7/6/201130027오류 유형: 129. Hyper-V + Realtek 랜카드가 설치된 시스템의 BSOD 현상 [2]
1078정성태7/5/201137560VC++: 52. Chromium 컴파일하는 방법 [2]
1077정성태6/24/201135185.NET Framework: 226. HttpWebRequest 타입의 HaveResponse 속성 이야기파일 다운로드1
1076정성태6/23/201129322오류 유형: 128. SQL Express - User Instance 옵션을 사용한 경우 발생하는 오류 메시지 유형 2가지
1075정성태6/21/201124944VS.NET IDE: 69. 윈폰 프로젝트에서 WCF 서비스 참조할 때 Reference.cs 파일이 비어있는 경우
1074정성태6/20/201125030.NET Framework: 225. 닷넷 네트워크 라이브러리의 트레이스 기능파일 다운로드1
... 151  152  153  154  155  156  157  [158]  159  160  161  162  163  164  165  ...