Najlepsze rozwiązania dotyczące niezawodności
Następujące reguły niezawodności są zorientowane na program SQL Server; jednak mają one również zastosowanie do dowolnej aplikacji serwera opartej na hoście. Bardzo ważne jest, aby serwery, takie jak SQL Server, nie wyciekły zasobów i nie były wyłączane. Nie można jednak tego zrobić, zapisując kod wsteczny dla każdej metody, która zmienia stan obiektu. Celem jest, aby nie pisać 100 procent niezawodnego kodu zarządzanego, który będzie odzyskiwał się po błędach w każdej lokalizacji z kodem zwrotnym. Byłoby to trudne zadanie z małą szansą na sukces. Środowisko uruchomieniowe języka wspólnego (CLR) nie może łatwo zapewnić wystarczająco silnych gwarancji, aby kod zarządzany mógł pisać doskonały kod. Należy pamiętać, że w przeciwieństwie do ASP.NET program SQL Server używa tylko jednego procesu, którego nie można odzyskać bez wyłączania bazy danych przez nieakceptowalny czas.
Dzięki tym słabszym gwarancjom i uruchomieniu w jednym procesie niezawodność opiera się na przerywaniu wątków lub odtwarzaniu domen aplikacji w razie potrzeby i podejmowaniu środków ostrożności w celu zapewnienia wycieku zasobów systemu operacyjnego, takich jak uchwyty lub pamięć. Nawet w przypadku tego prostszego ograniczenia niezawodności nadal istnieje znaczne wymaganie dotyczące niezawodności:
Nigdy nie przeciekaj zasobów systemu operacyjnego.
Zidentyfikuj wszystkie zarządzane blokady we wszystkich formularzach do środowiska CLR.
Nigdy nie przerywaj współużytkowanego stanu domeny między aplikacjami, co pozwala AppDomain na bezproblemowe działanie recyklingu.
Chociaż teoretycznie istnieje możliwość pisania kodu zarządzanego do obsługi ThreadAbortExceptionwyjątków , StackOverflowExceptioni OutOfMemoryException , oczekuje się, że deweloperzy będą pisać taki niezawodny kod w całej aplikacji, jest nieuzasadnione. Z tego powodu wyjątki poza pasmem powodują zakończenie wykonywania wątku; a jeśli przerwany wątek był edytowany w stanie udostępnionym, który można określić, czy wątek zawiera blokadę, to AppDomain jest zwalniany. Gdy metoda edytując stan udostępniony zostanie przerwana, stan będzie uszkodzony, ponieważ nie można zapisać niezawodnego kodu wycofywania dla aktualizacji stanu udostępnionego.
W programie .NET Framework w wersji 2.0 jedynym hostem, który wymaga niezawodności, jest program SQL Server. Jeśli zestaw zostanie uruchomiony w programie SQL Server, należy wykonać pracę nad niezawodnością dla każdej części tego zestawu, nawet jeśli istnieją określone funkcje, które są wyłączone podczas uruchamiania w bazie danych. Jest to wymagane, ponieważ aparat analizy kodu sprawdza kod na poziomie zestawu i nie może odróżnić wyłączonego kodu. Innym zagadnieniem programowania programu SQL Server jest to, że program SQL Server uruchamia wszystko w jednym procesie, a AppDomain recykling jest używany do czyszczenia wszystkich zasobów, takich jak pamięć i uchwyty systemu operacyjnego.
Nie można zależeć od finalizatorów ani destruktorów ani try/finally
bloków dla kodu zaplecza. Mogą być przerywane lub nie wywoływane.
Wyjątki asynchroniczne mogą być zgłaszane w nieoczekiwanych lokalizacjach, prawdopodobnie każda instrukcja maszyny: ThreadAbortException, StackOverflowExceptioni OutOfMemoryException.
Wątki zarządzane nie muszą być wątkami Win32 w języku SQL; mogą to być włókna.
Stan współużytkowany domeny obejmującej cały proces lub między aplikacjami jest niezwykle trudny do bezpiecznego zmieniania i należy unikać go, gdy tylko jest to możliwe.
Warunki braku pamięci nie są rzadkie w programie SQL Server.
Jeśli biblioteki hostowane w programie SQL Server nie aktualizują poprawnie stanu udostępnionego, istnieje duże prawdopodobieństwo, że kod nie zostanie odzyskany, dopóki baza danych nie zostanie ponownie uruchomiona. Ponadto w niektórych skrajnych przypadkach może to spowodować niepowodzenie procesu programu SQL Server, co spowoduje ponowne uruchomienie bazy danych. Ponowne uruchomienie bazy danych może zdjąć witrynę sieci Web lub wpłynąć na operacje firmy, co zaszkodzi dostępności. Powolny wyciek zasobów systemu operacyjnego, takich jak pamięć lub dojścia, może spowodować, że serwer ostatecznie nie będzie mógł przydzielać dojść bez możliwości odzyskiwania lub potencjalnie serwer może powoli obniżyć wydajność i zmniejszyć dostępność aplikacji klienta. Wyraźnie chcemy uniknąć tych scenariuszy.
Reguły najlepszych rozwiązań
Wprowadzenie koncentruje się na tym, co przegląd kodu zarządzanego, który działa na serwerze, musiałby przechwycić, aby zwiększyć stabilność i niezawodność platformy. Wszystkie te testy są ogólnie dobrym rozwiązaniem i bezwzględnym koniecznością na serwerze.
W obliczu ograniczenia martwej blokady lub zasobu program SQL Server przerwa wątek lub usunie AppDomainelement . W takim przypadku należy uruchomić tylko kod wycofywania w ograniczonym regionie wykonywania (CER).
Użyj Sejf Handle, aby uniknąć wycieków zasobów
W przypadku AppDomain zwolnienia nie można zależeć od finally
wykonywanych bloków ani finalizatorów, dlatego ważne jest, aby wyodrębnić dostęp do wszystkich zasobów systemu operacyjnego za pośrednictwem SafeHandle klasy, a nie IntPtrklasy , HandleReflub podobnych. Dzięki temu CLR może śledzić i zamykać uchwyty używane nawet w AppDomain przypadku rozerwania. SafeHandle będzie używać finalizatora krytycznego, który CLR będzie zawsze uruchamiany.
Uchwyt systemu operacyjnego jest przechowywany w bezpiecznym dojściu od momentu jego utworzenia do momentu jego wydania. Nie ma okna, w którym ThreadAbortException może wystąpić wyciek uchwytu. Ponadto wywołanie platformy będzie odwoływać się do uchwytu, co umożliwia ścisłe śledzenie okresu istnienia uchwytu, uniemożliwiając problem z zabezpieczeniami z warunkiem wyścigu między Dispose
i metodą, która obecnie korzysta z uchwytu.
Większość klas, które obecnie mają finalizator, aby po prostu wyczyścić uchwyt systemu operacyjnego, nie będzie już potrzebować finalizatora. Zamiast tego finalizator będzie znajdować się w klasie pochodnej SafeHandle .
Należy pamiętać, że SafeHandle nie zastępuje elementu IDisposable.Dispose. Nadal istnieją potencjalne zalety rywalizacji o zasoby i wydajność, aby jawnie usunąć zasoby systemu operacyjnego. Po prostu należy pamiętać, że bloki, finally
które jawnie usuwają zasoby, mogą nie być wykonywane do ukończenia.
SafeHandle Umożliwia zaimplementowanie własnej ReleaseHandle metody wykonującej pracę w celu zwolnienia uchwytu, takiego jak przekazywanie stanu do systemu operacyjnego do obsługi procedury zwalniania lub zwalnianie zestawu uchwytów w pętli. ClR gwarantuje, że ta metoda jest uruchamiana. Obowiązkiem autora implementacji ReleaseHandle jest zapewnienie, że dojście zostanie wydane we wszystkich okolicznościach. Niepowodzenie w tym celu spowoduje wyciek uchwytu, co często powoduje wyciek zasobów natywnych skojarzonych z uchwytem. W związku z tym kluczowe znaczenie ma struktura SafeHandle klas pochodnych, tak aby ReleaseHandle implementacja nie wymagała alokacji żadnych zasobów, które mogą nie być dostępne w czasie wywołania. Należy pamiętać, że dopuszczalne jest wywoływanie metod, które mogą zakończyć się niepowodzeniem w implementacji ReleaseHandle , pod warunkiem, że kod może obsłużyć takie błędy i ukończyć kontrakt w celu zwolnienia natywnego dojścia. W celach debugowania ma wartość zwracanąBoolean, ReleaseHandle która może być ustawiona na false
wartość w przypadku wystąpienia katastrofalnego błędu, co uniemożliwia zwolnienie zasobu. Spowoduje to aktywowanie wersjiHandleFailed MDA, jeśli jest włączone, aby pomóc w zidentyfikowaniu problemu. Nie ma to wpływu na środowisko uruchomieniowe w żaden inny sposób; ReleaseHandle Nie zostanie ponownie wywołany dla tego samego zasobu, a w związku z tym dojść zostanie ujawnione.
SafeHandle nie jest odpowiedni w niektórych kontekstach. ReleaseHandle Ponieważ metoda może być uruchamiana w wątku GC finalizatora, wszelkie uchwyty wymagane do zwolnienia w określonym wątku nie powinny być opakowane w SafeHandle.
Otoki wywoływane przez środowisko uruchomieniowe (RCW) mogą być czyszczone przez clR bez dodatkowego kodu. W przypadku kodu, który używa wywołania platformy i traktuje obiekt COM jako IUnknown*
IntPtrlub , kod powinien zostać przepisany do używania RCW. SafeHandle może nie być odpowiedni dla tego scenariusza ze względu na możliwość wywołania metody wydania niezarządzanej z powrotem do kodu zarządzanego.
Reguła analizy kodu
Służy SafeHandle do hermetyzacji zasobów systemu operacyjnego. Nie należy używać HandleRef ani pól typu IntPtr.
Upewnij się, że finalizatory nie muszą być uruchamiane, aby zapobiec wyciekowi zasobów systemu operacyjnego
Dokładnie przejrzyj finalizatory, aby upewnić się, że nawet jeśli nie zostaną uruchomione, krytyczny zasób systemu operacyjnego nie wycieknie. W przeciwieństwie do normalnego AppDomain zwolnienia, gdy aplikacja jest uruchamiana w stanie stałym lub gdy serwer taki jak program SQL Server zostanie zamknięty, obiekty nie są finalizowane podczas nagłego AppDomain zwolnienia. Upewnij się, że zasoby nie wyciekają w przypadku nagłego zwolnienia, ponieważ nie można zagwarantować poprawności aplikacji, ale integralność serwera musi być utrzymywana przez brak wycieku zasobów. Użyj SafeHandle polecenia , aby zwolnić wszystkie zasoby systemu operacyjnego.
Upewnij się, że klauzule finally nie muszą być uruchamiane, aby zapobiec wyciekowi zasobów systemu operacyjnego
finally
Klauzule nie są gwarantowane, aby działały poza cers, wymagając, aby deweloperzy bibliotek nie polegali na kodzie w finally
bloku, aby zwolnić niezarządzane zasoby. Użycie SafeHandle jest zalecanym rozwiązaniem.
Reguła analizy kodu
Służy SafeHandle do czyszczenia zasobów systemu operacyjnego zamiast Finalize
. Nie używaj metody IntPtr; użyj SafeHandle polecenia , aby hermetyzować zasoby. Jeśli klauzula finally musi zostać uruchomiona, umieść ją w cer.
Wszystkie blokady powinny przechodzić przez istniejący kod blokady zarządzanej
ClR musi wiedzieć, kiedy kod znajduje się w blokadzie, aby wiedzieć, aby usunąć AppDomain zamiast przerywać wątek. Przerwanie wątku może być niebezpieczne, ponieważ dane obsługiwane przez wątek mogą pozostać w niespójnym stanie. W związku z tym całość AppDomain musi być odzyskiwanych. Konsekwencje niepowodzenia identyfikacji blokady mogą być zakleszczenia lub nieprawidłowe wyniki. Użyj metod BeginCriticalRegion i EndCriticalRegion , aby zidentyfikować regiony blokady. Są to metody statyczne w Thread klasie, które mają zastosowanie tylko do bieżącego wątku, co pomaga zapobiec edytowaniu liczby blokad innego wątku.
Enter i Exit mają wbudowane to powiadomienie CLR, więc ich użycie jest zalecane, a także użycie instrukcji blokady, która używa tych metod.
Inne mechanizmy blokowania, takie jak blokady spin i AutoResetEvent muszą wywołać te metody, aby powiadomić CLR o wprowadzeniu sekcji krytycznej. Te metody nie przyjmują żadnych blokad; informują clR, że kod jest wykonywany w sekcji krytycznej i przerwanie wątku może pozostawić stan udostępniony niespójny. Jeśli zdefiniowano własny typ blokady, taki jak klasa niestandardowa ReaderWriterLock , użyj tych metod liczby blokad.
Reguła analizy kodu
Oznacz i zidentyfikuj wszystkie blokady przy użyciu poleceń BeginCriticalRegion i EndCriticalRegion. Nie używaj CompareExchangeparametrów , Incrementi Decrement w pętli. Nie należy wywoływać platformy wariantów win32 tych metod. Nie należy używać Sleep w pętli. Nie używaj nietrwałych pól.
Kod oczyszczania musi znajdować się w bloku w końcu lub w bloku catch, a nie po przechwyceniu
Kod oczyszczania nigdy nie powinien być zgodny z blokiemcatch
; powinien znajdować się w finally
samym bloku lub.catch
Powinno to być normalne dobre rozwiązanie. Blok finally
jest zazwyczaj preferowany, ponieważ uruchamia ten sam kod zarówno wtedy, gdy jest zgłaszany wyjątek, jak i gdy na końcu try
bloku występuje zwykle. W przypadku wystąpienia nieoczekiwanego wyjątku, na przykład ThreadAbortException, kod oczyszczania nie zostanie uruchomiony. Wszelkie niezarządzane zasoby, które należy wyczyścić w obiekcie finally
, powinny być opakowane w element , SafeHandle aby zapobiec wyciekom. Zwróć uwagę, że słowo kluczowe języka C# using
może służyć skutecznie do usuwania obiektów, w tym uchwytów.
Mimo że AppDomain recykling może oczyścić zasoby w wątku finalizatora, nadal ważne jest, aby umieścić kod oczyszczania w odpowiednim miejscu. Należy pamiętać, że jeśli wątek otrzymuje wyjątek asynchroniczny bez trzymania blokady, CLR próbuje zakończyć sam wątek bez konieczności recyklingu AppDomain. Zapewnienie, że zasoby są czyszczone wcześniej, a nie później, pomaga przez udostępnienie większej ilości zasobów i lepsze zarządzanie okresem istnienia. Jeśli nie zamkniesz jawnie dojścia do pliku w ścieżce kodu błędu, zaczekaj na SafeHandle jego wyczyszczenie, przy następnym uruchomieniu kodu próba uzyskania dostępu do dokładnie tego samego pliku może zakończyć się niepowodzeniem, jeśli finalizator nie został jeszcze uruchomiony. Z tego powodu upewnienie się, że kod oczyszczania istnieje i działa prawidłowo, pomoże odzyskać sprawność po awariach w sposób bardziej czysty i szybki, mimo że nie jest to ściśle konieczne.
Reguła analizy kodu
Wyczyść kod po tym, jak catch
musi znajdować się w finally
bloku. Umieść wywołania do usunięcia w bloku na końcu. catch
bloki powinny kończyć się rzutem lub wróceniem. Chociaż wystąpią wyjątki, takie jak kod wykrywający, czy można ustanowić połączenie sieciowe, w którym można uzyskać dowolną liczbę wyjątków, każdy kod, który wymaga przechwycenia wielu wyjątków w normalnych okolicznościach, powinien wskazywać, że kod powinien zostać przetestowany, aby sprawdzić, czy zakończy się powodzeniem.
Współużytkowany stan współużytkowany dla całego procesu między domenami aplikacji powinien zostać wyeliminowany lub użyć ograniczonego regionu wykonywania
Jak opisano we wprowadzeniu, bardzo trudno jest napisać zarządzany kod, który monitoruje stan współużytkowany przez cały proces między domenami aplikacji w niezawodny sposób. Stan udostępniony całego procesu to dowolna struktura danych współdzielona między domenami aplikacji w kodzie Win32 wewnątrz środowiska CLR lub w kodzie zarządzanym przy użyciu komunikacji wirtualnej. Każdy stan współużytkowany modyfikowalny jest bardzo trudny do poprawnego zapisu w kodzie zarządzanym, a każdy statyczny stan udostępniony może być wykonywany tylko z dużą starannością. Jeśli masz stan współużytkowany obejmujący cały proces lub maszynę, znajdź jakiś sposób, aby go wyeliminować lub chronić stan współużytkowany przy użyciu ograniczonego regionu wykonywania (CER). Należy pamiętać, że każda biblioteka ze stanem udostępnionym, który nie został zidentyfikowany i poprawiony, może spowodować awarię hosta, takiego jak program SQL Server, który wymaga czystego AppDomain zwolnienia.
Jeśli kod używa obiektu COM, unikaj udostępniania tego obiektu COM między domenami aplikacji.
Blokady nie działają w całym procesie ani między domenami aplikacji.
W przeszłości Enter i instrukcja blokady zostały użyte do tworzenia globalnych blokad procesów. Dzieje się tak na przykład w przypadku blokowania AppDomain klas agile, takich jak Type wystąpienia z zestawów nieudzielonych, Thread obiektów, ciągów internowanych i niektórych ciągów udostępnionych w domenach aplikacji przy użyciu komunikacji zdalnej. Te blokady nie są już przetwarzane w całym procesie. Aby zidentyfikować obecność blokady domeny międzyplikacyjnej obejmującej cały proces, ustal, czy kod w blokadzie używa dowolnego zewnętrznego, utrwalonego zasobu, takiego jak plik na dysku, czy ewentualnie baza danych.
Należy pamiętać, że zablokowanie w obrębie obiektu AppDomain może powodować problemy, jeśli chroniony kod używa zasobu zewnętrznego, ponieważ ten kod może być uruchamiany jednocześnie w wielu domenach aplikacji. Może to być problem podczas zapisywania do jednego pliku dziennika lub powiązania z gniazdem dla całego procesu. Te zmiany oznaczają, że nie ma łatwego sposobu, korzystając z kodu zarządzanego, aby uzyskać blokadę globalną procesu, inną niż użycie nazwanego Mutex lub Semaphore wystąpienia. Utwórz kod, który nie działa jednocześnie w dwóch domenach aplikacji lub użyj Mutex klas lub Semaphore . Jeśli nie można zmienić istniejącego kodu, nie używaj win32 o nazwie mutex do osiągnięcia tej synchronizacji, ponieważ działa w trybie światłowodowym oznacza, że nie można zagwarantować, że ten sam wątek systemu operacyjnego uzyska i zwolni mutex. Aby zsynchronizować blokadę kodu, należy użyć klasy zarządzanej Mutex lub nazwy ManualResetEventAutoResetEvent, albo Semaphore w taki sposób, aby clR wiedziała, że zamiast synchronizować blokadę przy użyciu kodu niezarządzanego.
Unikaj blokady(typeof(MyType))
Prywatne i publiczne Type obiekty w udostępnionych zestawach z tylko jedną kopią kodu współużytkowanego we wszystkich domenach aplikacji również mogą występować problemy. W przypadku zestawów udostępnionych istnieje tylko jedno wystąpienie Type każdego procesu, co oznacza, że wiele domen aplikacji współdzieli dokładnie to samo Type wystąpienie. Przejęcie blokady na wystąpieniu Type powoduje zablokowanie, które wpływa na cały proces, a nie tylko .AppDomain Jeśli ktoś AppDomain przyjmuje blokadę Type na obiekcie, ten wątek zostanie nagle przerwany, nie zwolni blokady. Ta blokada może spowodować zakleszczenie innych domen aplikacji.
Dobrym sposobem na zablokowanie metod statycznych jest dodanie statycznego obiektu synchronizacji wewnętrznej do kodu. Może to być zainicjowane w konstruktorze klasy, jeśli istnieje, ale jeśli nie, można go zainicjować w następujący sposób:
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;
}
}
Następnie podczas pobierania blokady użyj InternalSyncObject
właściwości , aby uzyskać obiekt do zablokowania. Nie musisz używać właściwości, jeśli zainicjowano wewnętrzny obiekt synchronizacji w konstruktorze klasy. Podwójny kod inicjowania blokady powinien wyglądać następująco:
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;
}
}
Uwaga dotycząca blokady (to)
Ogólnie rzecz biorąc, dopuszczalne jest zablokowanie pojedynczego obiektu, który jest publicznie dostępny. Jeśli jednak obiekt jest pojedynczym obiektem, który może spowodować zakleszczenie całego podsystemu, rozważ użycie powyższego wzorca projektowego. Na przykład blokada jednego SecurityManager obiektu może spowodować zakleszczenie w obrębie AppDomain całego obiektu, dzięki czemu nie AppDomain będzie można jej używać. Dobrym rozwiązaniem jest brak blokady na publicznie dostępnym obiekcie tego typu. Jednak blokada dla pojedynczej kolekcji lub tablicy zwykle nie powinna stanowić problemu.
Reguła analizy kodu
Nie należy używać blokad dla typów, które mogą być używane w domenach aplikacji lub nie mają silnego poczucia tożsamości. Nie należy wywoływać Enter elementu , , MethodInfo, PropertyInfoValueTypeString, , Threadani żadnego obiektu pochodzącego z klasy MarshalByRefObject.Type
Usuń GC. Połączenia keepAlive
Znaczna ilość istniejącego kodu nie jest używana KeepAlive , gdy powinna lub używa go, gdy nie jest odpowiednia. Po przekonwertowaniu na SafeHandleklasy nie muszą wywoływać KeepAlivemetody , zakładając, że nie mają finalizatora, ale polegają na SafeHandle finalizacji dojść systemu operacyjnego. Chociaż koszt wydajności utrzymania wywołania może być nieznaczny, postrzeganie, że wywołanie KeepAliveKeepAlive metody jest konieczne lub wystarczające do rozwiązania problemu z okresem istnienia, który może już nie istnieć, sprawia, że kod jest trudniejszy do utrzymania. Jednak w przypadku korzystania z międzyoperacyjnych otoek CLR (RCWs) KeepAlive międzyoperacyjności MODELU COM jest nadal wymagana przez kod.
Reguła analizy kodu
Usuń KeepAliveelement .
Używanie atrybutu HostProtection
Program HostProtectionAttribute (HPA) umożliwia korzystanie z deklaratywnych akcji zabezpieczeń w celu określenia wymagań dotyczących ochrony hosta, dzięki czemu host może zapobiec wywołaniu nawet w pełni zaufanego kodu, które są nieodpowiednie dla danego hosta, takiego jak Exit lub Show dla programu SQL Server.
HpA ma wpływ tylko na aplikacje niezarządzane, które hostują środowisko uruchomieniowe języka wspólnego i implementują ochronę hosta, taką jak SQL Server. Po zastosowaniu akcja zabezpieczeń powoduje utworzenie żądania łącza na podstawie zasobów hosta uwidacznianych przez klasę lub metodę. Jeśli kod jest uruchamiany w aplikacji klienckiej lub na serwerze, który nie jest chroniony przez hosta, atrybut "wyparuje"; nie jest wykrywany i dlatego nie jest stosowany.
Ważne
Celem tego atrybutu jest wymuszanie wytycznych dotyczących modelu programowania specyficznego dla hosta, a nie zachowania zabezpieczeń. Mimo że zapotrzebowanie na łącza służy do sprawdzania zgodności z wymaganiami modelu programowania, HostProtectionAttribute nie jest to uprawnienie zabezpieczeń.
Jeśli host nie ma wymagań dotyczących modelu programowania, wymagania dotyczące linków nie występują.
Ten atrybut identyfikuje następujące elementy:
Metody lub klasy, które nie pasują do modelu programowania hosta, ale w przeciwnym razie są łagodne.
Metody lub klasy, które nie pasują do modelu programowania hosta i mogą prowadzić do destabilizacji kodu użytkownika zarządzanego przez serwer.
Metody lub klasy, które nie pasują do modelu programowania hosta i mogą prowadzić do destabilizacji samego procesu serwera.
Uwaga
Jeśli tworzysz bibliotekę klas, która ma być wywoływana przez aplikacje, które mogą być wykonywane w środowisku chronionym przez hosta, należy zastosować ten atrybut do elementów członkowskich, które uwidaczniają HostProtectionResource kategorie zasobów. Elementy członkowskie biblioteki klas programu .NET Framework z tym atrybutem powodują sprawdzenie tylko bezpośredniego obiektu wywołującego. Członek biblioteki musi również spowodować sprawdzenie jego bezpośredniego obiektu wywołującego w ten sam sposób.
Więcej informacji na temat rozwiązania HPA można znaleźć w temacie HostProtectionAttribute.
Reguła analizy kodu
W przypadku programu SQL Server wszystkie metody używane do wprowadzenia synchronizacji lub wątkowania muszą być identyfikowane z hpa. Obejmuje to metody współużytkujące stan, są synchronizowane lub zarządzają procesami zewnętrznymi. Wartości HostProtectionResource wpływające na program SQL Server to SharedState, Synchronizationi ExternalProcessMgmt. Jednak każda metoda, która uwidacznia dowolny element HostProtectionResource , powinna zostać zidentyfikowana przez hpa, a nie tylko te, które korzystają z zasobów wpływających na język SQL.
Nie blokuj w sposób nieokreślony w kodzie niezarządzanych
Blokowanie w kodzie niezarządzanym zamiast w kodzie zarządzanym może spowodować atak typu "odmowa usługi", ponieważ clR nie może przerwać wątku. Zablokowany wątek uniemożliwia clR zwalnianie AppDomainelementu , przynajmniej bez wykonywania niektórych bardzo niebezpiecznych operacji. Blokowanie przy użyciu elementu pierwotnego synchronizacji systemu Windows jest wyraźnym przykładem czegoś, czego nie możemy zezwolić. Jeśli jest to możliwe, należy unikać ReadFile
blokowania wywołania w gniazdie — najlepiej, aby interfejs API systemu Windows zapewniał mechanizm dla operacji takiej jak ta, aby upłynął limit czasu.
Każda metoda, która wywołuje metodę natywną, powinna używać wywołania Win32 z rozsądnym, skończonym limitem czasu. Jeśli użytkownik może określić limit czasu, użytkownik nie powinien mieć zezwolenia na określenie nieskończonego limitu czasu bez określonego uprawnienia zabezpieczeń. Jeśli metoda będzie blokowana przez ponad 10 sekund, musisz użyć wersji obsługującej limity czasu lub potrzebujesz dodatkowej obsługi środowiska CLR.
Oto kilka przykładów problematycznych interfejsów API. Potoki (anonimowe i nazwane) można utworzyć przy użyciu limitu czasu; jednak kod musi zapewnić, że nigdy nie wywołuje CreateNamedPipe
ani WaitNamedPipe
NMPWAIT_WAIT_FOREVER. Ponadto może wystąpić nieoczekiwane blokowanie, nawet jeśli określono limit czasu. Wywołanie potoku anonimowego spowoduje zablokowanie WriteFile
do momentu zapisania wszystkich bajtów, co oznacza, że jeśli bufor zawiera nieprzeczytane dane, WriteFile
wywołanie zablokuje, dopóki czytelnik nie zwolni miejsca w buforze potoku. Gniazda powinny zawsze używać interfejsu API, który honoruje mechanizm przekroczenia limitu czasu.
Reguła analizy kodu
Blokowanie bez przekroczenia limitu czasu w kodzie niezarządzanym jest atakiem typu "odmowa usługi". Nie należy wykonywać wywołań platformy do WaitForSingleObject
wywołań , , WaitForSingleObjectEx
WaitForMultipleObjects
, MsgWaitForMultipleObjects
i MsgWaitForMultipleObjectsEx
. Nie używaj NMPWAIT_WAIT_FOREVER.
Identyfikowanie dowolnych funkcji zależnych od sta
Zidentyfikuj dowolny kod korzystający z apartamentów jednowątkowych COM (STA). StAs są wyłączone w procesie programu SQL Server. Funkcje zależne od CoInitialize
programu , takie jak liczniki wydajności lub schowek, muszą być wyłączone w programie SQL Server.
Upewnij się, że finalizatory są wolne od problemów z synchronizacją
W przyszłych wersjach programu .NET Framework może istnieć wiele wątków finalizatora, co oznacza, że finalizatory dla różnych wystąpień tego samego typu są uruchamiane jednocześnie. Nie muszą być całkowicie bezpieczne wątkami; Moduł odśmieceń pamięci gwarantuje, że tylko jeden wątek uruchomi finalizator dla danego wystąpienia obiektu. Jednak finalizatory muszą być kodowane, aby uniknąć warunków wyścigu i zakleszczeń podczas uruchamiania jednocześnie w wielu różnych wystąpieniach obiektów. W przypadku korzystania z dowolnego stanu zewnętrznego, takiego jak zapisywanie w pliku dziennika, w finalizatorze należy obsłużyć problemy z wątkami. Nie należy polegać na finalizacji w celu zapewnienia bezpieczeństwa wątków. Nie należy używać magazynu lokalnego wątku, zarządzanego lub natywnego do przechowywania stanu w wątku finalizatora.
Reguła analizy kodu
Finalizatory muszą być wolne od problemów z synchronizacją. Nie używaj statycznego stanu modyfikowalnego w finalizatorze.
Unikaj niezarządzanej pamięci, jeśli to możliwe
Pamięć niezarządzana może zostać ujawniona, podobnie jak w przypadku uchwytu systemu operacyjnego. Jeśli to możliwe, spróbuj użyć pamięci na stosie przy użyciu obiektu stackalloc lub przypiętego obiektu zarządzanego, takiego jak stała instrukcja lub GCHandle przy użyciu bajtu[]. Ostatecznie GC oczyści je. Jeśli jednak musisz przydzielić pamięć niezarządzaną, rozważ użycie klasy pochodzącej z SafeHandle klasy w celu opakowania alokacji pamięci.
Należy pamiętać, że istnieje co najmniej jeden przypadek, w którym SafeHandle nie jest odpowiedni. W przypadku wywołań metody COM, które przydzielają lub zwalniają pamięć, często jedna biblioteka DLL przydziela pamięć za pośrednictwem innej biblioteki DLL zwalnia tę pamięć za pomocą CoTaskMemAlloc
CoTaskMemFree
polecenia . Użycie SafeHandle w tych miejscach byłoby niewłaściwe, ponieważ będzie próbował powiązać okres istnienia niezarządzanej pamięci do okresu istnienia SafeHandle zamiast zezwalać innej biblioteki DLL na kontrolowanie okresu istnienia pamięci.
Przejrzyj wszystkie zastosowania catch(Exception)
Przechwyć bloki, które przechwytują wszystkie wyjątki zamiast jednego określonego wyjątku, przechwytują również wyjątki asynchroniczne. Sprawdź każdy blok catch(Exception), wyszukując brak ważnego zasobu zwalniającego lub wycofywania kodu, który może zostać pominięty, a także potencjalnie niepoprawne zachowanie w samym bloku catch na potrzeby obsługi ThreadAbortExceptionelementu , StackOverflowExceptionlub OutOfMemoryException. Należy pamiętać, że ten kod może być rejestrowania lub wprowadzania pewnych założeń, że może on widzieć tylko pewne wyjątki lub że zawsze, gdy wystąpi wyjątek, wystąpił dokładnie jeden konkretny powód. Może być konieczne zaktualizowanie tych założeń w celu uwzględnienia ThreadAbortException.
Rozważ zmianę wszystkich miejsc, które przechwytują wszystkie wyjątki w celu przechwycenia określonego typu wyjątku, którego oczekujesz, zostaną zgłoszone, na przykład FormatException z metod formatowania ciągów. Zapobiega to uruchamianiu bloku catch w przypadku nieoczekiwanych wyjątków i pomoże zapewnić, że kod nie ukrywa usterek, przechwytując nieoczekiwane wyjątki. Ogólna zasada nigdy nie obsługuje wyjątku w kodzie biblioteki (kod, który wymaga przechwycenia wyjątku, może wskazywać na wadę projektu w wywoływanym kodzie). W niektórych przypadkach może być konieczne przechwycenie wyjątku i zgłoszenie innego typu wyjątku w celu dostarczenia większej ilości danych. W tym przypadku należy użyć wyjątków zagnieżdżonych, przechowując rzeczywistą przyczynę awarii we InnerException właściwości nowego wyjątku.
Reguła analizy kodu
Przejrzyj wszystkie bloki catch w kodzie zarządzanym, które przechwytują wszystkie obiekty lub przechwytują wszystkie wyjątki. W języku C#oznacza to flagowanie zarówno , jak catch
{} i catch(Exception)
{}. Rozważ wprowadzenie bardzo specyficznego typu wyjątku lub przejrzyj kod, aby upewnić się, że nie działa on w niewłaściwy sposób, jeśli przechwytuje nieoczekiwany typ wyjątku.
Nie zakładaj, że zarządzany wątek jest wątkiem Win32 — jest to światłowod
Użycie zarządzanego magazynu lokalnego wątku działa, ale nie można używać magazynu lokalnego niezarządzanego wątku lub założyć, że kod zostanie uruchomiony ponownie w bieżącym wątku systemu operacyjnego. Nie zmieniaj ustawień, takich jak ustawienia regionalne wątku. Nie należy wywoływać InitializeCriticalSection
ani CreateMutex
za pośrednictwem wywołania platformy, ponieważ wymagają wątku systemu operacyjnego, który wprowadza blokadę, również zamyka blokadę. Ponieważ nie będzie tak w przypadku korzystania z światłowodów, sekcje krytyczne Win32 i mutexes nie mogą być używane bezpośrednio w języku SQL. Należy pamiętać, że klasa zarządzana Mutex nie obsługuje tych problemów z koligacją wątku.
Można bezpiecznie używać większości stanu w obiekcie zarządzanym Thread , w tym zarządzanego magazynu lokalnego wątku i bieżącej kultury interfejsu użytkownika wątku. Można również użyć ThreadStaticAttribute, co sprawia, że wartość istniejącej zmiennej statycznej jest dostępna tylko przez bieżący zarządzany wątek (jest to inny sposób wykonywania magazynu lokalnego światłowodu w CLR). Ze względów modelu programowania nie można zmienić bieżącej kultury wątku podczas uruchamiania w języku SQL.
Reguła analizy kodu
Program SQL Server działa w trybie światłowodowym; nie należy używać magazynu lokalnego wątku. Unikaj wywoływania wywołań platformy do TlsAlloc
, , TlsFree
TlsGetValue
iTlsSetValue.
Zezwalaj programowi SQL Server na personifikację
Ponieważ personifikacja działa na poziomie wątku, a sql może działać w trybie światłowodowym, zarządzany kod nie powinien personifikować użytkowników i nie powinien wywoływać metody RevertToSelf
.
Reguła analizy kodu
Zezwalaj programowi SQL Server na personifikację. Nie należy używać RevertToSelf
, DdeImpersonateClient
ImpersonateLoggedOnUser
ImpersonateNamedPipeClient
ImpersonateDdeClientWindow
ImpersonateAnonymousToken
RpcImpersonateClient
RpcRevertToSelf
ImpersonateSelf
RpcRevertToSelfEx
lub .SetThreadToken
Nie należy wywoływać wątku::Suspend
Możliwość zawieszenia wątku może wydawać się prostą operacją, ale może powodować zakleszczenia. Jeśli wątek trzymający blokadę zostanie zawieszony przez drugi wątek, a następnie drugi wątek próbuje użyć tej samej blokady, wystąpi impas. Suspend może zakłócać zabezpieczenia, ładowanie klas, komunikacja zdalna i odbicie obecnie.
Reguła analizy kodu
Nie należy wywoływać metody Suspend. Rozważ użycie rzeczywistego elementu pierwotnego synchronizacji, takiego jak lub SemaphoreManualResetEvent .
Ochrona operacji krytycznych przy użyciu ograniczonych regionów wykonywania i kontraktów niezawodności
Podczas wykonywania złożonej operacji, która aktualizuje stan współużytkowany lub który musi deterministycznie zakończyć się powodzeniem lub w pełni zakończyć niepowodzeniem, upewnij się, że jest on chroniony przez ograniczony region wykonywania (CER). Gwarantuje to, że kod jest uruchamiany w każdym przypadku, nawet nagłe przerwanie wątku lub nagłe AppDomain zwolnienie.
CER to konkretny try/finally
blok bezpośrednio poprzedzony wywołaniem metody PrepareConstrainedRegions.
W ten sposób kompilator just-in-time przygotuje cały kod w bloku na koniec przed uruchomieniem try
bloku. Gwarantuje to, że kod w bloku na koniec zostanie skompilowany i zostanie uruchomiony we wszystkich przypadkach. Nie jest rzadkością w CER, aby mieć pusty try
blok. Używanie cer chroni przed przerwaniem asynchronicznych wątków i wyjątkami poza pamięcią. Zobacz ExecuteCodeWithGuaranteedCleanup , jak uzyskać formularz CER, który dodatkowo obsługuje przepełnienia stosu w celu uzyskania bardzo głębokiego kodu.