다음을 통해 공유


안정성 모범 사례

다음 안정성 규칙은 SQL Server를 지향합니다. 그러나 호스트 기반 서버 애플리케이션에도 적용됩니다. SQL Server와 같은 서버는 리소스를 누출하지 않고 중단하지 않는 것이 매우 중요합니다. 그러나 개체의 상태를 변경하는 모든 메서드에 대한 백아웃 코드를 작성하여 수행할 수는 없습니다. 목표는 백아웃 코드를 사용하여 모든 위치의 오류에서 복구할 100% 신뢰할 수 있는 관리 코드를 작성하는 것이 아닙니다. 그것은 성공할 기회가 거의 없는 어려운 일이 될 것입니다. CLR(공용 언어 런타임)은 완벽한 코드를 작성할 수 있도록 관리 코드에 대한 강력한 보장을 쉽게 제공할 수 없습니다. 참고로, ASP.NET과 달리, SQL Server는 데이터베이스를 오랫동안 중단시킬 수밖에 없는 상황 없이 재활용할 수 없는 단일 프로세스를 사용합니다.

이러한 약한 보장과 단일 프로세스에서 실행되는 안정성은 필요할 때 스레드를 종료하거나 애플리케이션 도메인을 재활용하고 핸들이나 메모리와 같은 운영 체제 리소스가 유출되지 않도록 예방 조치를 취하는 것을 기반으로 합니다. 이 간단한 안정성 제약 조건이 있더라도 여전히 중요한 안정성 요구 사항이 있습니다.

  • 운영 체제 리소스를 누출하지 마세요.

  • CLR에 대해 모든 형태의 관리되는 잠금을 식별합니다.

  • 애플리케이션 간 도메인 공유 상태를 중단하지 않도록 하여 재활용이 AppDomain 원활하게 작동할 수 있도록 합니다.

** 이론적으로는 ThreadAbortException, StackOverflowException, OutOfMemoryException 예외를 처리할 수 있는 관리 코드를 작성하는 것이 가능할 수 있지만, 개발자들이 전체 애플리케이션에서 이러한 강력한 코드를 써야 한다고 기대하는 것은 무리입니다. 이러한 이유로 대역 외 예외로 인해 실행 중인 스레드가 종료됩니다. 종료된 스레드가 공유 상태를 편집하고 있으면 스레드가 잠금을 보유하는지 여부에 따라 결정될 수 있습니다. 그러면 AppDomain 해당 스레드가 언로드됩니다. 공유 상태를 편집하는 메서드가 종료되면 공유 상태에 대한 업데이트에 대한 신뢰할 수 있는 백아웃 코드를 작성할 수 없으므로 상태가 손상됩니다.

.NET Framework 버전 2.0에서 안정성이 필요한 호스트는 SQL Server뿐입니다. 어셈블리가 SQL Server에서 실행되는 경우 데이터베이스에서 실행할 때 비활성화된 특정 기능이 있더라도 해당 어셈블리의 모든 부분에 대해 안정성 작업을 수행해야 합니다. 코드 분석 엔진이 어셈블리 수준에서 코드를 검사하고 비활성화된 코드를 구분할 수 없으므로 이 작업이 필요합니다. 또 다른 SQL Server 프로그래밍 고려 사항은 SQL Server가 모든 것을 한 프로세스에서 실행하고 메모리 및 운영 체제 핸들과 AppDomain 같은 모든 리소스를 정리하는 데 재활용이 사용된다는 것입니다.

최종 처리자, 소멸자 또는 try/finally 블록에 백아웃 코드를 의존할 수 없습니다. 중단되거나 호출되지 않을 수 있습니다.

예기치 않은 위치에서 비동기 예외가 발생할 수 있으며, 이러한 예외는 거의 모든 기계 명령 단계마다 (ThreadAbortException, StackOverflowException, OutOfMemoryException) 발생할 수 있습니다.

관리되는 스레드가 SQL의 Win32 스레드일 필요는 없습니다. 섬유일 수도 있습니다.

프로세스 전체 또는 애플리케이션 간 도메인 변경 가능한 공유 상태는 안전하게 변경하기가 매우 어렵고 가능하면 피해야 합니다.

SQL Server에서는 메모리 부족 조건이 드물지 않습니다.

SQL Server에서 호스트되는 라이브러리가 공유 상태를 올바르게 업데이트하지 않으면 데이터베이스가 다시 시작될 때까지 코드가 복구되지 않을 가능성이 높습니다. 또한 일부 극단적인 경우 SQL Server 프로세스가 실패하여 데이터베이스가 다시 부팅될 수 있습니다. 데이터베이스를 다시 부팅하면 웹 사이트가 중단되거나 회사 운영에 영향을 미쳐 가용성이 저하될 수 있습니다. 메모리 또는 핸들과 같은 운영 체제 리소스가 느리게 누출되면 서버가 결국 복구 가능성 없이 핸들을 할당하지 못하거나 서버의 성능이 저하되고 고객의 애플리케이션 가용성이 저하될 수 있습니다. 분명히 이러한 시나리오를 방지하려고 합니다.

모범 사례 규칙

소개는 프레임워크의 안정성과 신뢰성을 높이기 위해 서버에서 실행되는 관리 코드에 대한 코드 검토가 포착해야 하는 사항에 초점을 맞췄습니다. 이러한 모든 검사는 일반적으로 좋은 방법이며, 서버에서는 절대적으로 필요합니다.

교착 상태 또는 리소스 제약 조건에 직면하면 SQL Server는 스레드를 중단하거나 AppDomain를 해체합니다. 이 경우 CER(제한된 실행 지역)의 백아웃 코드만 실행되도록 보장됩니다.

SafeHandle을 사용하여 리소스 누수 방지

언로드의 경우, 블록 AppDomain 또는 종료자가 실행될 것이라고 기대할 수 없으므로, 모든 운영 체제 리소스 액세스를 finally 클래스를 통해 추상화해야 합니다. SafeHandle, IntPtr 또는 유사한 클래스를 사용하는 대신 finally 클래스를 사용하세요. 이를 통해 CLR은 사용하는 핸들을 추적하고 닫을 수 있는데, 이는 해체 사례에서도 가능합니다. SafeHandle는 CLR이 반드시 실행할 중요한 종료자를 사용하게 됩니다.

운영 체제 핸들은 생성되는 순간부터 릴리스되는 순간까지 안전 핸들에 저장됩니다. 핸들을 누수하기 위해 발생할 수 있는 ThreadAbortException 창이 없습니다. 또한 플랫폼 호출은 핸들을 참조 계산하여 핸들의 수명을 면밀히 추적합니다. 이는 핸들을 사용하는 현재 메서드와 Dispose 사이의 경합 상태에서 발생할 수 있는 보안 문제를 방지합니다.

운영 체제 핸들을 정리하기 위해 현재 종료자가 있는 대부분의 클래스는 더 이상 종료자가 필요하지 않습니다. 대신, 종료자가 SafeHandle 파생 클래스에 있을 것입니다.

SafeHandleIDisposable.Dispose의 대체물이 아닙니다. 운영 체제 리소스를 명시적으로 삭제할 수 있는 잠재적인 리소스 경합 및 성능 이점이 여전히 있습니다. 리소스를 finally 명시적으로 해제하는 블록이 완전히 실행되지 않을 수 있음을 유념하세요.

SafeHandle에서는 운영 체제의 핸들 해제 루틴에 상태를 전달하거나 루프에서 핸들 집합을 해제하는 등의 작업을 수행하는 고유한 ReleaseHandle 메서드를 구현할 수 있습니다. CLR은 이 메서드가 실행되도록 보장합니다. 모든 상황에서 핸들이 해제되도록 하는 것은 구현 작성자의 ReleaseHandle 책임입니다. 이렇게 하지 않으면 핸들이 유출되어 핸들과 연결된 네이티브 리소스가 누출되는 경우가 많습니다. 따라서 SafeHandle 파생 클래스를 설계할 때, ReleaseHandle 구현이 호출 시점에 사용할 수 없는 리소스를 할당할 필요가 없도록 구조화하는 것이 중요합니다. 코드에서 이러한 오류를 처리하고 네이티브 핸들을 해제하는 계약을 완료할 수 있는 경우 구현 ReleaseHandle 내에서 실패할 수 있는 메서드를 호출할 수 있습니다. 디버깅을 목적으로, 자원 해제를 방해하는 치명적인 오류가 발생할 경우, ReleaseHandleBoolean 반환값을 false로 설정할 수 있습니다. 이렇게 하면 releaseHandleFailed MDA(활성화된 경우)가 활성화되어 문제를 식별하는 데 도움이 됩니다. 다른 방법으로는 런타임에 영향을 주지 않습니다. ReleaseHandle 는 동일한 리소스에 대해 다시 호출되지 않으므로 핸들이 유출됩니다.

SafeHandle 는 특정 컨텍스트에서 적절하지 않습니다. 메서드는 종료자 스레드인 ReleaseHandle에서 실행될 수 있으므로, 특정 스레드에서 해제해야 하는 핸들은 GC으로 래핑되지 않아야 합니다.

추가 코드 없이 CLR에서 RCW(런타임 호출 가능 래퍼)를 정리할 수 있습니다. 플랫폼 호출을 사용하고 COM 개체를 IUnknown* 또는 IntPtr로 처리하는 코드의 경우, RCW를 사용하도록 코드를 다시 작성해야 합니다. SafeHandle 관리되지 않는 릴리스 메서드가 관리 코드로 다시 호출될 가능성이 있으므로 이 시나리오에 적합하지 않을 수 있습니다.

코드 분석 규칙

운영 체제 리소스를 캡슐화하는 데 사용합니다 SafeHandle . HandleRefIntPtr 형식의 필드를 사용하지 마세요.

종료자가 실행될 필요 없이 운영 체제 리소스 누출을 방지하는지 확인합니다.

종료자를 주의 깊게 검토하여 종료자가 실행되지 않더라도 중요한 운영 체제 리소스가 유출되지 않도록 합니다. 애플리케이션이 안정적인 상태로 실행되거나 SQL Server와 같은 서버가 종료될 때 일반 AppDomain 언로드와 달리 갑작스러운 AppDomain 언로드 중에는 개체가 종료되지 않습니다. 애플리케이션의 정확성을 보장할 수는 없지만 리소스를 누출하지 않음으로써 서버의 무결성을 유지해야 하므로 갑작스러운 언로드 시 리소스가 유출되지 않도록 합니다. 운영 체제 리소스를 해제하려면 SafeHandle를 사용하세요.

운영 체제 리소스 유출을 방지하기 위해 finally 절을 실행할 필요가 없는지 확인합니다.

finally 절은 CER 외부에서 실행되도록 보장되지 않으므로 라이브러리 개발자는 관리되지 않는 리소스를 해제하기 위해 블록 내 finally 의 코드에 의존하지 않아도 됩니다. 사용하는 SafeHandle 것이 권장되는 솔루션입니다.

코드 분석 규칙

운영 체제 리소스를 정리하는 데 SafeHandle을 사용하고 Finalize은 사용하지 마십시오. IntPtr을 사용하지 마세요. SafeHandle을 사용하여 리소스를 캡슐화하세요. finally 절을 실행해야 하는 경우 CER에 배치합니다.

모든 잠금은 기존 관리되는 잠금 코드를 통과해야 합니다.

CLR은 코드가 잠금 상태일 때 스레드를 중단하는 대신, AppDomain를 철거해야 한다는 것을 알고 있어야 합니다. 스레드에서 작동하는 데이터가 일관되지 않은 상태로 남을 수 있으므로 스레드를 중단하는 것은 위험할 수 있습니다. 따라서 전체 AppDomain 는 재활용되어야 합니다. 잠금을 식별하지 못하면 교착 상태 또는 잘못된 결과가 발생할 수 있습니다. 메서드 BeginCriticalRegionEndCriticalRegion 를 사용하고 잠금 영역을 식별합니다. 현재 스레드에만 적용되는 클래스의 Thread 정적 메서드이므로 한 스레드가 다른 스레드의 잠금 횟수를 편집하지 못하도록 방지할 수 있습니다.

EnterExit에는 이 CLR 알림 기능이 내장되어 있으므로, 이러한 메서드를 사용하는 lock 문뿐만 아니라 이들의 사용도 권장됩니다.

스핀 잠금과 같은 다른 잠금 메커니즘은 AutoResetEvent 이러한 메서드를 호출하여 CLR에 중요한 섹션이 입력되고 있음을 알려야 합니다. 이러한 메서드는 잠금을 사용하지 않습니다. 중요한 섹션에서 코드가 실행되고 있으며 스레드를 중단하면 공유 상태가 일관되지 않게 될 수 있음을 CLR에 알릴 수 있습니다. 사용자 지정 ReaderWriterLock 클래스와 같은 사용자 고유의 잠금 유형을 정의한 경우 이러한 잠금 개수 메서드를 사용합니다.

코드 분석 규칙

BeginCriticalRegionEndCriticalRegion를 사용하여 모든 잠금을 표시하고 식별합니다. CompareExchange, Increment, Decrement를 루프에서 사용하지 마세요. 이러한 메서드의 Win32 변형에 대한 플랫폼 호출을 수행하지 마세요. 루프에서 Sleep를 사용하지 마세요. 휘발성 필드를 사용하지 마세요.

정리 코드는 catch를 따르지 않고 최종 또는 catch 블록에 있어야 합니다.

정리 코드는 catch 블록을 따르지 않아야 합니다. 블록 자체에 finally 있거나 블록 자체에 catch 있어야 합니다. 이것은 정상적인 모범 사례여야 합니다. 일반적으로 finally 블록은 예외가 발생했을 때와 try 블록의 끝에 도달할 때 동일한 코드를 실행하기 때문에 선호됩니다. 예기치 않은 예외가 발생하는 경우, 예를 들어 ThreadAbortException가 발생할 때, 정리 코드는 실행되지 않습니다. 관리되지 않는 리소스는 finally 정리해야 할 것이며, 누출을 방지하기 위해 이상적으로 SafeHandle로 래핑되어야 합니다. C# using 키워드를 효과적으로 사용하여 핸들을 포함한 개체를 삭제할 수 있습니다.

재활용은 종료자 스레드에서 리소스를 정리할 수 있지만 AppDomain 정리 코드를 올바른 위치에 배치하는 것이 여전히 중요합니다. 스레드가 잠금을 유지하지 않은 상태에서 비동기 예외를 받으면, CLR은 AppDomain을 재활용하지 않고 스레드를 자체적으로 종료하려고 시도합니다. 리소스를 나중에 정리하는 것이 아니라 더 많은 리소스를 사용할 수 있도록 하고 수명을 더 잘 관리하여 리소스를 더 빨리 정리하는 데 도움이 됩니다. 일부 오류 코드 경로에서 파일에 대한 핸들을 명시적으로 닫지 않고 종료자가 정리되기를 기다리는 경우, SafeHandle 종료자가 아직 실행되지 않았다면 다음 코드 실행 시 정확히 동일한 파일에 접근하려다 실패할 수 있습니다. 이러한 이유로 정리 코드가 존재하고 올바르게 작동하는지 확인하면 꼭 필요한 것은 아니지만 오류를 보다 깨끗하고 신속하게 복구하는 데 도움이 됩니다.

코드 분석 규칙

catch 코드 정리는 finally 블록 내에 있어야 합니다. 마지막 블록에서 삭제할 호출을 배치합니다. catch 블록은 throw 또는 다시 throw로 끝나야 합니다. 많은 수의 예외를 가져올 수 있는 네트워크 연결을 설정할 수 있는지 여부를 검색하는 코드와 같은 예외가 있지만 정상적인 상황에서 여러 예외를 catch해야 하는 코드는 코드가 성공 여부를 확인하기 위해 테스트되어야 한다는 표시를 제공해야 합니다.

Process-Wide 애플리케이션 도메인 간의 변경 가능한 공유 상태를 제거하거나 제한된 실행 영역을 사용해야 합니다.

소개에 설명된 대로 신뢰할 수 있는 방식으로 애플리케이션 도메인에서 프로세스 전체 공유 상태를 모니터링하는 관리 코드를 작성하는 것은 매우 어려울 수 있습니다. 프로세스 전체 공유 상태는 Win32 코드, CLR 내부 또는 원격을 사용하는 관리 코드에서 애플리케이션 도메인 간에 공유되는 모든 종류의 데이터 구조입니다. 변경 가능한 공유 상태는 관리 코드에서 올바르게 작성하기가 매우 어렵고 정적 공유 상태는 주의해서만 수행될 수 있습니다. 프로세스 전체 또는 컴퓨터 전체 공유 상태가 있는 경우 CER(제한된 실행 영역)을 사용하여 공유 상태를 제거하거나 보호하는 방법을 찾습니다. 식별되지 않고 수정되지 않은 공유 상태를 가진 라이브러리는 SQL Server와 같은 호스트에서 필요한 깨끗한 AppDomain 언로드를 제대로 실행하지 못할 경우 크래시를 유발할 수 있습니다.

코드에서 COM 개체를 사용하는 경우 해당 COM 개체를 애플리케이션 도메인 간에 공유하지 마세요.

잠금은 프로세스 전체 또는 애플리케이션 도메인 간에 작동하지 않습니다.

과거에 Enterlock 문이 함께 사용되어 글로벌 프로세스 잠금을 만들었습니다. 예를 들어, 원격을 사용하여 애플리케이션 도메인 간에 공유되는 AppDomain Agile 클래스에 잠금을 설정할 때와 같은 경우에는 비공유 어셈블리의 인스턴스, Type 개체, 인턴 문자열 및 일부 문자열이 포함됩니다. 이러한 잠금은 더 이상 프로세스 전체에서 수행되지 않습니다. 프로세스 차원의 상호 적용 도메인 잠금의 존재를 식별하려면 잠금 내의 코드가 디스크의 파일 또는 데이터베이스와 같은 외부의 지속형 리소스를 사용하는지 확인합니다.

보호된 코드에서 외부 리소스를 AppDomain 사용하는 경우 해당 코드가 여러 애플리케이션 도메인에서 동시에 실행될 수 있으므로 잠금을 설정하면 문제가 발생할 수 있습니다. 이 문제는 하나의 로그 파일에 쓰거나 전체 프로세스에 대한 소켓에 바인딩할 때 문제가 될 수 있습니다. 이러한 변경은 관리 코드를 사용하여 명명된 Mutex 또는 Semaphore 인스턴스를 사용하는 것 외에는 프로세스 전역 잠금을 가져오는 쉬운 방법이 없다는 것을 의미합니다. 두 애플리케이션 도메인에서 동시에 실행되지 않는 코드를 만들거나 Mutex 또는 Semaphore 클래스를 사용합니다. 기존 코드를 변경할 수 없는 경우 파이버 모드에서 실행하면 동일한 운영 체제 스레드가 뮤텍스를 획득하고 해제할 수 없으므로 Win32 명명된 뮤텍스를 사용하여 이 동기화를 수행하지 마세요. 관리되는 Mutex 클래스, 명명된 ManualResetEvent 클래스, AutoResetEvent 클래스 또는 Semaphore 중 하나를 사용하여 CLR이 인식할 수 있는 방식으로 코드 잠금을 동기화해야 하며, 관리되지 않는 코드를 사용하여 잠금을 동기화하지 않아야 합니다.

lock(typeof(MyType)) 사용을 피하십시오

모든 애플리케이션 도메인에서 공유된 코드 복사본이 하나만 있는 공유 어셈블리의 프라이빗 및 공용 Type 개체도 문제를 발생합니다. 공유 어셈블리의 Type 경우 프로세스당 하나의 인스턴스만 있습니다. 즉, 여러 애플리케이션 도메인이 정확히 동일한 Type 인스턴스를 공유합니다. 인스턴스에 잠금을 Type 걸면, 이는 전체 프로세스에 영향을 미치는 잠금을 AppDomain 걸게 됩니다. AppDomain 객체의 잠금을 Type 걸면 해당 스레드가 갑자기 중단되더라도 잠금은 해제되지 않습니다. 그러면 이 잠금으로 인해 다른 애플리케이션 도메인이 교착 상태에 빠질 수 있습니다.

정적 메서드에서 잠금을 수행하는 좋은 방법은 코드에 정적 내부 동기화 개체를 추가하는 것입니다. 클래스 생성자가 있는 경우 클래스 생성자에서 초기화할 수 있지만 그렇지 않은 경우 다음과 같이 초기화할 수 있습니다.

private static Object s_InternalSyncObject;
private static Object InternalSyncObject
{
    get
    {
        if (s_InternalSyncObject == null)
        {
            Object o = new Object();
            Interlocked.CompareExchange(
                ref s_InternalSyncObject, o, null);
        }
        return s_InternalSyncObject;
    }
}

그런 다음 잠금을 걸 때 InternalSyncObject 속성을 사용하여 잠글 개체를 가져옵니다. 클래스 생성자에서 내부 동기화 개체를 초기화한 경우에는 이 속성을 사용할 필요가 없습니다. 이중 검사 잠금 초기화 코드는 다음 예제와 같습니다.

public static MyClass SingletonProperty
{
    get
    {
        if (s_SingletonProperty == null)
        {
            lock(InternalSyncObject)
            {
                // Do not use lock(typeof(MyClass))
                if (s_SingletonProperty == null)
                {
                    MyClass tmp = new MyClass(…);
                    // Do all initialization before publishing
                    s_SingletonProperty = tmp;
                }
            }
        }
        return s_SingletonProperty;
    }
}

lock(this)에 대한 참고 사항

일반적으로 공개적으로 액세스할 수 있는 개별 개체를 잠그는 것이 좋습니다. 그러나 개체가 전체 하위 시스템이 교착 상태에 빠질 수 있는 싱글톤 개체인 경우 위의 디자인 패턴도 사용하는 것이 좋습니다. 예를 들어 하나의 SecurityManager 개체에 대한 잠금은 AppDomain 내에서 교착 상태를 발생시켜 전체 AppDomain를 사용할 수 없게 만들 수 있습니다. 이 형식의 공개적으로 액세스할 수 있는 개체를 잠그지 않는 것이 좋습니다. 그러나 개별 컬렉션 또는 배열에 대한 잠금은 일반적으로 문제가 되지 않아야 합니다.

코드 분석 규칙

애플리케이션 도메인 간에 사용될 수 있는 형식 또는 강력한 ID 감각이 없는 형식에는 잠금을 설정하지 마세요. Enter, Type, MethodInfo, PropertyInfo, String, ValueType, Thread, 또는 MarshalByRefObject에서 파생된 어떤 객체도 호출하지 마십시오.

GC.KeepAlive 호출을 제거하세요.

많은 기존 코드가 사용해야 할 때 KeepAlive를 사용하지 않거나, 사용하면 안 되는 경우에 사용합니다. 변환된 SafeHandle 후에는 클래스에 종료자가 없고 운영 체제 핸들을 마무리하는 데 KeepAlive에 의존한다고 가정하여 SafeHandle을 호출할 필요가 없습니다. 호출 KeepAlive 을 유지하는 성능 비용은 무시할 수 있지만 호출이 더 이상 존재하지 않을 수 있는 KeepAlive 수명 문제를 해결하기 위해 필요하거나 충분하다는 인식으로 인해 코드를 유지 관리하기가 더 어려워집니다. 그러나 COM interop CLR RCW(호출 가능 래퍼) KeepAlive 를 사용하는 경우 코드에서 여전히 필요합니다.

코드 분석 규칙

KeepAlive제거합니다.

HostProtection 특성 사용

HPA는 호스트 보호 요구 사항을 결정하기 위해 선언적 보안 작업을 사용하는데, 이를 통해 호스트가 완전히 신뢰할 수 있는 코드조차도 SQL Server와 같은 지정된 호스트에 부적합한 특정 메서드(HostProtectionAttribute 또는 Exit)를 호출하지 못하도록 할 수 있습니다.

HPA는 공용 언어 런타임을 호스트하고 SQL Server와 같은 호스트 보호를 구현하는 관리되지 않는 애플리케이션에만 영향을 줍니다. 적용하면 보안 작업을 수행하면 클래스 또는 메서드가 노출하는 호스트 리소스에 따라 링크 요청이 생성됩니다. 코드가 클라이언트 애플리케이션 또는 호스트 보호되지 않은 서버에서 실행되는 경우 특성은 "증발"합니다. 검색되지 않으므로 적용되지 않습니다.

중요합니다

이 특성의 목적은 보안 동작이 아닌 호스트별 프로그래밍 모델 지침을 적용하는 것입니다. 프로그래밍 모델 요구 사항 준수를 확인하는 데 링크 요구 사항이 HostProtectionAttribute 사용되지만 보안 권한은 아닙니다.

호스트에 프로그래밍 모델 요구 사항이 없는 경우 링크 요구 사항이 발생하지 않습니다.

이 특성은 다음을 식별합니다.

  • 호스트 프로그래밍 모델에 맞지 않지만 그렇지 않은 경우 무해한 메서드 또는 클래스입니다.

  • 호스트 프로그래밍 모델에 맞지 않고 서버 관리 사용자 코드를 불안정하게 만들 수 있는 메서드 또는 클래스입니다.

  • 호스트 프로그래밍 모델에 맞지 않고 서버 프로세스 자체가 불안정해질 수 있는 메서드 또는 클래스입니다.

비고

호스트 보호 환경에서 실행할 수 있는 애플리케이션에서 호출할 클래스 라이브러리를 만드는 경우 리소스 범주를 노출 HostProtectionResource 하는 멤버에 이 특성을 적용해야 합니다. 이 특성이 있는 .NET Framework 클래스 라이브러리 멤버는 즉시 호출자만 확인합니다. 귀하의 라이브러리 멤버 또한 동일한 방식으로 즉각적인 호출자를 점검해야 합니다.

에서 HPA HostProtectionAttribute에 대한 자세한 내용을 확인하세요.

코드 분석 규칙

SQL Server의 경우 동기화 또는 스레딩을 도입하는 데 사용되는 모든 메서드를 HPA로 식별해야 합니다. 여기에는 상태를 공유하거나, 동기화되거나, 외부 프로세스를 관리하는 메서드가 포함됩니다. SQL Server에 영향을 주는 값은 HostProtectionResourceSharedState, SynchronizationExternalProcessMgmt. 그러나 HostProtectionResource를 노출하는 모든 메서드는, SQL에 영향을 주는 리소스를 사용하는 경우뿐만 아니라, HPA로 식별되어야 합니다.

비관리 코드에서 무기한 차단 안 함

CLR이 스레드를 중단할 수 없기 때문에 관리 코드 대신 관리되지 않는 코드에서 차단하면 서비스 거부 공격이 발생할 수 있습니다. 차단된 스레드는 적어도 매우 안전하지 않은 작업을 수행하지 않고 CLR이 언로드 AppDomain하는 것을 방지합니다. Windows 동기화 기본 형식을 사용하는 차단은 허용할 수 없는 명확한 예입니다. 가능한 경우 소켓에서 ReadFile 호출 시 차단되는 것을 피해야 합니다. 이상적으로는 Windows API가 이와 같은 작업의 시간 초과를 처리할 메커니즘을 제공해야 합니다.

네이티브로 호출하는 모든 메서드는 합리적인 유한 시간 제한으로 Win32 호출을 사용하는 것이 이상적입니다. 사용자가 시간 제한을 지정할 수 있는 경우 사용자는 특정 보안 권한 없이 무한 시간 제한을 지정할 수 없습니다. 지침에 따라 메서드가 최대 10초 이상 차단되는 경우 시간 제한을 지원하는 버전을 사용해야 하거나 추가 CLR 지원이 필요합니다.

다음은 문제가 있는 API의 몇 가지 예입니다. 파이프는 익명 및 명명된 모두 시간 제한과 함께 생성할 수 있습니다. 하지만 코드는 CreateNamedPipe 또는 WaitNamedPipe을 NMPWAIT_WAIT_FOREVER과 함께 호출하지 않도록 해야 합니다. 또한 시간 제한이 지정된 경우에도 예기치 않은 차단이 발생할 수 있습니다. 익명 파이프에 대한 호출 WriteFile 은 모든 바이트가 기록될 때까지 차단됩니다. 즉, 버퍼에 읽지 않은 데이터가 있는 WriteFile 경우 판독기에서 파이프 버퍼의 공간을 확보할 때까지 호출이 차단됩니다. 소켓은 항상 시간 제한 메커니즘을 적용하는 일부 API를 사용해야 합니다.

코드 분석 규칙

관리되지 않는 코드에서 시간 제한 없이 차단은 서비스 거부 공격입니다. 플랫폼 호출을 WaitForSingleObject, WaitForSingleObjectEx, WaitForMultipleObjects, MsgWaitForMultipleObjects, 및 MsgWaitForMultipleObjectsEx에 대해 수행하지 마세요. NMPWAIT_WAIT_FOREVER 사용하지 마세요.

STA-Dependent 기능을 식별하세요

COM STA(단일 스레드 아파트)를 사용하는 코드를 식별합니다. SQL Server 프로세스에서 STA를 사용할 수 없습니다. 성능 카운터 또는 클립보드와 같이 의존하는 CoInitialize기능은 SQL Server 내에서 사용하지 않도록 설정해야 합니다.

최종 처리기를 동기화 문제에서 자유롭게 유지하십시오.

이후 버전의 .NET Framework에는 여러 종료자 스레드가 있을 수 있습니다. 즉, 동일한 형식의 여러 인스턴스에 대한 종료자가 동시에 실행됩니다. 완전히 스레드 안전할 필요는 없습니다. 가비지 컬렉터는 특정 개체 인스턴스에 대해 한 스레드만 종료자를 실행하도록 보장합니다. 그러나 여러 다른 개체 인스턴스에서 동시에 실행될 때 경합 상태 및 교착 상태를 방지하기 위해 종료자를 코딩해야 합니다. 종료자에서 로그 파일에 쓰는 것과 같은 외부 상태를 사용하는 경우 스레딩 문제를 처리해야 합니다. 스레드 보안을 제공하기 위해 종료에 의존하지 마세요. 종료자 스레드에 상태를 저장하기 위해 스레드 로컬 스토리지(관리형 또는 네이티브)를 사용하지 마세요.

코드 분석 규칙

종료자는 동기화와 관련된 문제가 없어야 합니다. 종료자에서 정적 변경 가능 상태를 사용하지 마세요.

가능하면 관리되지 않는 메모리 방지

운영 체제 핸들처럼 관리되지 않는 메모리가 누출될 수 있습니다. 가능하면 stackalloc 또는 고정된 관리 개체(예: 고정 문 또는 GCHandle byte[]를 사용하여 스택에서 메모리를 사용하세요.) 결국 GC이(가) 이것을 정리합니다. 그러나 관리되지 않는 메모리를 할당해야 하는 경우 메모리 할당을 래핑하기 위해 파생되는 SafeHandle 클래스를 사용하는 것이 좋습니다.

SafeHandle이(가) 적절하지 않은 경우가 최소한 한 가지 있습니다. 메모리를 할당하거나 해제하는 COM 메서드 호출의 경우 한 DLL이 메모리 CoTaskMemAlloc 를 할당하는 것이 일반적이며, 다른 DLL은 해당 메모리를 해제 CoTaskMemFree합니다. SafeHandle을 이러한 위치에서 사용하는 것은 부적절합니다. 이는 다른 DLL이 메모리의 수명을 제어하도록 하는 대신, 관리되지 않는 메모리의 수명을 SafeHandle의 수명에 연결하려고 시도하기 때문입니다.

catch의 모든 사용 검토(예외)

하나의 특정 예외 대신 모든 예외를 catch하는 Catch 블록은 이제 비동기 예외도 catch합니다. 모든 catch(예외) 블록을 검사하여 중요 리소스가 해제되지 않거나 복구할 코드가 누락될 수 있는지 확인하고, 블록 내에서 ThreadAbortException, StackOverflowException, 또는 OutOfMemoryException를 처리하는 데 있어 잠재적으로 잘못된 동작이 없는지 살펴보세요. 이 코드는 예외가 발생할 때마다 정확히 하나의 특정 이유로 실패했다고 기록하거나 일부 가정을 할 수 있으며, 특정 예외만 표시될 가능성이 있습니다. 이러한 가정을 포함 ThreadAbortException하도록 업데이트해야 할 수 있습니다.

모든 예외를 catch하는 대신, 특정 유형의 예외를 catch하도록 변경하는 것이 좋습니다. 예를 들어, 문자열 서식 지정 메서드에서 발생할 수 있는 FormatException와 같은 예외를 catch하도록 설정할 수 있습니다. 이렇게 하면 catch 블록이 예기치 않은 예외에서 실행되지 않으며 코드가 예기치 않은 예외를 catch하여 버그를 숨기지 않도록 합니다. 일반적으로 라이브러리 코드에서 예외를 처리하지 않습니다(예외를 catch해야 하는 코드는 호출하는 코드의 디자인 결함을 나타낼 수 있음). 경우에 따라 예외를 catch하여 다른 예외 형식을 throw함으로써 더 많은 데이터를 제공할 수 있습니다. 이 경우 중첩된 예외를 사용하여 실패의 실제 원인을 새 예외의 속성에 InnerException 저장합니다.

코드 분석 규칙

모든 개체와 예외를 포착하는 관리 코드의 모든 catch 블록을 검토하십시오. C#에서 이는 둘 다 catch{} 플래그를 지정하는 것을 의미합니다 catch(Exception){}. 예외 형식을 매우 구체적으로 지정하거나 예기치 않은 예외 형식을 catch하는 경우 잘못된 방식으로 동작하지 않도록 코드를 검토하세요.

관리되는 스레드가 Win32 스레드라고 가정하지 마세요. 파이버입니다.

관리되는 스레드 로컬 스토리지를 사용하는 것은 작동하지만 관리되지 않는 스레드 로컬 스토리지를 사용하지 않거나 코드가 현재 운영 체제 스레드에서 다시 실행된다고 가정할 수 있습니다. 스레드의 로캘과 같은 설정을 변경하지 마세요. InitializeCriticalSection 또는 CreateMutex를 플랫폼 호출로 호출하지 마세요. 왜냐하면 잠금에 들어가는 운영 체제 스레드는 잠금을 종료해야 하기 때문입니다. 파이버를 사용할 때는 그렇지 않으므로 Win32 중요 섹션 및 뮤텍스는 SQL에서 직접 사용할 수 없습니다. Mutex 클래스는 관리되지 않는 스레드 결속 문제를 처리하지 않습니다.

관리되는 스레드 로컬 스토리지 및 스레드의 현재 UI 문화권을 포함하여, 관리되는 Thread 개체에서 대부분의 상태를 안전하게 사용할 수 있습니다. 현재 관리되는 스레드에서만 액세스할 수 있는 기존 정적 변수의 값을 만드는 를 사용할 ThreadStaticAttribute수도 있습니다(CLR에서 파이버 로컬 스토리지를 수행하는 또 다른 방법임). 프로그래밍 모델 때문에 SQL에서 실행할 때 스레드의 현재 문화권을 변경할 수 없습니다.

코드 분석 규칙

SQL Server는 파이버 모드에서 실행됩니다. 는 스레드 로컬 스토리지를 사용하지 않습니다. 플랫폼 호출을 피하십시오TlsAlloc, TlsFree, TlsGetValue, 및 TlsSetValue. 호출을 피하십시오

SQL Server가 사용자 대행을 처리하도록 허용

가장하기는 스레드 수준에서 작동하고 SQL은 파이버 모드에서 실행할 수 있으므로, 관리 코드는 사용자를 가장해서는 안 되며 RevertToSelf를 호출해서는 안 됩니다.

코드 분석 규칙

SQL Server에서 대리 실행을 처리하도록 합니다. RevertToSelf, ImpersonateAnonymousToken, DdeImpersonateClient, ImpersonateDdeClientWindow, ImpersonateLoggedOnUser, ImpersonateNamedPipeClient, ImpersonateSelf, RpcImpersonateClient, RpcRevertToSelf, RpcRevertToSelfEx, 또는 SetThreadToken를 사용하지 마세요.

Thread::Suspend를 호출하지 마세요.

스레드를 일시 중단하는 기능은 간단한 작업으로 나타날 수 있지만 교착 상태가 발생할 수 있습니다. 잠금을 보유한 스레드가 두 번째 스레드에 의해 일시 중단된 다음 두 번째 스레드가 동일한 잠금을 시도하면 교착 상태가 발생합니다. Suspend 는 현재 보안, 클래스 로드, 원격 및 리플렉션을 방해할 수 있습니다.

코드 분석 규칙

Suspend를 호출하지 마세요. 대신 a 또는 Semaphore .와 같은 실제 동기화 기본 형식을 ManualResetEvent 사용하는 것이 좋습니다.

제한된 실행 지역 및 안정성 계약을 사용하여 중요한 작업 보호

공유 상태를 업데이트하거나 완전히 성공하거나 완전히 실패해야 하는 복잡한 작업을 수행하는 경우 CER(제한된 실행 영역)으로 보호되어야 합니다. 이렇게 하면 갑작스러운 스레드 중단 또는 갑작스러운 AppDomain 언로드까지도 모든 경우에 코드가 실행됩니다.

CER은 특정 try/finally 블록으로, 이는 PrepareConstrainedRegions 호출 바로 앞에 위치합니다.

이렇게 하면 즉시 컴파일러가 try 블록을 실행하기 전에 finally 블록의 모든 코드를 준비하도록 지시합니다. 이렇게 하면 최종 블록의 코드가 빌드되고 모든 경우에 실행됩니다. CER에서 빈 try 블록을 갖는 것은 드문 일이 아닙니다. CER을 사용하면 비동기 스레드 중단 및 메모리 부족 예외로부터 보호됩니다. 매우 깊은 코드에 대한 스택 오버플로를 추가로 처리하는 CER 형식을 참조 ExecuteCodeWithGuaranteedCleanup 하세요.

참고하십시오