Udostępnij za pośrednictwem


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 traciły zasobów i nie były zatrzymywane. 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 jest w stanie łatwo zapewnić wystarczająco silnych gwarancji, aby umożliwić pisanie doskonałego kodu zarządzanego. 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 działaniu w jednym procesie, niezawodność opiera się na zakończeniu wątków lub ponownym wykorzystaniu domen aplikacji w razie potrzeby, oraz na podejmowaniu środków ostrożności w celu zapobiegania wyciekowi zasobów systemu operacyjnego, takich jak uchwyty czy 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 formach w środowisku CLR.

  • Nigdy nie przerywaj wspólnego stanu domeny między aplikacjami, co pozwala na bezproblemowe działanie recyklingu AppDomain.

Chociaż teoretycznie istnieje możliwość pisania kodu zarządzanego do obsługi wyjątków ThreadAbortException, StackOverflowException i OutOfMemoryException, nieuzasadnione jest oczekiwanie, że deweloperzy będą pisać taki niezawodny kod w całej aplikacji. 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 polegać na finalizatorach, destruktorach ani blokach try/finally w kodzie cofania. 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 wpłynąć na operacje firmy i spowodować, że strona internetowa stanie się niedostępna, co może wpłynąć negatywnie na dostępność. 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 kontrole są ogólnie dobrą praktyką i bezwzględną koniecznością na serwerze.

W przypadku zakleszczenia albo ograniczenia zasobów, SQL Server przerwie wątek lub usunie AppDomain. W takim przypadku gwarantowane jest, że zostanie uruchomiony tylko kod wycofywania w ograniczonym obszarze wykonawczym (CER).

Użyj elementu SafeHandle, aby uniknąć wycieków zasobów

W przypadku AppDomain rozładunku nie można polegać na wykonywaniu bloków finalizujących ani finalizatorów, dlatego ważne jest, aby udostępniać dostęp do wszystkich zasobów systemu operacyjnego za pośrednictwem klasy finally zamiast klasy SafeHandle, IntPtr lub 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 zawsze będzie uruchamiać.

Uchwyt systemu operacyjnego jest przechowywany w bezpiecznym uchwycie od momentu jego utworzenia do momentu jego zwolnienia. Nie ma okna, w którym ThreadAbortException może wystąpić wyciek uchwytu. Ponadto wywołanie platformy będzie zliczać odwołania do uchwytu, co umożliwia ścisłe śledzenie okresu istnienia uchwytu, uniemożliwiając problem z zabezpieczeniami wynikający z warunku wyścigu między Dispose a metodą, która aktualnie używa uchwytu.

Większość klas, które obecnie mają finalizator do czyszczenia uchwytu systemu operacyjnego, nie potrzebują go już więcej. Zamiast tego finalizator będzie znajdować się w klasie pochodnej SafeHandle .

Należy pamiętać, że SafeHandle nie zastępuje elementu IDisposable.Dispose. Wciąż istnieją potencjalne korzyści związane z rywalizacją zasobów i poprawą wydajności dzięki jawnemu zarządzaniu zasobami 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, na przykład poprzez przekazywanie stanu do procedury zwalniania uchwytu w systemie operacyjnym lub zwalnianie zestawu uchwytów w pętli. CLR gwarantuje, że ta metoda zostanie uruchomiona. Obowiązkiem autora implementacji ReleaseHandle jest zapewnienie, że uchwyt zostanie zwolniony 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 Twój kod może obsłużyć takie błędy i spełnić warunki kontraktu w celu zwolnienia natywnego uchwytu. W celach debugowania ReleaseHandle ma wartość zwracaną Boolean, która może być ustawiona na false w przypadku wystąpienia katastrofalnego błędu, co uniemożliwia zwolnienie zasobu. Spowoduje to aktywację releaseHandleFailed MDA, jeśli jest włączony, aby ułatwić zidentyfikowanie 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 uchwyt zostanie ujawniony.

SafeHandle nie jest odpowiedni w niektórych kontekstach. Ponieważ metoda ReleaseHandle może działać w wątku GC finalizatora, wszelkie uchwyty, które muszą być zwolnione w określonym wątku, nie powinny być opakowane w SafeHandle.

Opakowania wywoływane przez środowisko uruchomieniowe (RCW) mogą być czyszczone przez CLR bez dodatkowego kodu. W przypadku kodu, który używa wywołania interfejsu platformy i traktuje obiekt COM jako IUnknown* lub IntPtr, 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

Użyj 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 wyładowania, gdy aplikacja działa w stabilnym stanie lub gdy serwer takiego jak SQL Server zamyka się, obiekty nie są dokończone podczas nagłego AppDomain wyładowania. 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 Nie ma gwarancji, że klauzule będą działały poza CERs, co wymaga od deweloperów bibliotek, aby nie polegali na kodzie w finally bloku do zwalniania niezarządzanych zasobów. Użycie SafeHandle jest zalecanym rozwiązaniem.

Reguła analizy kodu

Użyj SafeHandle do czyszczenia zasobów systemu operacyjnego zamiast Finalize. Nie używaj IntPtr; użyj SafeHandle do hermetyzacji zasobów. 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ć, że należy zlikwidować 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ć odzyskana. Konsekwencjami niepowodzenia w identyfikacji blokady mogą być zarówno zakleszczenia, jak i 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 zaleca się ich używanie, a także korzystanie z instrukcji lock, która wykorzystuje te metody.

Dodatkowe mechanizmy blokujące, takie jak blokady spin i AutoResetEvent muszą wywołać te metody, aby powiadomić CLR o wejściu do sekcji krytycznej. Te metody nie przyjmują żadnych blokad; informują CLR, że kod jest wykonywany w krytycznym fragmencie, a przerwanie wątku może pozostawić współdzielony stan niespójny. Jeśli zdefiniujesz własny typ blokady, taki jak niestandardowa klasa ReaderWriterLock, użyj tych metod liczenia blokad.

Reguła analizy kodu

Oznacz i zidentyfikuj wszystkie blokady przy użyciu poleceń BeginCriticalRegion i EndCriticalRegion. Nie używaj CompareExchange, Increment i Decrement w pętli. Nie należy używać platformowego wywołania dla wariantów Win32 tych metod. Nie należy używać Sleep w pętli. Nie używaj pól volatile.

Kod czyszczenia musi znajdować się w bloku finally albo catch, a nie po bloku catch.

Kod oczyszczania nigdy nie powinien następować po bloku catch; powinien znajdować się w bloku finally albo w samym bloku catch. Powinno to być normalne dobre rozwiązanie. Blok finally jest zazwyczaj preferowany, ponieważ uruchamia ten sam kod zarówno wtedy, gdy zgłaszany jest wyjątek, jak i gdy koniec bloku try jest napotykany. 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 finally, powinny być zawarte w 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 i nie trzyma blokady, CLR sam próbuje zakończyć 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 uchwytu do pliku w ścieżce kodu błędu i poczekasz na to, aż finalizator go wyczyści, 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.

Process-Wide modyfikowalny stan współużytkowany między domenami aplikacji należy wyeliminować lub użyć ograniczonego regionu wykonywania

Jak opisano we wprowadzeniu, bardzo trudno jest napisać zarządzany kod, który monitoruje stan współdzielony przez cały proces we wszystkich domenach aplikacji w niezawodny sposób. Stan współdzielony w całym procesie to dowolna struktura danych, która jest współdzielona między domenami aplikacji, zarówno w kodzie Win32, jak i wewnątrz środowiska CLR oraz w kodzie zarządzanym za pomocą zdalnej komunikacji. Modyfikowalny stan współdzielony jest bardzo trudny do poprawnego napisania w zarządzanym kodzie, a każdy statyczny stan współdzielony powinien być traktowany 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 nieudostępnionych, Thread obiektów, ciągów internowanych i niektórych ciągów udostępnionych w domenach aplikacji poprzez zdalne udostępnianie. Te blokady nie obejmują już całego procesu. 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 klas Mutex albo 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ą współdzielonego kodu we wszystkich domenach aplikacji również powodują 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 na AppDomain. Jeśli ktoś AppDomain przyjmuje blokadę Type na obiekcie, ten wątek zostanie nagle przerwany, nie zwolni blokady. Ta blokada może spowodować zawieszenie się 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, wykorzystaj właściwość InternalSyncObject, aby uzyskać obiekt, na którym można ustawić blokadę. Nie musisz używać właściwości, jeśli zainicjowano wewnętrzny obiekt synchronizacji w konstruktorze klasy. Kod inicjowania blokady z podwójnym sprawdzaniem 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 (this)

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, co sprawi, że cały AppDomain stanie się nieużywalny. Dobrym rozwiązaniem jest brak blokady na publicznie dostępnym obiekcie tego typu. Jednak blokada na pojedynczą kolekcję lub tablicę 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 na Type, MethodInfo, PropertyInfo, String, ValueType, Thread ani żadnego obiektu pochodzącego z MarshalByRefObject.

Usuń wywołania GC.KeepAlive

Istniejący kod w znacznej części albo nie używa KeepAlive tam, gdzie powinien, albo używa go, gdzie jest to nieodpowiednie. Po przekonwertowaniu na SafeHandle klasy nie muszą wywoływać KeepAlive, zakładając, że nie mają finalizatora, ale polegają na SafeHandle do finalizacji uchwytów systemu operacyjnego. Chociaż koszt wydajności utrzymania wywołania KeepAlive może być nieznaczny, postrzeganie, że wywołanie KeepAlive jest konieczne lub wystarczające do rozwiązania problemu związanego z czasem życia, który może już nie istnieć, utrudnia utrzymanie kodu. Jednak w przypadku korzystania z międzyoperacyjnych opakowań wywołujących CLR dla COM (RCWs), KeepAlive jest nadal wymagane przez kod.

Reguła analizy kodu

Usuń KeepAlive.

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 żądanie łącza służy do sprawdzania zgodności z wymaganiami modelu programowania, HostProtectionAttribute nie jest uprawnieniem bezpieczeństwa.

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ą nieszkodliwe.

  • 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 / Notatka

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 bezpośredniego wywołującego w ten sam sposób.

Więcej informacji na temat HPA można znaleźć w 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, które współdzielą stan, są zsynchronizowane 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 odsłania dowolny komponent HostProtectionResource, powinna być zidentyfikowana przez HPA, a nie tylko te, które korzystają z zasobów wpływających na SQL.

Nie blokuj bez ograniczeń czasowych w kodzie niezarządzanym

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 prymitywu synchronizacji Windows jest wyraźnym przykładem czegoś, czego nie możemy pozwolić. Najlepiej, jeśli to możliwe, należy unikać blokowania wywołania w gniazdie ReadFile — interfejs API systemu Windows powinien zapewniać mechanizm umożliwiający przekroczenie limitu czasu dla takiej operacji.

Każda metoda, która odwołuje się do kodu natywnego, powinna używać wywołania Win32 z sensownym, 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ć z limitem czasu; jednak kod musi zapewnić, że nigdy nie wywołuje ani CreateNamedPipe, ani WaitNamedPipe z NMPWAIT_WAIT_FOREVER. Ponadto może wystąpić nieoczekiwane blokowanie, nawet jeśli określono limit czasu. Wywołanie WriteFile na potoku anonimowym zablokuje do momentu zapisania wszystkich bajtów, co oznacza, że jeśli bufor zawiera nieprzeczytane dane, wywołanie WriteFile 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ń platformowych do WaitForSingleObject, WaitForSingleObjectEx, WaitForMultipleObjects, MsgWaitForMultipleObjects i MsgWaitForMultipleObjectsEx. Nie używaj NMPWAIT_WAIT_FOREVER.

Zidentyfikuj funkcje STA-Dependent

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, 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 dla wątków; mechanizm odśmiecania pamięci gwarantuje, że tylko jeden wątek uruchomi finalizator dla danej instancji obiektu. Jednak finalizatory muszą być zaprogramowane, aby uniknąć warunków wyścigu i zakleszczeń, gdy są uruchamiane równocześnie na wielu różnych instancjach 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 polegaj na finalizacji jako metodzie zapewniania bezpieczeństwa wątków. Nie używaj lokalnego magazynu wątku, ani zarządzanego, ani 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 do 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 pomocą CoTaskMemAlloc, a następnie inna biblioteka DLL zwalnia tę pamięć za pomocą CoTaskMemFree. 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)

Bloki przechwytujące wszystkie wyjątki zamiast jednego konkretnego wyjątku będą teraz także przechwytywać wyjątki asynchroniczne. Sprawdź każdy blok catch(Exception), wyszukując brak ważnego zwalniania zasobów lub kodu wycofania, który może być pominięty, a także potencjalnie niepoprawne zachowanie w samym bloku catch podczas obsługi ThreadAbortException, StackOverflowException lub OutOfMemoryException. Należy pamiętać, że ten kod może rejestrować lub zakładać, że może być tak, że widzi tylko pewne wyjątki lub że zawsze, gdy wystąpi wyjątek, niepowodzenie było spowodowane dokładnie jednym konkretnym powodem. Może być konieczne zaktualizowanie tych założeń w celu uwzględnienia ThreadAbortException.

Rozważ zmianę miejsc, które przechwytują wszystkie wyjątki, na przechwytywanie określonego typu wyjątku, który, jak oczekujesz, zostanie zgłoszony, na przykład FormatException z metod formatowania ciągów. Zapobiega to wykonaniu bloku catch w przypadku pojawienia się nieoczekiwanych wyjątków i pomoże upewnić się, że kod nie ukrywa błędów poprzez przechwytywanie takich wyjątków. Ogólna zasada, nigdy nie obsługuj wyjątku w kodzie biblioteki (kod wymagający 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ę błędu 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 catch{} jak 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 Fiber

Stosowanie zarządzanego magazynu lokalnego dla wątku działa poprawnie, ale nie można używać niezarządzanego magazynu lokalnego dla wątku ani zakładać, że kod zostanie uruchomiony ponownie w aktualnym 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 platformowego, ponieważ wymagają one, aby wątek systemu operacyjnego, który wprowadza blokadę, również ją zwolnił. 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 kwestii związanych z powiązaniami wątku.

Można bezpiecznie używać większości stanu w zarządzanym obiekcie Thread, w tym zarządzanej pamięci lokalnej wątku oraz bieżącej kultury UI 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łań platformy do TlsAlloc, TlsFree, TlsGetValue i TlsSetValue.

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, ImpersonateAnonymousToken, DdeImpersonateClient, ImpersonateDdeClientWindow, ImpersonateLoggedOnUser, ImpersonateNamedPipeClient, ImpersonateSelf, RpcImpersonateClient, RpcRevertToSelf, RpcRevertToSelfEx ani SetThreadToken.

Nie należy wywoływać Thread::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 spróbuje przejąć tę samą blokadę, wystąpi zakleszczenie. Suspend może zakłócać zabezpieczenia, ładowanie klas, komunikacja zdalna i odbicie obecnie.

Reguła analizy kodu

Nie wywołuj Suspend. Rozważ użycie rzeczywistego prymitywu synchronizacji, takiego jak Semaphore lub ManualResetEvent.

Ochrona operacji krytycznych przy użyciu ograniczonych regionów wykonywania i kontraktów niezawodności

Podczas wykonywania złożonej operacji, która aktualizuje współdzielony stan lub która musi deterministycznie zakończyć się pełnym powodzeniem albo pełnym niepowodzeniem, upewnij się, że jest chroniona przez ograniczony region wykonywania (CER). Zapewnia to, że kod jest uruchamiany w każdej sytuacji, nawet w przypadku gwałtownego przerwania wątku lub gwałtownego zakończenia ładowania AppDomain.

CER to konkretny try/finally blok bezpośrednio poprzedzony wywołaniem metody PrepareConstrainedRegions.

Instrukcja ta nakazuje kompilatorowi JIT przygotowanie całego kodu w bloku finally przed uruchomieniem bloku try. Gwarantuje to, że kod w bloku finally zostanie skompilowany i 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, aby uzyskać wersję CER, która dodatkowo obsługuje przepełnienia stosu dla wyjątkowo głębokiego kodu.

Zobacz także