Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
As regras de confiabilidade a seguir são orientadas ao SQL Server; no entanto, eles também se aplicam a qualquer aplicativo de servidor baseado em host. É extremamente importante que servidores como o SQL Server não vazem recursos e não sejam derrubados. No entanto, isso não pode ser feito escrevendo código de backup para cada método que altera o estado de um objeto. A meta é não gravar código gerenciado 100 por cento confiável que se recuperará de erros em todos os locais com o código de recuo. Essa seria uma tarefa assustadora com pouca chance de sucesso. O CLR (Common Language Runtime) não pode facilmente fornecer garantias fortes o suficiente para o código gerenciado para tornar viável a escrita de código perfeito. Observe que, ao contrário de ASP.NET, o SQL Server usa apenas um processo que não pode ser reciclado sem remover um banco de dados por um tempo inaceitável.
Com essas garantias mais fracas e em execução em um único processo, a confiabilidade é baseada no encerramento de threads ou na reciclagem de domínios do aplicativo quando necessário, além tomar precauções para garantir que os recursos do sistema operacional, tais como memória ou identificadores, não vazem. Mesmo com essa restrição de confiabilidade mais simples, ainda há um requisito de confiabilidade significativo:
Nunca vaze recursos do sistema operacional.
Identificar todos os bloqueios gerenciados em todos os formulários para o CLR.
Nunca interrompa o estado compartilhado do domínio entre aplicativos, permitindo AppDomain que a reciclagem funcione sem problemas.
Embora seja teoricamente possível escrever código gerenciado para manipular ThreadAbortException, StackOverflowExceptione OutOfMemoryException exceções, esperar que os desenvolvedores escrevam um código tão robusto em todo um aplicativo não é razoável. Por esse motivo, as exceções fora de banda resultam no encerramento do thread em execução; e se o thread encerrado estava editando o estado compartilhado, o que pode ser determinado dependendo de o thread manter ou não um bloqueio, o AppDomain é descarregado. Quando um método que está editando o estado compartilhado é encerrado, o estado será corrompido porque não é possível gravar um código de back-out confiável para atualizações no estado compartilhado.
No .NET Framework versão 2.0, o único host que requer confiabilidade é o SQL Server. Se o assembly for executado no SQL Server, você deverá fazer o trabalho de confiabilidade para cada parte desse assembly, mesmo se houver recursos específicos desabilitados durante a execução no banco de dados. Isso é necessário porque o mecanismo de análise de código examina o código no nível de montagem e não consegue diferenciar o código desabilitado. Outra consideração sobre a programação do SQL Server é que o SQL Server executa tudo em um processo e a reciclagem de AppDomain é usada para limpar todos os recursos como memória e identificadores do sistema operacional.
Você não pode depender de finalizadores ou destruidores ou blocos try/finally para código de recuo. Eles podem ser interrompidos ou não chamados.
Exceções assíncronas podem ser geradas em locais inesperados, possivelmente todas as instruções do computador: ThreadAbortException, StackOverflowExceptione OutOfMemoryException.
Os threads gerenciados não são necessariamente threads Win32 no SQL; podem ser fibras.
Deve-se evitar sempre que possível alterar o estado compartilhado mutável em todo o processo ou entre domínios de aplicativos, pois é extremamente difícil fazê-lo com segurança.
As condições de memória insuficiente não são raras no SQL Server.
Se as bibliotecas hospedadas no SQL Server não atualizarem corretamente seu estado compartilhado, há uma grande probabilidade de que o código não seja recuperado até que o banco de dados seja reiniciado. Além disso, em alguns casos extremos, é possível que isso faça com que o processo do SQL Server falhe, fazendo com que o banco de dados seja reinicializado. A reinicialização do banco de dados pode derrubar um site da Web ou afetar as operações da empresa, prejudicando a disponibilidade. Um vazamento lento de recursos do sistema operacional, como memória ou identificadores, pode fazer com que o servidor eventualmente falhe ao alocar identificadores sem nenhuma possibilidade de recuperação ou, potencialmente, o servidor pode degradar lentamente o desempenho e reduzir a disponibilidade do aplicativo de um cliente. Claramente queremos evitar esses cenários.
Regras de prática recomendada
A introdução se concentrou no que a revisão de código para o código gerenciado executado no servidor teria que capturar para aumentar a estabilidade e a confiabilidade da estrutura. Todas essas verificações são uma boa prática em geral e são absolutamente imprescindíveis no servidor.
Diante de um bloqueio morto ou restrição de recurso, o SQL Server anulará um thread ou derrubará um AppDomain. Quando isso acontece, o único elemento que certamente está em execução é o código de recuo em uma CER (região de execução restrita).
Usar o SafeHandle para evitar vazamentos de recursos
No caso de um descarregamento de AppDomain, você não pode depender de blocos finally ou finalizadores serem executados, portanto, é importante abstrair todo acesso a recursos do sistema operacional por meio da classe SafeHandle em vez de IntPtr, HandleRef ou classes semelhantes. Isso permite que o CLR acompanhe e feche os identificadores que você usa até mesmo no caso de desativação de AppDomain.
SafeHandle estará usando um finalizador crítico que o CLR sempre executará.
O identificador de sistema operacional é armazenado no SafeHandle desde o momento em que ele é criado até o momento em que ele é liberado. Não há período durante o qual um ThreadAbortException pode ocorrer para que haja perda de um identificador. Além disso, uma invocação de plataforma faz a contagem de referências do identificador, o que permite um acompanhamento preciso do tempo de vida dele e evita um problema de segurança com uma condição de corrida entre Dispose e um método que está usando atualmente o identificador.
A maioria das classes que atualmente têm um finalizador apenas para limpar um identificador de sistema operacional não precisarão mais do finalizador. Em vez disso, o finalizador estará na classe SafeHandle derivada.
Observe que SafeHandle não é uma substituição para IDisposable.Dispose. Ainda há possibilidade de contenção de recursos e vantagens de desempenho para descartar explicitamente os recursos de sistema operacional. Observe que apenas blocos finally que descartam explicitamente os recursos podem não ser executados até a conclusão.
SafeHandle permite que você implemente seu próprio ReleaseHandle método que executa o trabalho para liberar o identificador, como passar o estado para uma rotina de liberação de identificadores do sistema operacional ou liberar um conjunto de identificadores em um loop. O CLR garante que esse método seja executado. É responsabilidade do autor da implementação de ReleaseHandle assegurar que o identificador seja liberado em todas as circunstâncias. Falha em fazê-lo causará perda do identificador, o que geralmente resultará em perda de recursos nativos associados com o identificador. Portanto, é fundamental estruturar SafeHandle classes derivadas de modo que a ReleaseHandle implementação não exija a alocação de recursos que possam não estar disponíveis no momento da invocação. Observe que é permitido chamar métodos que podem falhar na implementação de ReleaseHandle, desde que seu código possa lidar com essas falhas e concluir o contrato para liberar o identificador nativo. Para fins de depuração, ReleaseHandle tem um valor de retorno Boolean que pode ser definido para false se um erro catastrófico for encontrado, impedindo a liberação do recurso. Isso ativará o MDA releaseHandleFailed , se habilitado, para ajudar a identificar o problema. Ele não afeta o runtime de nenhuma outra forma; ReleaseHandle não será chamado novamente para o mesmo recurso e, consequentemente, o identificador será perdido.
SafeHandle não é apropriado em determinados contextos. Já que o método ReleaseHandle pode ser executado em um thread do finalizador GC, quaisquer identificadores que seja necessário liberar em um determinado thread não devem ser encapsulados em um SafeHandle.
RCWs (Runtime Callable Wrappers) podem ser limpos pelo CLR sem código adicional. Para o código que usa a plataforma invoke e trata um objeto COM como um IUnknown* ou um IntPtr, o código deve ser reescrito para usar um RCW.
SafeHandle pode não ser adequado para esse cenário devido à possibilidade de um método de liberação não gerenciado retornar a chamada em código gerenciado.
Regra de análise de código
Use SafeHandle para encapsular recursos do sistema operacional. Não use HandleRef nem campos do tipo IntPtr.
Verifique se os finalizadores não precisam ser executados para evitar o vazamento de recursos do sistema operacional
Examine os finalizadores com cuidado para garantir que, mesmo que eles não sejam executados, um recurso crítico do sistema operacional não seja vazado. Ao contrário de um descarregamento normal AppDomain quando o aplicativo está em execução em um estado estável ou quando um servidor como o SQL Server é desligado, os objetos não são finalizados durante um descarregamento abrupto AppDomain . Verifique se os recursos não são vazados no caso de um descarregamento abrupto, pois a correção de um aplicativo não pode ser garantida, mas a integridade do servidor deve ser mantida por não vazamento de recursos. Use SafeHandle para liberar todos os recursos do sistema operacional.
Verifique se as cláusulas finally não precisam executar para impedir a perda de recursos do sistema operacional
Não há garantia de que cláusulas finally sejam executadas fora de CERs, exigindo que os desenvolvedores de biblioteca não confiem no código dentro de um bloco finally para liberar recursos não gerenciados. Usar SafeHandle é a solução recomendada.
Regra de análise de código
Use SafeHandle para limpar os recursos do sistema operacional em vez de Finalize. Não use IntPtr; utilize SafeHandle para encapsular os recursos. Se a cláusula finally precisa ser executada, coloque-a em uma CER.
Todos os bloqueios devem passar pelo código de bloqueio gerenciado existente
O CLR deve saber quando o código está em um bloqueio para que ele saiba subdividir o AppDomain em vez de apenas anular o thread. Anular o thread pode ser perigoso, já que os dados operados pelo thread podem ser deixados em um estado inconsistente. Portanto, o todo AppDomain precisa ser reciclado. As consequências de não identificar um bloqueio podem ser impasses ou resultados incorretos. Use os métodos BeginCriticalRegion e EndCriticalRegion identifique as regiões de bloqueio. Eles são métodos estáticos na Thread classe que se aplicam apenas ao thread atual, ajudando a impedir que um thread edite a contagem de bloqueios de outro thread.
Enter e Exit têm essa notificação de CLR como interna, portanto, o uso deles é recomendado, bem como o uso da instrução lock, a qual usa esses métodos.
Outros mecanismos de bloqueio como bloqueios de rotação e AutoResetEvent devem chamar esses métodos para notificar o CLR que uma seção crítica está sendo inserida. Esses métodos não fazem bloqueios; eles informam ao CLR que o código está sendo executado em uma seção crítica e anular o thread pode deixar o estado compartilhado inconsistente. Se você definiu seu próprio tipo de bloqueio, como uma classe personalizada ReaderWriterLock , use esses métodos de contagem de bloqueios.
Regra de análise de código
Marcar e identificar todos os bloqueios usando BeginCriticalRegion e EndCriticalRegion. Não use CompareExchange, Incremente Decrement em um loop. Não faça uma invocação de plataforma das variantes Win32 desses métodos. Não use Sleep em um loop. Não use campos voláteis.
O código de limpeza deve estar em um bloco finally ou catch e não deve seguir um catch
O código de limpeza nunca deve seguir um bloco catch; ele deve estar em um bloco finally ou no próprio bloco catch. Essa deve ser uma boa prática normal. Um finally bloco é geralmente preferido porque executa o mesmo código tanto quando uma exceção é lançada quanto quando o final do try bloco é normalmente encontrado. No caso de uma exceção inesperada ser gerada, por exemplo, um ThreadAbortException, o código de limpeza não será executado. Todos os recursos não gerenciados que você limparia em um finally devem ser encapsulados idealmente em um SafeHandle para evitar vazamentos. Observe que a palavra-chave using do C# pode ser usada com eficiência para descartar objetos, incluindo identificadores.
Embora a reciclagem de AppDomain possa limpar recursos no thread do finalizador, ainda é importante colocar o código de limpeza no local correto. Observe que, se um thread receber uma exceção assíncrona sem manter um bloqueio, o CLR tentará encerrar o thread em si sem precisar reciclar o AppDomain. Assegurar que recursos sejam limpos cedo em vez de tarde ajuda por meio da disponibilização de mais recursos e do melhor gerenciamento do tempo de vida. Se você não fechar explicitamente um identificador para um arquivo em algum caminho de código de erro, aguarde que o finalizador SafeHandle o limpe; na próxima vez que seu código executar, ele pode poderá falhar ao tentar acessar exatamente o mesmo arquivo, se o finalizador ainda não tiver executado. Por esse motivo, garantir que o código de limpeza exista e funcione corretamente ajudará a se recuperar de falhas de forma mais limpa e rápida, mesmo que não seja estritamente necessário.
Regra de análise de código
O código de limpeza após catch precisa estar em um bloco finally. Faça chamadas para descartar em um bloco finally. Blocos catch devem terminar com um lançamento ou relançamento. Embora haja exceções, como o código que detecta se uma conexão de rede pode ser estabelecida onde você pode obter qualquer uma de um grande número de exceções, qualquer código que exija a captura de várias exceções em circunstâncias normais deve dar uma indicação de que o código deve ser testado para ver se ele será bem-sucedido.
Process-Wide estado compartilhado mutável entre domínios de aplicativo deve ser eliminado ou usar uma região de execução restrita
Conforme descrito na introdução, pode ser muito difícil escrever código gerenciado que monitora o estado compartilhado em todo o processo em domínios de aplicativo de maneira confiável. O estado compartilhado em todo o processo é qualquer tipo de estrutura de dados compartilhada entre domínios de aplicativo, no código Win32, dentro do CLR ou no código gerenciado usando comunicação remota. Qualquer estado compartilhado mutável é muito difícil de escrever corretamente no código gerenciado e qualquer estado compartilhado estático pode ser feito apenas com muito cuidado. Se você tiver um estado compartilhado em todo o processo ou em todo o computador, encontre alguma maneira de eliminá-lo ou proteger o estado compartilhado usando uma CER (região de execução restrita). Observe que qualquer biblioteca com estado compartilhado que não for identificada e corrigida pode causar a falha de um host, como o SQL Server, que requer um descarregamento limpo AppDomain.
Se o código usar um objeto COM, evite compartilhar esse objeto COM entre domínios de aplicativo.
Os bloqueios não funcionam em todo o processo ou entre domínios de aplicativo.
No passado, Enter e o "lock Statement" foram usados para criar bloqueios de processo globais. Por exemplo, isso ocorre ao bloquear classes ágeis de AppDomain, tais como instâncias de Type de assemblies não compartilhados, objetos de Thread, cadeias de caracteres internas e algumas cadeias de caracteres compartilhadas entre domínios do aplicativo usando comunicação remota. Esses bloqueios não são mais abrangentes em todo o processo. Para identificar a presença de um bloqueio de domínio de interaplicação em todo o processo, determine se o código dentro do bloqueio usa qualquer recurso externo e persistente, como um arquivo em disco ou possivelmente um banco de dados.
Observe que usar um bloqueio dentro de um AppDomain pode causar problemas se o código protegido usa um recurso externo, porque esse código pode ser executado simultaneamente em vários domínios de aplicativo. Isso pode ser um problema ao gravar em um arquivo de log ou vincular-se a um soquete durante todo o processo. Essas alterações significam que não há uma maneira fácil, usando código gerenciado, de obter um bloqueio global de processo, exceto por meio de uma instância nomeada Mutex ou Semaphore. Crie um código que não seja executado simultaneamente em dois domínios de aplicativo, ou use as classes Mutex ou Semaphore. Se o código existente não pode ser alterado, não use um mutex nomeado do Win32 para alcançar essa sincronização, porque a execução em modo fibra significa que você não pode assegurar que um mutex será adquirido e liberado pelo mesmo thread do sistema operacional. Você deve usar a classe gerenciada Mutex, ou uma classe nomeada ManualResetEvent, AutoResetEvent ou Semaphore para sincronizar o bloqueio de código de maneira que o CLR o reconheça, em vez de sincronizar o bloqueio usando código não gerenciado.
Evite lock(typeof(MyType))
Objetos privados e públicos Type em assemblies compartilhados com apenas uma cópia do código compartilhado em todos os domínios do aplicativo também apresentam problemas. Para assemblies compartilhados, há apenas uma instância de um Type por processo, o que significa que vários domínios do aplicativo compartilham exatamente a mesma instância de Type. A execução de um bloqueio em uma instância de Type usa um bloqueio que afeta todo o processo, não apenas o AppDomain. Se um AppDomain usar um bloqueio em um objeto Type e depois esse thread for anulado repentinamente, ele não liberará o bloqueio. Esse bloqueio, em seguida, pode fazer com que outros domínios de aplicativo fiquem em deadlock.
Uma boa maneira de fazer bloqueios em métodos estáticos envolve a adição de um objeto de sincronização interna estático ao código. Isso poderá ser inicializado no construtor de classe se houver um, mas se não for possível inicializar assim:
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;
}
}
Então, ao usar um bloqueio, use a propriedade InternalSyncObject para obter um objeto no qual o bloqueio será usado. Você não precisará usar a propriedade se tiver inicializado o objeto de sincronização interna em seu construtor de classe. O código de inicialização de bloqueio de verificação dupla deve ser semelhante a este exemplo:
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;
}
}
Uma observação sobre lock(this)
Geralmente, é aceitável bloquear um objeto individual acessível publicamente. No entanto, se o objeto for um objeto singleton que pode causar deadlock em um subsistema inteiro, considere usar o padrão de design acima também. Por exemplo, um bloqueio no objeto SecurityManager pode causar um deadlock dentro do AppDomain, tornando todo o AppDomain inutilizável. É uma boa prática não colocar um bloqueio em um objeto acessível publicamente desse tipo. No entanto, um bloqueio em uma coleção ou matriz individual geralmente não deve apresentar um problema.
Regra de análise de código
Não use bloqueios em tipos que podem ser usados em domínios do aplicativo ou não têm um forte senso de identidade. Não chame Enter em um Type, MethodInfo, PropertyInfo, String, ValueType, Thread ou qualquer objeto que derive de MarshalByRefObject.
Remover chamadas GC.KeepAlive
Uma quantidade significativa de código existente não usa KeepAlive quando deveria ou a usa quando não é apropriado. Depois de converter em SafeHandle, as classes não precisam chamar KeepAlive, desde que não tenham um finalizador e dependam de SafeHandle para finalizar os identificadores do sistema operacional. Embora o custo de desempenho de manter uma chamada para KeepAlive possa ser insignificante, a percepção de que uma chamada para KeepAlive é necessária ou suficiente para resolver um problema de ciclo de vida que pode não existir mais torna o código mais difícil de manter. No entanto, ao usar os RCWs (Runtime Callable Wrappers) do CLR de interoperabilidade COM, KeepAlive ainda é exigido pelo código.
Regra de análise de código
Remova KeepAlive.
Usar o atributo HostProtection
O HostProtectionAttribute (HPA) fornece o uso de ações declarativas de segurança para determinar os requisitos de proteção do host, permitindo que o host impeça até mesmo o código totalmente confiável de chamar determinados métodos que são inadequados para o host especificado, como Exit ou Show para o SQL Server.
O HPA afeta apenas aplicativos não gerenciados que hospedam o common language runtime e implementam a proteção de host, como o SQL Server. Quando aplicada, a ação de segurança resulta na criação de uma demanda de link com base nos recursos de host que a classe ou o método expõe. Se o código for executado em um aplicativo cliente ou em um servidor que não esteja protegido por host, o atributo "evapora"; não é detectado e, portanto, não aplicado.
Importante
A finalidade desse atributo é impor diretrizes de modelo de programação específicas do host, não o comportamento de segurança. Embora uma demanda de link seja usada para verificar se há conformidade com os requisitos do modelo de programação, o HostProtectionAttribute não é uma permissão de segurança.
Se o host não tiver requisitos de modelo de programação, as demandas de link não ocorrerão.
Esse atributo identifica o seguinte:
Métodos ou classes que não se encaixam no modelo de programação principal, mas são benignos.
Métodos ou classes que não se encaixam no modelo de programação do host e podem levar à desestabilização do código do usuário gerenciado pelo servidor.
Métodos ou classes que não se encaixam no modelo de programação do host e podem levar a uma desestabilização do próprio processo do servidor.
Observação
Se você está criando uma biblioteca de classes que pode vir a ser chamada por aplicativos que podem vir a ser executados em um ambiente de host protegido, você deve aplicar esse atributo aos membros que expõem categorias de recursos HostProtectionResource. Os membros da biblioteca de classes do .NET Framework com esse atributo fazem com que apenas o chamador imediato seja verificado. O membro da biblioteca deve também causar uma verificação de seu chamador imediato da mesma maneira.
Encontre mais informações sobre o HPA em HostProtectionAttribute.
Regra de análise de código
Para o SQL Server, todos os métodos usados para introduzir a sincronização ou o threading devem ser identificados com o HPA. Isso inclui métodos que compartilham estado, são sincronizados ou gerenciam processos externos. Os HostProtectionResource valores que afetam o SQL Server são SharedState, Synchronizatione ExternalProcessMgmt. No entanto, qualquer método que exponha qualquer HostProtectionResource deve ser identificado por um HPA, não apenas aqueles que usam recursos que afetam o SQL.
Não bloquear indefinidamente em código não gerenciado
O bloqueio em código não gerenciado, ao invés de no código gerenciado, pode causar um ataque de negação de serviço porque o CLR não é capaz de interromper o thread. Um thread bloqueado impede que o CLR descarregue o AppDomain, pelo menos sem fazer algumas operações extremamente inseguras. Bloquear o uso de um primitivo de sincronização do Windows é um exemplo claro de algo que não podemos permitir. O bloqueio em uma chamada para ReadFile em um soquete deve ser evitado se possível – idealmente, a API do Windows deve fornecer um mecanismo para que uma operação como essa atinja o tempo limite.
Qualquer método que chame recursos nativos deve idealmente usar uma chamada de Win32 com um tempo limite razoável e finito. Se o usuário tiver permissão para especificar o tempo limite, o usuário não deverá ter permissão para especificar um tempo limite infinito sem alguma permissão de segurança específica. Como diretriz, se um método bloquear por mais de aproximadamente 10 segundos, você precisará usar uma versão que dê suporte a tempos limite ou precisar de suporte adicional do CLR.
Aqui estão alguns exemplos de APIs problemáticas. Pipes (anônimos e nomeados) podem ser criados com um tempo limite; no entanto, o código deve assegurar que ele nunca chame CreateNamedPipe nem WaitNamedPipe com NMPWAIT_WAIT_FOREVER. Além disso, pode haver bloqueio inesperado mesmo se um tempo limite for especificado. A chamada WriteFile em um pipe anônimo será bloqueada até que todos os bytes sejam gravados, ou seja, se o buffer tiver dados não lidos nele, a WriteFile chamada será bloqueada até que o leitor libere espaço no buffer do pipe. Os soquetes sempre devem usar alguma API que honra um mecanismo de tempo limite.
Regra de análise de código
Bloquear sem tempo limite no código não gerenciado é um ataque de negação de serviço. Não execute chamadas de invocação de plataforma para WaitForSingleObject, WaitForSingleObjectEx, WaitForMultipleObjects, MsgWaitForMultipleObjects e MsgWaitForMultipleObjectsEx. Não utilize NMPWAIT_WAIT_FOREVER.
Identificar quaisquer recursos dependentes de STA
Identifique qualquer código que use STAs (apartments de thread único) COM. As STAs estão desabilitadas no processo do SQL Server. Recursos que dependem de CoInitialize, assim como contadores de desempenho ou a área de transferência, devem ser desabilitados no SQL Server.
Verifique se os finalizadores estão livres de problemas de sincronização
Vários threads de finalizador podem existir em versões futuras do .NET Framework, o que significa que os finalizadores para instâncias diferentes do mesmo tipo são executados simultaneamente. Eles não precisam ser completamente thread-safe; o coletor de lixo garante que apenas um thread executará o finalizador para uma determinada instância do objeto. No entanto, os finalizadores devem ser programados de forma a evitar condições de corrida e deadlocks ao serem executados simultaneamente em múltiplas instâncias diferentes de objetos. Ao usar qualquer estado externo, por exemplo, ao gravar em um arquivo de log em um finalizador, problemas de threading devem ser solucionados. Não dependa da finalização para fornecer acesso thread-safe. Não use o armazenamento local de thread, gerenciado ou nativo, para armazenar o estado no thread do finalizador.
Regra de análise de código
Os finalizadores devem estar livres de problemas de sincronização. Não use um estado mutável estático em um finalizador.
Evite memória não gerenciada, se possível
Memória não gerenciada pode ser perdida, assim como um identificador de sistema operacional. Se possível, tente usar memória na pilha com stackalloc ou um objeto gerenciado fixo, como a instrução fixed ou um GCHandle usando um byte[]. O GC limpa esses elementos eventualmente. No entanto, se você precisar alocar memória não gerenciada, considere usar uma classe que deriva de SafeHandle para encapsular a alocação de memória.
Observe que há pelo menos um caso onde SafeHandle não é adequado. Para chamadas de método COM que alocam ou liberam memória, é comum que uma DLL aloque memória por meio de CoTaskMemAlloc e, em seguida, outra DLL libere essa memória com CoTaskMemFree. Usar SafeHandle nesses lugares seria inadequado, pois tentará vincular o tempo de vida da memória não gerenciada ao tempo de vida do SafeHandle em vez de permitir que a outra DLL controle o tempo de vida da memória.
Examinar todos os usos de Catch(Exception)
Os blocos de captura que capturam todas as exceções em vez de uma exceção específica também capturarão as exceções assíncronas. Examine cada bloco catch(Exception), procurando por nenhuma liberação de recursos importantes ou código de recuo que possa ser ignorado, bem como comportamento potencialmente incorreto dentro do próprio bloco catch para tratar uma ThreadAbortException, StackOverflowException ou OutOfMemoryException. Observe que é possível que esse código esteja registrando ou fazendo algumas suposições de que ele só pode ver determinadas exceções, ou que sempre que uma exceção acontece, ele falhou exatamente por um motivo específico. Essas suposições podem precisar ser atualizadas para incluir ThreadAbortException.
Considere a possibilidade de alterar todos os locais que capturam todas as exceções para capturar um tipo específico de exceção que você espera ser gerada, tal como uma FormatException de métodos de formatação de cadeia de caracteres. Isso impede que o bloco catch encontre exceções inesperadas e ajuda a garantir que o código não oculte bugs capturando exceções inesperadas. Como regra geral, nunca manipule uma exceção no código de biblioteca (código que requer que você capture uma exceção pode indicar uma falha de design no código que você está chamando). Em alguns casos, talvez você queira capturar uma exceção e gerar um tipo de exceção diferente para fornecer mais dados. Usar exceções aninhadas nesse caso, armazenando a causa real da falha na propriedade InnerException da nova exceção.
Regra de análise de código
Examine todos os blocos de captura no código gerenciado que capturam todos os objetos ou capturam todas as exceções. Em C#, isso significa sinalizar tanto catch{} quanto catch(Exception){}. Considere tornar o tipo de exceção muito específico ou revise o código para garantir que ele não atue de forma inadequada se capturar um tipo de exceção inesperado.
Não suponha que um thread gerenciado é um thread do Win32 – ele é uma fibra
O uso do armazenamento local de thread gerenciado funciona, mas você pode não usar o armazenamento local de thread não gerenciado ou assumir que o código será executado no thread atual do sistema operacional novamente. Não altere configurações como a localidade do thread. Não chame InitializeCriticalSection ou CreateMutex por meio de invocação de plataforma porque eles requerem que o thread de sistema operacional que entra em um bloqueio também saia do bloqueio. Como esse não será o caso ao usar fibras, as seções críticas do Win32 e os mutexes não podem ser usados diretamente no SQL. Observe que a classe gerenciada Mutex não trata das preocupações relacionadas à afinidade de thread.
Você pode usar com segurança a maior parte do estado em um objeto Thread gerenciado, incluindo o armazenamento local de thread gerenciado e a cultura de interface do usuário atual do thread. Você também pode usar o ThreadStaticAttribute, que torna o valor de uma variável estática existente acessível apenas pelo thread gerenciado atual (essa é outra maneira de fazer o armazenamento local de fibra no CLR). Por motivos de modelo de programação, você não pode alterar a cultura atual de um thread ao executar no SQL.
Regra de análise de código
O SQL Server é executado no modo de fibra; não use o armazenamento local do thread. Evite chamadas de invocação de plataforma para TlsAlloc, TlsFree, TlsGetValue e TlsSetValue.
Permitir a representação de identificador do SQL Server
Já que a representação opera em nível de thread e o SQL pode executar em modo de fibra, o código gerenciado não deve representar usuários e não deve chamar RevertToSelf.
Regra de análise de código
Permita a representação de identificador do SQL Server. Não use RevertToSelf, ImpersonateAnonymousToken, , DdeImpersonateClient, ImpersonateDdeClientWindow, ImpersonateLoggedOnUser, ImpersonateNamedPipeClient, , ImpersonateSelf, RpcImpersonateClient, , RpcRevertToSelf, RpcRevertToSelfEx, ou SetThreadToken.
Não chame Thread::Suspend
A capacidade de suspender um thread pode parecer uma operação simples, mas pode causar deadlocks. Se um thread que mantém um bloqueio for suspenso por um segundo thread e, em seguida, o segundo thread tentar usar o mesmo bloqueio, ocorrerá um deadlock. Suspend pode atualmente interferir com a segurança, o carregamento de classe, a comunicação remota e a reflexão.
Regra de análise de código
Não chame Suspend. Considere usar um primitivo de sincronização real, como um Semaphore ou ManualResetEvent .
Proteger operações críticas com regiões de execução restritas e contratos de confiabilidade
Ao executar uma operação complexa que atualiza um status compartilhado ou que precisa ter êxito total ou falha total, verifique se ela está protegida por uma CER (região de execução restrita). Isso garante que o código seja executado em todos os casos, até mesmo uma operação de anulação de thread abrupta ou um descarregamento de AppDomain abrupto.
Uma CER é um bloco try/finally específico imediatamente precedido por uma chamada para PrepareConstrainedRegions.
Fazer isso instrui o compilador Just-In-Time para preparar a todo o código no bloco finally antes de executar o bloco try. Isso assegura que o código no bloco finally será criado e que será executado em todos os casos. Não é incomum em um CER ter um bloco vazio try . Usar uma CER protege contra anulações de thread assíncronas e exceções de falta de memória. Consulte ExecuteCodeWithGuaranteedCleanup para um formulário de uma CER que manipula excedentes de pilha de código excessivamente profundo.