Aanbevolen procedures voor betrouwbaarheid
De volgende betrouwbaarheidsregels zijn gericht op SQL Server; Ze zijn echter ook van toepassing op elke servertoepassing op basis van een host. Het is uiterst belangrijk dat servers zoals SQL Server geen resources lekken en niet worden neergehaald. Dit kan echter niet worden gedaan door back-outcode te schrijven voor elke methode die de status van een object wijzigt. Het doel is niet om 100 procent betrouwbare beheerde code te schrijven die herstelt van fouten op elke locatie met back-outcode. Dat zou een ontmoedigende taak zijn met weinig kans op succes. De Common Language Runtime (CLR) kan niet eenvoudig voldoende garanties bieden voor beheerde code om het schrijven van perfecte code mogelijk te maken. In tegenstelling tot ASP.NET maakt SQL Server gebruik van slechts één proces dat niet kan worden gerecycled zonder dat een database gedurende een onacceptabele lange tijd uitvalt.
Met deze zwakkere garanties en uitvoering in één proces, is betrouwbaarheid gebaseerd op afsluitthreads of het recyclen van toepassingsdomeinen wanneer dat nodig is en het nemen van voorzorgsmaatregelen om ervoor te zorgen dat besturingssysteemresources, zoals ingangen of geheugen, niet worden gelekt. Zelfs met deze eenvoudigere betrouwbaarheidsbeperking is er nog steeds een aanzienlijke betrouwbaarheidsvereiste:
Nooit besturingssysteembronnen lekken.
Identificeer alle beheerde vergrendelingen in alle formulieren voor de CLR.
Breek nooit de gedeelde status van het domein tussen toepassingen, waardoor AppDomain recycling soepel kan functioneren.
Hoewel het theoretisch mogelijk is om beheerde code te schrijven voor het verwerken ThreadAbortException, StackOverflowExceptionen OutOfMemoryException uitzonderingen, is het onredelijk dat ontwikkelaars dergelijke robuuste code in een hele toepassing schrijven. Om die reden leiden out-of-band-uitzonderingen ertoe dat de uit te voeren thread wordt beëindigd; en als de beëindigde thread de gedeelde status heeft bewerkt, die kan worden bepaald door of de thread een vergrendeling bevat, wordt de AppDomain thread verwijderd. Wanneer een methode die de gedeelde status bewerkt, wordt beëindigd, is de status beschadigd omdat het niet mogelijk is om betrouwbare back-outcode te schrijven voor updates naar de gedeelde status.
In .NET Framework versie 2.0 is SQL Server de enige host die betrouwbaarheid vereist. Als uw assembly wordt uitgevoerd op SQL Server, moet u de betrouwbaarheid voor elk onderdeel van die assembly uitvoeren, zelfs als er specifieke functies zijn die zijn uitgeschakeld bij het uitvoeren in de database. Dit is vereist omdat de codeanalyse-engine code op assemblyniveau onderzoekt en geen onderscheid kan maken tussen uitgeschakelde code. Een andere sql Server-programmeeroverweging is dat SQL Server alles in één proces uitvoert en AppDomain recycling wordt gebruikt voor het opschonen van alle resources, zoals geheugen- en besturingssysteemgrepen.
U kunt niet afhankelijk zijn van finalizers of destructors of try/finally
blokken voor back-outcode. Ze kunnen worden onderbroken of niet worden aangeroepen.
Asynchrone uitzonderingen kunnen worden gegenereerd op onverwachte locaties, mogelijk elke computerinstructie: ThreadAbortException, StackOverflowExceptionen OutOfMemoryException.
Beheerde threads zijn niet noodzakelijkerwijs Win32-threads in SQL; ze kunnen vezels zijn.
Het onveranderbare gedeelde domein van procesbrede of cross-application-domein is uiterst moeilijk te wijzigen en moet waar mogelijk worden vermeden.
Onvoldoende geheugenproblemen zijn niet zeldzaam in SQL Server.
Als bibliotheken die worden gehost in SQL Server hun gedeelde status niet correct bijwerken, is de kans groot dat de code pas wordt hersteld nadat de database opnieuw is opgestart. In sommige extreme gevallen kan dit ertoe leiden dat het SQL Server-proces mislukt, waardoor de database opnieuw wordt opgestart. Door de database opnieuw op te starten, kan een website worden uitgeschakeld of de bedrijfsactiviteiten worden beïnvloed, waardoor de beschikbaarheid wordt beïnvloed. Een traag lek van besturingssysteembronnen, zoals geheugen of ingangen, kan ertoe leiden dat de server uiteindelijk mislukt bij het toewijzen van ingangen zonder dat er herstel mogelijk is, of dat de server langzaam kan afnemen in de prestaties en de beschikbaarheid van de toepassing van een klant vermindert. We willen deze scenario's duidelijk vermijden.
Best practice-regels
De inleiding was gericht op wat de codebeoordeling voor de beheerde code die op de server wordt uitgevoerd, moet vangen om de stabiliteit en betrouwbaarheid van het framework te vergroten. Al deze controles zijn in het algemeen een goede gewoonte en een absolute must op de server.
In het gezicht van een dode vergrendeling of resourcebeperking zal SQL Server een thread afbreken of een AppDomain. Als dit gebeurt, wordt alleen back-outcode in een beperkte uitvoeringsregio (CER) gegarandeerd uitgevoerd.
SafeHandle gebruiken om lekken van resources te voorkomen
In het geval van een AppDomain uitlading kunt u niet afhankelijk zijn van finally
blokken of finalizers die worden uitgevoerd, dus het is belangrijk om alle toegang tot besturingssysteembronnen te abstraheren via de SafeHandle klasse in plaats IntPtrvan , HandleRefof vergelijkbare klassen. Hierdoor kan de CLR de ingangen die u gebruikt, bijhouden en sluiten, zelfs in het AppDomain geval van scheuren. SafeHandle maakt gebruik van een kritieke finalizer die altijd door de CLR wordt uitgevoerd.
De ingang van het besturingssysteem wordt opgeslagen in de veilige ingang vanaf het moment dat het wordt gemaakt tot het moment dat het wordt vrijgegeven. Er is geen venster waarin een ThreadAbortException kan optreden om een handgreep te lekken. Daarnaast verwijst platformaanroep naar het aantal ingangen, waarmee de levensduur van de ingang wordt bijgehouden, waardoor een beveiligingsprobleem met een racevoorwaarde wordt voorkomen tussen Dispose
en een methode die momenteel gebruikmaakt van de ingang.
De meeste klassen die momenteel een finalizer hebben om een greep van een besturingssysteem op te schonen, hebben de finalizer niet meer nodig. In plaats daarvan bevindt de finalizer zich in de SafeHandle afgeleide klasse.
Houd er rekening mee dat SafeHandle dit geen vervanging is voor IDisposable.Dispose. Er zijn nog steeds potentiële resourceconflicten en prestatievoordelen om besturingssysteemresources expliciet te verwijderen. U hoeft alleen maar te beseffen dat finally
blokken die expliciet resources verwijderen, mogelijk niet worden uitgevoerd tot voltooiing.
SafeHandle hiermee kunt u uw eigen ReleaseHandle methode implementeren waarmee het werk wordt uitgevoerd om de ingang vrij te maken, zoals het doorgeven van de status aan een besturingssysteem voor het vrijmaken van routines of het vrijmaken van een set ingangen in een lus. De CLR garandeert dat deze methode wordt uitgevoerd. Het is de verantwoordelijkheid van de auteur van de ReleaseHandle implementatie om ervoor te zorgen dat de ingang in alle omstandigheden wordt vrijgegeven. Als u dit niet doet, wordt de ingang gelekt, wat vaak resulteert in het lekken van systeemeigen resources die aan de ingang zijn gekoppeld. Daarom is het van cruciaal belang om afgeleide klassen zodanig te structuren SafeHandle dat de ReleaseHandle implementatie niet de toewijzing vereist van resources die mogelijk niet beschikbaar zijn tijdens aanroeptijd. Houd er rekening mee dat het is toegestaan om methoden aan te roepen die kunnen mislukken binnen de implementatie van ReleaseHandle mits uw code dergelijke fouten kan verwerken en het contract kan voltooien om de systeemeigen ingang vrij te geven. Voor foutopsporingsdoeleinden ReleaseHandle heeft u een Boolean retourwaarde die kan worden ingesteld false
op als er een onherstelbare fout optreedt, waardoor de release van de resource wordt voorkomen. Als u dit doet, wordt de releaseHandleFailed MDA geactiveerd, indien ingeschakeld, om te helpen bij het identificeren van het probleem. Het heeft geen invloed op de runtime op een andere manier; ReleaseHandle wordt niet opnieuw aangeroepen voor dezelfde resource en daarom wordt de ingang gelekt.
SafeHandle is niet geschikt in bepaalde contexten. Omdat de ReleaseHandle methode kan worden uitgevoerd op een GC finalizer-thread, mogen alle ingangen die op een bepaalde thread moeten worden vrijgemaakt, niet worden verpakt in een SafeHandle.
Runtime aanroepbare wrappers (RCW's) kunnen worden opgeschoond door de CLR zonder extra code. Voor code die gebruikmaakt van platformaanroepen en behandelt een COM-object als een IUnknown*
of een IntPtr, moet de code worden herschreven om een RCW te gebruiken. SafeHandle is mogelijk niet voldoende voor dit scenario vanwege de mogelijkheid van een onbeheerde releasemethode die terugroept naar beheerde code.
Codeanalyseregel
Hiermee SafeHandle kunt u besturingssysteembronnen inkapselen. Gebruik of velden van het type IntPtrnietHandleRef.
Zorg ervoor dat finalizers niet hoeven te worden uitgevoerd om te voorkomen dat besturingssysteembronnen worden gelekt
Controleer uw finalizers zorgvuldig om ervoor te zorgen dat zelfs als ze niet worden uitgevoerd, een kritieke besturingssysteemresource niet wordt gelekt. In tegenstelling tot een normale AppDomain uitlading wanneer de toepassing wordt uitgevoerd in een stabiele toestand of wanneer een server zoals SQL Server wordt afgesloten, worden objecten niet voltooid tijdens een plotselinge AppDomain uitlading. Zorg ervoor dat resources niet worden gelekt in het geval van een plotselinge uitlading, omdat de juistheid van een toepassing niet kan worden gegarandeerd, maar de integriteit van de server moet worden onderhouden door geen resources te lekken. Gebruik SafeHandle dit om alle besturingssysteembronnen vrij te maken.
Zorg ervoor dat ten slotte componenten niet hoeven te worden uitgevoerd om te voorkomen dat besturingssysteembronnen worden gelekt
finally
-componenten worden niet gegarandeerd buiten CER's uitgevoerd, waardoor bibliotheekontwikkelaars niet afhankelijk zijn van code binnen een finally
blok om onbeheerde resources vrij te maken. Het gebruik SafeHandle is de aanbevolen oplossing.
Codeanalyseregel
Gebruiken SafeHandle voor het opschonen van besturingssysteemresources in plaats van Finalize
. Niet gebruiken IntPtr; gebruiken SafeHandle om resources in te kapselen. Als de laatste component moet worden uitgevoerd, plaatst u deze in een CER.
Alle vergrendelingen moeten via bestaande beheerde vergrendelingscode gaan
De CLR moet weten wanneer de code zich in een vergrendeling bevindt, zodat deze weet de thread te afbreken in plaats van alleen de thread af te AppDomain splitsen. Het afbreken van de thread kan gevaarlijk zijn omdat de gegevens die door de thread worden uitgevoerd, in een inconsistente toestand kunnen blijven. Daarom moet het geheel AppDomain worden gerecycled. De gevolgen van het niet identificeren van een vergrendeling kunnen impasses of onjuiste resultaten zijn. Gebruik de methoden BeginCriticalRegion en EndCriticalRegion om vergrendelingsregio's te identificeren. Ze zijn statische methoden voor de Thread klasse die alleen van toepassing zijn op de huidige thread, waardoor de ene thread het aantal vergrendelingen van een andere thread niet kan bewerken.
Enter en Exit deze CLR-melding ingebouwd hebben, zodat het gebruik ervan wordt aanbevolen en het gebruik van de vergrendelingsinstructie, die deze methoden gebruikt.
Andere vergrendelingsmechanismen, zoals kringvergrendelingen, en AutoResetEvent moeten deze methoden aanroepen om de CLR op de hoogte te stellen dat er een kritieke sectie wordt ingevoerd. Deze methoden nemen geen vergrendelingen; ze informeren de CLR dat code wordt uitgevoerd in een kritieke sectie en dat het afbreken van de thread de gedeelde status inconsistent kan laten. Als u uw eigen vergrendelingstype hebt gedefinieerd, zoals een aangepaste ReaderWriterLock klasse, gebruikt u deze methoden voor het tellen van vergrendelingen.
Codeanalyseregel
Alle vergrendelingen markeren en identificeren met en BeginCriticalRegionEndCriticalRegion. Gebruik niet CompareExchange, Incrementen Decrement in een lus. Voer geen platform aan van de Win32-varianten van deze methoden. Gebruik niet Sleep in een lus. Gebruik geen vluchtige velden.
Opschoningscode moet zich in een laatste of een catch-blok, niet na een catch
Opschoningscode mag nooit een catch
blok volgen; deze moet zich in een finally
of in het catch
blok zelf bevindt. Dit moet een normale goede gewoonte zijn. Een finally
blok heeft doorgaans de voorkeur omdat dezelfde code wordt uitgevoerd, zowel wanneer er een uitzondering wordt gegenereerd als wanneer het einde van het try
blok normaal wordt aangetroffen. In het geval van een onverwachte uitzondering die wordt gegenereerd, bijvoorbeeld een ThreadAbortException, wordt de opschoningscode niet uitgevoerd. Onbeheerde resources die u in een finally
product opschoont, moeten in het ideale geval worden verpakt om SafeHandle lekken te voorkomen. Let op: het C# using
-trefwoord kan effectief worden gebruikt om objecten, inclusief ingangen, te verwijderen.
Hoewel AppDomain recycling resources op de finalizerthread kan opschonen, is het nog steeds belangrijk om opschooncode op de juiste plaats te plaatsen. Als een thread een asynchrone uitzondering ontvangt zonder een vergrendeling vast te houden, probeert de CLR de thread zelf te beëindigen zonder de AppDomainthread te hoeven recyclen. Ervoor zorgen dat resources eerder worden opgeschoond in plaats van later, helpt door meer resources beschikbaar te maken en door de levensduur beter te beheren. Als u een ingang niet expliciet sluit naar een bestand in een bepaald pad met foutcodes, wacht u totdat de SafeHandle finalizer het opschoont, de volgende keer dat de code wordt uitgevoerd, kan het mislukken om toegang te krijgen tot hetzelfde bestand als de finalizer nog niet is uitgevoerd. Om deze reden zorgt u ervoor dat de opschoningscode bestaat en correct werkt, zodat u beter en sneller herstelt van fouten, ook al is het niet strikt noodzakelijk.
Codeanalyseregel
Code opschonen nadat catch
deze in een finally
blok moet staan. Plaats oproepen om te verwijderen in een definitief blok. catch
blokken moeten eindigen in een gooi of opnieuw worden gegooid. Hoewel er uitzonderingen zijn, zoals code die detecteert of er een netwerkverbinding tot stand kan worden gebracht waar u een groot aantal uitzonderingen kunt krijgen, moet elke code die het vangen van een aantal uitzonderingen onder normale omstandigheden vereist, een indicatie geven dat de code moet worden getest om te zien of deze slaagt.
Procesbrede onveranderbare gedeelde status tussen toepassingsdomeinen moet worden geëlimineerd of een beperkte uitvoeringsregio gebruiken
Zoals beschreven in de inleiding, kan het heel moeilijk zijn om beheerde code te schrijven die de procesbrede gedeelde status in toepassingsdomeinen op een betrouwbare manier bewaakt. Procesbrede gedeelde status is een soort gegevensstructuur die wordt gedeeld tussen toepassingsdomeinen, in Win32-code, binnen de CLR of in beheerde code met behulp van externe communicatie. Elke onveranderbare gedeelde status is erg moeilijk om correct te schrijven in beheerde code en elke statische gedeelde status kan alleen met grote zorg worden uitgevoerd. Als u een procesbrede of machinebrede gedeelde status hebt, kunt u deze verwijderen of de gedeelde status beveiligen met behulp van een beperkte uitvoeringsregio (CER). Houd er rekening mee dat een bibliotheek met een gedeelde status die niet wordt geïdentificeerd en gecorrigeerd, een host, zoals SQL Server, kan veroorzaken dat een schone AppDomain lossing vastloopt.
Als code gebruikmaakt van een COM-object, vermijdt u het delen van dat COM-object tussen toepassingsdomeinen.
Vergrendelingen werken niet procesbreed of tussen toepassingsdomeinen.
In het verleden Enter zijn de vergrendelingsinstructie gebruikt om globale procesvergrendelingen te maken. Dit gebeurt bijvoorbeeld bij het vergrendelen van AppDomain agile-klassen, zoals Type exemplaren van niet-gedeelde assembly's, Thread objecten, interne tekenreeksen en sommige tekenreeksen die worden gedeeld in toepassingsdomeinen met behulp van externe communicatie. Deze vergrendelingen zijn niet langer procesbreed. Als u de aanwezigheid van een procesbrede domeinvergrendeling wilt identificeren, moet u bepalen of de code in de vergrendeling gebruikmaakt van een externe, persistente resource, zoals een bestand op schijf of mogelijk een database.
Houd er rekening mee dat het maken van een vergrendeling binnen een AppDomain vergrendeling problemen kan veroorzaken als de beveiligde code gebruikmaakt van een externe resource, omdat die code tegelijkertijd kan worden uitgevoerd in meerdere toepassingsdomeinen. Dit kan een probleem zijn bij het schrijven naar één logboekbestand of het binden aan een socket voor het hele proces. Deze wijzigingen betekenen dat er geen eenvoudige manier is, met behulp van beheerde code, om een proces-globale vergrendeling op te halen, behalve het gebruik van een benoemde Mutex of Semaphore instantie. Maak code die niet tegelijkertijd in twee toepassingsdomeinen wordt uitgevoerd of gebruik de Mutex of Semaphore klassen. Als bestaande code niet kan worden gewijzigd, gebruikt u geen Win32 met de naam mutex om deze synchronisatie te bereiken, omdat het uitvoeren in de glasvezelmodus betekent dat u niet kunt garanderen dat dezelfde besturingssysteemthread een mutex krijgt en vrijgeeft. U moet de beheerde Mutex klasse, of een benoemde ManualResetEvent, AutoResetEventof een Semaphore om de codevergrendeling te synchroniseren op een manier waarvan de CLR op de hoogte is in plaats van de vergrendeling te synchroniseren met behulp van niet-beheerde code.
Vergrendeling voorkomen(typeof(MyType))
Privé- en openbare Type objecten in gedeelde assembly's met slechts één kopie van de code die in alle toepassingsdomeinen wordt gedeeld, hebben ook problemen. Voor gedeelde assembly's is er slechts één exemplaar van een Type per proces, wat betekent dat meerdere toepassingsdomeinen precies hetzelfde Type exemplaar delen. Het nemen van een vergrendeling op een Type exemplaar neemt een vergrendeling die van invloed is op het hele proces, niet alleen op de AppDomain. Als iemand AppDomain een vergrendeling op een Type object neemt, wordt die thread plotseling afgebroken, wordt de vergrendeling niet losgemaakt. Deze vergrendeling kan ertoe leiden dat andere toepassingsdomeinen een impasse veroorzaken.
Een goede manier om vergrendelingen in statische methoden op te nemen, omvat het toevoegen van een statisch intern synchronisatieobject aan de code. Dit kan worden geïnitialiseerd in de klasseconstructor als er een aanwezig is, maar als dat niet zo is, kan deze als volgt worden geïnitialiseerd:
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;
}
}
Wanneer u vervolgens een vergrendeling neemt, gebruikt u de InternalSyncObject
eigenschap om een object te verkrijgen dat moet worden vergrendeld. U hoeft de eigenschap niet te gebruiken als u het interne synchronisatieobject in uw klasseconstructor hebt geïnitialiseerd. De initialisatiecode voor dubbele controle van vergrendeling moet er als volgt uitzien:
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;
}
}
Een opmerking over vergrendeling (dit)
Het is over het algemeen acceptabel om een vergrendeling te nemen op een afzonderlijk object dat openbaar toegankelijk is. Als het object echter een singleton-object is dat ertoe kan leiden dat een volledig subsysteem een impasse veroorzaakt, kunt u ook het bovenstaande ontwerppatroon gebruiken. Een vergrendeling op het ene SecurityManager object kan bijvoorbeeld leiden tot een impasse binnen het AppDomain maken van het hele AppDomain onbruikbare object. Het is een goede gewoonte om geen vergrendeling te nemen op een openbaar toegankelijk object van dit type. Een vergrendeling van een afzonderlijke verzameling of matrix zou in het algemeen echter geen probleem moeten opleveren.
Codeanalyseregel
Neem geen vergrendelingen op typen die kunnen worden gebruikt in toepassingsdomeinen of die geen sterk identiteitsgevoel hebben. Roep geen aan op Enter een Type, , PropertyInfoMethodInfo, String, , ValueTypeof Threadeen object dat is afgeleid van MarshalByRefObject.
Verwijder GC. KeepAlive-aanroepen
Een aanzienlijke hoeveelheid bestaande code wordt niet gebruikt wanneer deze moet worden gebruikt KeepAlive of gebruikt wanneer deze niet geschikt is. Na het converteren naar SafeHandle, hoeven klassen niet aan te roepen KeepAlive, ervan uitgaande dat ze geen finalizer hebben, maar afhankelijk zijn SafeHandle van het voltooien van de grepen van het besturingssysteem. Hoewel de prestatiekosten van het behouden van een aanroep KeepAlive te verwaarloosbaar zijn, is de perceptie dat een aanroep KeepAlive noodzakelijk of voldoende is om een levensduurprobleem op te lossen dat mogelijk niet meer bestaat, de code moeilijker te onderhouden. Wanneer u echter de AANROEPBARE WRAPPERS (RCW's) van COM gebruikt, KeepAlive is deze nog steeds vereist voor code.
Codeanalyseregel
Verwijderen KeepAlive.
Het kenmerk HostProtection gebruiken
De HostProtectionAttribute (HPA) biedt het gebruik van declaratieve beveiligingsacties om vereisten voor hostbeveiliging te bepalen, zodat de host kan voorkomen dat zelfs volledig vertrouwde code bepaalde methoden aanroept die ongepast zijn voor de opgegeven host, zoals Exit of Show voor SQL Server.
De HPA is alleen van invloed op niet-beheerde toepassingen die als host fungeren voor de algemene taalruntime en hostbeveiliging implementeren, zoals SQL Server. Wanneer deze wordt toegepast, resulteert de beveiligingsactie in het maken van een koppelingsvraag op basis van de hostbronnen die de klasse of methode beschikbaar maakt. Als de code wordt uitgevoerd in een clienttoepassing of op een server die niet met een host is beveiligd, verdampt het kenmerk "verdampt"; het wordt niet gedetecteerd en daarom niet toegepast.
Belangrijk
Het doel van dit kenmerk is het afdwingen van richtlijnen voor hostspecifieke programmeermodellen, niet van beveiligingsgedrag. Hoewel een koppelingsvraag wordt gebruikt om te controleren op naleving van de vereisten van het programmeermodel, is dit HostProtectionAttribute geen beveiligingsmachtiging.
Als de host geen programmeermodelvereisten heeft, treden de koppelingsvereisten niet op.
Dit kenmerk identificeert het volgende:
Methoden of klassen die niet passen bij het hostprogrammeermodel, maar die anders goedaardig zijn.
Methoden of klassen die niet passen bij het hostprogrammeermodel en kunnen leiden tot een destabilisatie van door de server beheerde gebruikerscode.
Methoden of klassen die niet passen bij het hostprogrammeermodel en kunnen leiden tot een destabilisatie van het serverproces zelf.
Notitie
Als u een klassebibliotheek maakt die moet worden aangeroepen door toepassingen die kunnen worden uitgevoerd in een met host beveiligde omgeving, moet u dit kenmerk toepassen op leden die resourcecategorieën beschikbaar maken HostProtectionResource . De leden van de .NET Framework-klassebibliotheek met dit kenmerk zorgen ervoor dat alleen de directe beller wordt gecontroleerd. Uw bibliotheeklid moet ook een controle van de directe beller op dezelfde manier veroorzaken.
Meer informatie over HPA vindt u in HostProtectionAttribute.
Codeanalyseregel
Voor SQL Server moeten alle methoden die worden gebruikt om synchronisatie of threading te introduceren, worden geïdentificeerd met de HPA. Dit omvat methoden die de status delen, worden gesynchroniseerd of externe processen beheren. De HostProtectionResource waarden die van invloed zijn op SQL Server zijn SharedState, Synchronizationen ExternalProcessMgmt. Elke methode die beschikbaar wordt gemaakt HostProtectionResource , moet echter worden geïdentificeerd door een HPA, niet alleen de methoden die gebruikmaken van resources die van invloed zijn op SQL.
Niet voor onbepaalde tijd blokkeren in onbeheerde code
Blokkeren in niet-beheerde code in plaats van in beheerde code kan leiden tot een Denial of Service-aanval omdat de CLR de thread niet kan afbreken. Een geblokkeerde draad voorkomt dat de CLR de AppDomain, in ieder geval zonder extreem onveilige bewerkingen uit te lossen. Blokkeren met behulp van een Windows-synchronisatieprimitief is een duidelijk voorbeeld van iets dat we niet kunnen toestaan. Blokkeren in een aanroep naar ReadFile
een socket moet indien mogelijk worden vermeden. In het ideale geval moet de Windows-API een mechanisme bieden voor een dergelijke time-out voor een bewerking.
Elke methode die systeemeigen aanroept, moet idealiter een Win32-aanroep gebruiken met een redelijke, eindige time-out. Als de gebruiker de time-out mag opgeven, mag de gebruiker geen oneindige time-out opgeven zonder een specifieke beveiligingsmachtiging. Als een methode langer dan ~10 seconden wordt geblokkeerd, moet u een versie gebruiken die time-outs ondersteunt of als u aanvullende CLR-ondersteuning nodig hebt.
Hier volgen enkele voorbeelden van problematische API's. Pijpen (zowel anoniem als benoemd) kunnen worden gemaakt met een time-out; Code moet er echter voor zorgen dat deze nooit aanroept CreateNamedPipe
en WaitNamedPipe
niet met NMPWAIT_WAIT_FOREVER. Bovendien kan er onverwachte blokkering zijn, zelfs als er een time-out is opgegeven. Als u een anonieme pijp aanroept WriteFile
, wordt geblokkeerd totdat alle bytes zijn geschreven, wat betekent dat als de buffer ongelezen gegevens bevat, de WriteFile
aanroep wordt geblokkeerd totdat de lezer ruimte vrij heeft in de buffer van de pijp. Sockets moeten altijd gebruikmaken van een API die een time-outmechanisme respecteert.
Codeanalyseregel
Blokkeren zonder time-out in onbeheerde code is een Denial of Service-aanval. Voer geen aanroepen van het platform uit naar WaitForSingleObject
, WaitForSingleObjectEx
, WaitForMultipleObjects
en MsgWaitForMultipleObjects
.MsgWaitForMultipleObjectsEx
Gebruik geen NMPWAIT_WAIT_FOREVER.
Sta-afhankelijke functies identificeren
Identificeer code die gebruikmaakt van COM single threaded apartments (STA's). STA's zijn uitgeschakeld in het SQL Server-proces. Functies die afhankelijk CoInitialize
zijn van, zoals prestatiemeteritems of het Klembord, moeten worden uitgeschakeld in SQL Server.
Zorg ervoor dat finalizers vrij zijn van synchronisatieproblemen
Er kunnen meerdere finalizer-threads bestaan in toekomstige versies van .NET Framework, wat betekent dat de finalizers voor verschillende exemplaren van hetzelfde type tegelijkertijd worden uitgevoerd. Ze hoeven niet volledig thread veilig te zijn; de garbagecollector garandeert dat slechts één thread de finalizer uitvoert voor een bepaald objectexemplaar. De finalizers moeten echter worden gecodeerd om racevoorwaarden en impasses te voorkomen bij gelijktijdig uitvoeren op meerdere verschillende objectexemplaren. Wanneer u een externe status gebruikt, zoals schrijven naar een logboekbestand, moeten threadingproblemen in een finalizer worden afgehandeld. Vertrouw niet op de voltooien om threadveiligheid te bieden. Gebruik geen lokale threadopslag, beheerd of systeemeigen, om de status op te slaan op de finalizer-thread.
Codeanalyseregel
Finalizers moeten vrij zijn van synchronisatieproblemen. Gebruik geen statische veranderlijke status in een finalizer.
Vermijd indien mogelijk onbeheerd geheugen
Onbeheerd geheugen kan worden gelekt, net zoals een besturingssysteemhandgreep. Probeer indien mogelijk geheugen op de stack te gebruiken met behulp van stackalloc of een vastgemaakt beheerd object, zoals de vaste instructie of een GCHandle byte[]. Uiteindelijk GC worden deze opgeschoond. Als u echter onbeheerd geheugen moet toewijzen, kunt u overwegen een klasse te gebruiken die is afgeleid van SafeHandle om de geheugentoewijzing te verpakken.
Houd er rekening mee dat er ten minste één geval is waar SafeHandle niet voldoende is. Voor COM-methode-aanroepen die geheugen toewijzen of vrijmaken, is het gebruikelijk dat één DLL geheugen toewijst via CoTaskMemAlloc
vervolgens een andere DLL die geheugen vrijgeeft met CoTaskMemFree
. Het gebruik SafeHandle op deze plaatsen zou ongepast zijn omdat het probeert de levensduur van het onbeheerde geheugen te koppelen aan de levensduur van de SafeHandle in plaats van de andere DLL-controle de levensduur van het geheugen toe te staan.
Bekijk alle toepassingen van catch(Exception)
Catch-blokken die alle uitzonderingen vangen in plaats van één specifieke uitzondering, vangen nu ook de asynchrone uitzonderingen op. Bekijk elk catch(Exception)-blok, op zoek naar geen belangrijke resource die code vrijgeeft of back-outcode die kan worden overgeslagen, evenals mogelijk onjuist gedrag binnen het catch-blok zelf voor het verwerken van een ThreadAbortException, StackOverflowExceptionof OutOfMemoryException. Houd er rekening mee dat het mogelijk is dat deze code logboekregistratie maakt of bepaalde veronderstellingen maakt dat deze alleen bepaalde uitzonderingen kan zien, of dat wanneer er een uitzondering optreedt, deze om precies één bepaalde reden is mislukt. Deze veronderstellingen moeten mogelijk worden bijgewerkt om op te nemen ThreadAbortException.
Overweeg alle plaatsen te wijzigen die alle uitzonderingen ondervangen om een specifiek type uitzondering te vangen dat u verwacht, zoals een FormatException tekenreeksopmaakmethode. Dit voorkomt dat het catch-blok wordt uitgevoerd op onverwachte uitzonderingen en zorgt ervoor dat de code geen fouten verbergt door onverwachte uitzonderingen te ondervangen. In de algemene regel wordt nooit een uitzondering in bibliotheekcode verwerkt (code die vereist dat u een uitzondering ondervangt, kan duiden op een ontwerpfout in de code die u aanroept). In sommige gevallen wilt u mogelijk een uitzondering onderscheppen en een ander uitzonderingstype genereren om meer gegevens op te geven. Gebruik geneste uitzonderingen in dit geval, waarbij de werkelijke oorzaak van de fout in de InnerException eigenschap van de nieuwe uitzondering wordt opgeslagen.
Codeanalyseregel
Bekijk alle catchblokken in beheerde code die alle objecten vangen of alle uitzonderingen ondervangen. In C# betekent dit dat u zowel catch
{}catch(Exception)
{}als . Overweeg het uitzonderingstype zeer specifiek te maken of controleer de code om ervoor te zorgen dat deze niet slecht werkt als er een onverwacht uitzonderingstype wordt onderschept.
Neem niet aan dat een beheerde thread een Win32-thread is: het is een glasvezel
Het gebruik van lokale opslag voor beheerde threads werkt wel, maar u kunt geen lokale threadopslag gebruiken of ervan uitgaan dat de code opnieuw wordt uitgevoerd op de huidige thread van het besturingssysteem. Wijzig geen instellingen, zoals de landinstelling van de thread. Roep niet InitializeCriticalSection
aan of CreateMutex
via platform-aanroepen omdat ze de besturingssysteemthread vereisen die een vergrendeling binnenkomt, ook de vergrendeling afsluiten. Omdat dit niet het geval is bij het gebruik van vezels, kunnen Win32 kritieke secties en mutexes niet rechtstreeks in SQL worden gebruikt. Houd er rekening mee dat de beheerde Mutex klasse deze problemen met threadaffiniteit niet afhandelt.
U kunt de meeste status veilig gebruiken voor een beheerd Thread object, waaronder de lokale opslag van beheerde threads en de huidige UI-cultuur van de thread. U kunt ook de ThreadStaticAttributewaarde van een bestaande statische variabele alleen toegankelijk maken door de huidige beheerde thread (dit is een andere manier om lokale glasvezelopslag in de CLR uit te voeren). Om redenen van programmeermodellen kunt u de huidige cultuur van een thread niet wijzigen bij het uitvoeren in SQL.
Codeanalyseregel
SQL Server wordt uitgevoerd in de glasvezelmodus; gebruik geen lokale threadopslag. Vermijd aanroepen van platformen naar TlsAlloc
, TlsFree
en TlsGetValue
TlsSetValue.
Imitatie van SQL Server afhandelen
Omdat imitatie op threadniveau werkt en SQL kan worden uitgevoerd in de glasvezelmodus, mag beheerde code geen gebruikers imiteren en mag deze niet aanroepen RevertToSelf
.
Codeanalyseregel
Laat SQL Server imitatie afhandelen. Niet gebruikenRevertToSelf
, , , , DdeImpersonateClient
, ImpersonateDdeClientWindow
, ImpersonateLoggedOnUser
, , ImpersonateNamedPipeClient
, ImpersonateSelf
, , RpcImpersonateClient
, RpcRevertToSelf
, , of SetThreadToken
RpcRevertToSelfEx
. ImpersonateAnonymousToken
Gesprek niet aanroepen::Onderbreken
De mogelijkheid om een thread op te schorten, kan een eenvoudige bewerking lijken, maar kan impasses veroorzaken. Als een thread met een vergrendeling wordt onderbroken door een tweede thread en de tweede thread probeert dezelfde vergrendeling te nemen, treedt er een impasse op. Suspend kan momenteel invloed hebben op beveiliging, klasse laden, externe communicatie en reflectie.
Codeanalyseregel
Bel niet Suspend. Overweeg in plaats daarvan een echte synchronisatie primitief te gebruiken, zoals een Semaphore of ManualResetEvent .
Kritieke bewerkingen beveiligen met beperkte uitvoeringsregio's en betrouwbaarheidscontracten
Wanneer u een complexe bewerking uitvoert waarmee een gedeelde status wordt bijgewerkt of die deterministisch volledig of volledig moet slagen, moet u ervoor zorgen dat deze wordt beveiligd door een beperkte uitvoeringsregio (CER). Dit garandeert dat de code in elk geval wordt uitgevoerd, zelfs een abrupte thread wordt afgebroken of een abrupte AppDomain uitlading.
Een CER is een bepaald try/finally
blok direct voorafgegaan door een aanroep naar PrepareConstrainedRegions.
Hierdoor wordt de Just-In-Time-compiler geïnstrueerd om alle code in het laatste blok voor te bereiden voordat het try
blok wordt uitgevoerd. Dit garandeert dat de code in het laatste blok is gebouwd en in alle gevallen wordt uitgevoerd. Het is niet ongebruikelijk dat een CER een leeg try
blok heeft. Het gebruik van een CER-beveiliging tegen asynchrone threads wordt afgebroken en uitzonderingen buiten het geheugen. Zie ExecuteCodeWithGuaranteedCleanup voor een vorm van een CER die bovendien stack-overloop afhandelt voor zeer diepe code.