Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Las siguientes reglas de confiabilidad están orientadas a SQL Server; sin embargo, también se aplican a cualquier aplicación de servidor basada en host. Es extremadamente importante que los servidores como SQL Server no pierdan recursos ni se caigan. Sin embargo, esto no se puede hacer escribiendo código de retroceso para cada método que modifica el estado de un objeto. El objetivo no es escribir código administrado confiable al 100 por cien que se recupere de los errores en todas las ubicaciones con código devuelto. Eso sería una tarea intimidante con pocas posibilidades de éxito. El entorno de ejecución común (CLR) no puede proporcionar fácilmente garantías lo suficientemente fuertes al código administrado como para hacer factible la escritura de un código perfecto. Tenga en cuenta que, a diferencia de ASP.NET, SQL Server usa solo un proceso que no se puede reciclar sin quitar una base de datos inactiva durante mucho tiempo.
Con estas garantías más débiles y al ejecutarse en un único proceso, la confiabilidad se basa en terminar subprocesos o reciclar dominios de aplicación cuando es necesario, y en tomar precauciones para asegurarse de que no se pierden recursos del sistema operativo como identificadores o memoria. Incluso con esta restricción de confiabilidad más sencilla, todavía hay un requisito de confiabilidad significativo:
No perder nunca recursos del sistema operativo.
Identificar todos los tipos de bloqueos administrados para el CLR.
Nunca interrumpa el estado compartido entre dominios de aplicación, permitiendo así que el reciclaje funcione sin problemas.
Aunque teóricamente es posible escribir código administrado para controlar las excepciones ThreadAbortException, StackOverflowException y OutOfMemoryException, esperar que los desarrolladores escriban este código sólido en toda una aplicación no es razonable. Por ese motivo, las excepciones fuera de banda hacen que se finalice el subproceso en ejecución; y si el subproceso finalizado estaba editando el estado compartido, lo que se puede determinar si el subproceso mantiene un bloqueo, entonces se descarga el AppDomain. Cuando finaliza un método que edita el estado compartido, el estado estará dañado porque no es posible escribir código de retroceso confiable para las actualizaciones en estado compartido.
En .NET Framework versión 2.0, el único host que requiere confiabilidad es SQL Server. Si el ensamblado se ejecutará en SQL Server, debe realizar el trabajo de confiabilidad para cada parte de ese ensamblado, incluso si hay características específicas que están deshabilitadas al ejecutarse en la base de datos. Esto es necesario porque el motor de análisis de código examina el código en el nivel de ensamblado y no puede diferenciar el código deshabilitado. Otra consideración a la hora de programar es que SQL Server ejecuta todo el contenido en un proceso y el reciclaje de AppDomain se usa para limpiar todos los recursos, como los identificadores de memoria y del sistema operativo.
No puede depender de los finalizadores, destructores ni de bloques try/finally
para el código devuelto. Es posible que sean interrumpidos o que no se les llame.
Las excepciones asincrónicas se pueden producir en ubicaciones inesperadas, posiblemente todas las instrucciones de máquina: ThreadAbortException, StackOverflowExceptiony OutOfMemoryException.
Los subprocesos administrados no son necesariamente subprocesos win32 en SQL; pueden ser fibras.
El estado compartido mutable a nivel de proceso o entre dominios de aplicación es extremadamente difícil de modificar de forma segura y debe evitarse siempre que sea posible.
Las condiciones fuera de memoria no son raras en SQL Server.
Si las bibliotecas hospedadas en SQL Server no actualizan correctamente su estado compartido, existe una alta probabilidad de que el código no se recupere hasta que se haya reiniciado la base de datos. Además, en algunos casos extremos, es posible que esto cause un error en el proceso de SQL Server, lo que hace que la base de datos se reinicie. Reiniciar la base de datos puede quitar un sitio web o afectar a las operaciones de la empresa, lo que afecta a la disponibilidad. Una pérdida lenta de recursos del sistema operativo, como la memoria o los identificadores, puede provocar que el servidor produzca errores al asignar identificadores sin posibilidad de recuperación o, posiblemente, el servidor puede degradar lentamente el rendimiento y reducir la disponibilidad de la aplicación de un cliente. Claramente queremos evitar estos escenarios.
Reglas de procedimientos recomendados
La introducción se centró en lo que la revisión de código para el código administrado que se ejecuta en el servidor tendría que detectar para aumentar la estabilidad y confiabilidad del marco. Todas estas comprobaciones son prácticas recomendadas en general y deben ser absolutas en el servidor.
En el caso de un interbloqueo o de una restricción de recursos, SQL Server anulará un subproceso o un AppDomain. Cuando esto sucede, solo se garantiza que se ejecute el código de reversión en una región de ejecución restringida (CER).
Uso de SafeHandle para evitar pérdidas de recursos
En el caso de una descarga de AppDomain, no se puede depender de la ejecución de bloques finally
o finalizadores, por lo que es importante abstraer el acceso a todos los recursos del sistema operativo a través de la clase SafeHandle en lugar de IntPtr, HandleRef o clases similares. Esto permite que el CLR realice el seguimiento y cierre los identificadores usados incluso en el caso de anulación de AppDomain.
SafeHandle usará un finalizador crítico que el CLR ejecutará siempre.
El identificador del sistema operativo se almacena en el controlador seguro desde el momento en que se crea hasta el momento en que se libera. No hay ninguna período de tiempo durante el que una excepción ThreadAbortException pueda provocar la pérdida de un identificador. Además, la invocación de plataforma realizará el recuento de referencias del identificador, lo que permite un seguimiento estricto de la duración del identificador, evitando un problema de seguridad con una condición de carrera entre Dispose
y un método que actualmente use el identificador.
La mayoría de las clases que actualmente tienen un finalizador simplemente para limpiar un controlador del sistema operativo ya no necesitarán el finalizador. En su lugar, el finalizador estará en la SafeHandle clase derivada.
Tenga en cuenta que SafeHandle no es un reemplazo de IDisposable.Dispose. Todavía hay posibles ventajas de contención de recursos y rendimiento a la eliminación explícita de recursos del sistema operativo. Simplemente tenga en cuenta que es posible que los bloques finally
que eliminan explícitamente de los recursos no se ejecuten hasta su finalización.
SafeHandle permite implementar tu propio método ReleaseHandle que realiza el trabajo de liberar el identificador, como pasar el estado a una rutina del sistema operativo para liberar identificadores o liberar un conjunto de identificadores en un bucle. El CLR garantiza que este método se ejecute. Es responsabilidad del autor de la implementación de ReleaseHandle asegurarse de que el identificador se libera en todas las circunstancias. Si no lo hace, se producirá la pérdida del identificador, lo que a menudo produce la pérdida de recursos nativos asociados al identificador. Por lo tanto, es fundamental estructurar SafeHandle las clases derivadas de modo que la ReleaseHandle implementación no requiera la asignación de ningún recurso que pueda no estar disponible en el momento de la invocación. Tenga en cuenta que se permite llamar a métodos que pueden producir errores en la implementación de ReleaseHandle siempre que el código pueda controlar estos errores y completar el contrato para liberar el identificador nativo. Con fines de depuración, ReleaseHandle tiene un Boolean valor de retorno que puede establecerse en false
si se encuentra un error catastrófico que impide la liberación del recurso. Al hacerlo, se activará el MDA releaseHandleFailed , si está habilitado, para ayudar a identificar el problema. No afecta el tiempo de ejecución de ninguna otra forma; no se volverá a llamar a ReleaseHandle para el mismo recurso y, por tanto, el identificador se perderá.
SafeHandle no es adecuado en determinados contextos. Como el método ReleaseHandle se puede ejecutar en un subproceso de finalizador de GC, los identificadores que deban liberarse en un subproceso concreto no deben estar contenidos en SafeHandle.
La CLR puede limpiar los envoltorios invocables en tiempo de ejecución (RCWs) sin necesitar código adicional. Para el código que utiliza la invocación de plataforma y trata un objeto COM como un IUnknown*
o un IntPtr, el código debe ser reescrito para usar un RCW.
SafeHandle puede no ser adecuado para este escenario debido a la posibilidad de que un método de liberación no administrado vuelva a entrar en el código administrado.
Regla de análisis de código
Use SafeHandle para encapsular los recursos del sistema operativo. No use HandleRef ni campos de tipo IntPtr.
Asegúrese de que los finalizadores no tienen que ejecutarse para evitar la pérdida de recursos del sistema operativo.
Revise cuidadosamente los finalizadores para asegurarse de que, incluso si no se ejecutan, no se produzca una pérdida de recursos críticos del sistema operativo. A diferencia de una descarga normal AppDomain cuando la aplicación se ejecuta en un estado estable o cuando un servidor como SQL Server se cierra, los objetos no se finalizan durante una descarga abrupta AppDomain . Asegúrese de que los recursos no se filtren en el caso de una descarga abrupta, ya que no se puede garantizar la corrección de una aplicación, pero la integridad del servidor debe mantenerse no filtrando recursos. Use SafeHandle para liberar los recursos del sistema operativo.
Asegúrese de que las cláusulas finally no tienen que ejecutarse para evitar la pérdida de recursos del sistema operativo.
finally
No se garantiza que las cláusulas se ejecuten fuera de las CER, lo que requiere que los desarrolladores de bibliotecas no dependan del código dentro de un finally
bloque para liberar recursos no administrados. Usar SafeHandle es la solución recomendada.
Regla de análisis de código
Use SafeHandle para limpiar los recursos del sistema operativo en lugar de Finalize
. No use IntPtr; use SafeHandle para encapsular los recursos. Si se tiene que ejecutar la cláusula finally, colóquela en una CER.
Todos los bloqueos deben pasar por el código de bloqueo administrado existente.
El CLR debe saber cuándo el código está en un bloqueo para saber que debe anular el AppDomain en lugar de limitarse a anular el subproceso. Anular el subproceso podría ser peligroso dado que los datos en los que opera el subproceso podrían quedar en un estado incoherente. Por lo tanto, todo AppDomain tiene que reciclarse. Las consecuencias de no poder identificar un bloqueo pueden ser interbloqueos o resultados incorrectos. Use los métodos BeginCriticalRegion y EndCriticalRegion para identificar las regiones de bloqueo. Son métodos estáticos en la Thread clase que solo se aplican al subproceso actual, lo que ayuda a evitar que un subproceso edite el recuento de bloqueos de otro subproceso.
Enter y Exit tienen integrada esta notificación al CLR, por lo que se recomienda su uso, así como el uso de la instrucción lock, que usa estos métodos.
Otros mecanismos de bloqueo, como los bloqueos por subproceso y AutoResetEvent, deben llamar a estos métodos para notificar al CLR que se está entrando en una sección crítica. Estos métodos no toman ningún bloqueo; informan al CLR que se está ejecutando código en una sección crítica y la anulación del subproceso podría dejar un estado compartido incoherente. Si ha definido su propio tipo de bloqueo, como una clase personalizada ReaderWriterLock , use estos métodos de recuento de bloqueos.
Regla de análisis de código
Marque e identifique todos los bloqueos mediante BeginCriticalRegion y EndCriticalRegion. No utilice CompareExchange, Increment, y Decrement en un bucle. No realice una invocación de plataforma de las variantes Win32 de estos métodos. No utilices Sleep en un bucle. No use campos volátiles.
El código de limpieza debe estar en un bloque finally o catch, no después de una instrucción catch
El código de limpieza nunca debe seguir un bloque catch
; debe estar en un bloque finally
o en el propio bloque catch
. Debe ser una buena práctica habitual. Por lo general, se prefiere un bloque finally
porque ejecuta el mismo código tanto cuando se produce una excepción como cuando se llega al final del bloque try
de forma normal. En caso de que se produzca una excepción inesperada, por ejemplo, no se ejecutará el ThreadAbortExceptioncódigo de limpieza. Los recursos no administrados que limpiarías en un finally
deberían estar encapsulados idealmente en un SafeHandle para evitar filtraciones. Tenga en cuenta que se puede usar la palabra clave using
de C# de manera eficaz para desechar objetos, incluidos los identificadores.
Aunque el reciclaje de AppDomain puede limpiar los recursos en el subproceso de finalizador, sigue siendo importante colocar el código de limpieza en el lugar correcto. Tenga en cuenta que si un subproceso recibe una excepción asincrónica sin tener un bloqueo, el CLR intenta finalizar el subproceso por sí mismo sin tener que reciclar el AppDomain. Asegurarse de que los recursos se limpian antes, en lugar de más tarde, ayuda a disponer de más recursos y administrar mejor la vida útil. Si no cierra explícitamente un identificador a un archivo en alguna ruta de acceso del código de error y después espera a que el finalizador de SafeHandle lo limpie, la próxima vez que ejecute el código se puede producir un error al intentar obtener acceso al mismo archivo si todavía no se ha ejecutado el finalizador. Por este motivo, asegurarse de que el código de limpieza existe y funciona correctamente ayudará a recuperarse de errores de forma más limpia y rápida, aunque no sea estrictamente necesario.
Regla de análisis de código
El código de limpieza después de catch
debe estar en un bloque finally
. Coloque las llamadas al método dispose en un bloque finally. Los bloques catch
deben terminar con throw o rethrow. Aunque habrá excepciones, como el código que detecta si se puede establecer una conexión de red donde puede obtener cualquiera de un gran número de excepciones, cualquier código que requiera la detección de una serie de excepciones en circunstancias normales debe dar una indicación de que el código debe probarse para ver si se realizará correctamente.
Process-Wide estado compartido mutable entre dominios de aplicación debe eliminarse o usar una región de ejecución restringida
Como se describe en la introducción, puede ser muy difícil escribir código administrado que supervise el estado compartido de todo el proceso entre dominios de aplicación de forma confiable. El estado compartido de todo el proceso es cualquier tipo de estructura de datos compartida entre dominios de aplicación, ya sea en código Win32, dentro de CLR o en código administrado mediante comunicación remota. Cualquier estado compartido mutable es muy difícil de escribir correctamente en código administrado y cualquier estado compartido estático puede realizarse solo con gran cuidado. Si tiene un estado compartido de todo el proceso o de toda la máquina, busque alguna manera de eliminarlo o proteger el estado compartido mediante una región de ejecución restringida (CER). Tenga en cuenta que cualquier biblioteca con estado compartido que no se identifique y corrija podría provocar que un host, como SQL Server, que requiere una descarga limpia AppDomain, se bloquee.
Si el código usa un objeto COM, evite compartir ese objeto COM entre dominios de aplicación.
Los bloqueos no funcionan en todo el proceso ni entre dominios de aplicación.
En el pasado, Enter y la instrucción lock se han usado para crear bloqueos de proceso globales. Por ejemplo, esto se produce al bloquear en clases ágiles de AppDomain, como instancias de Type de ensamblados no compartidos, objetos Thread, cadenas internadas y algunas cadenas compartidas entre dominios de aplicación mediante comunicación remota. Estos bloqueos ya no abarcan todo el proceso. Para identificar la presencia de un bloqueo de dominio de interaplicación en todo el proceso, determine si el código dentro del bloqueo usa cualquier recurso externo, persistente, como un archivo en disco o posiblemente una base de datos.
Tenga en cuenta que tomar un bloqueo dentro de un AppDomain podría causar problemas si el código protegido usa un recurso externo porque ese código se puede ejecutar simultáneamente en varios dominios de aplicación. Esto puede ser un problema al escribir en un archivo de registro o enlazar a un socket para todo el proceso. Estos cambios significan que no hay ninguna manera fácil de usar código administrado para obtener un bloqueo global de proceso, aparte del uso de una instancia con nombre Mutex o Semaphore . Cree código que no se ejecute en dos dominios de aplicación simultáneamente o use las Mutex clases o Semaphore . Si no se puede cambiar el código existente, no use una exclusión mutua con nombre de Win32 para conseguir esta sincronización, dado que la ejecución en modo de fibra significa que no se puede garantizar que el mismo subproceso de sistema operativo vaya a adquirir y liberar una exclusión mutua. Debe usar la clase administrada Mutex, o un objeto denominado ManualResetEvent, AutoResetEvent o Semaphore, para sincronizar el bloqueo de una manera reconocida por el CLR en lugar de sincronizar el bloqueo mediante código no administrado.
Evitar bloqueo(typeof(MyType))
Los objetos privados y públicos Type en ensamblados compartidos con solo una copia del código compartido en todos los dominios de aplicación también presentan problemas. En el caso de los ensamblados compartidos, solo hay una instancia de Type por proceso, lo que significa que varios dominios de aplicación comparten la misma instancia de Type. Tomar un bloqueo en una instancia de Type toma un bloqueo que afecta a todo el proceso, no solo al AppDomain. Si AppDomain toma un bloqueo en un objeto Type y después ese subproceso se anula abruptamente, no liberará el bloqueo. Este bloqueo puede provocar que otros dominios de aplicación entren en un estado de interbloqueo.
Una buena manera de tomar bloqueos en métodos estáticos implica agregar un objeto de sincronización interno estático al código. Esto se podría inicializar en el constructor de clase si hay uno presente, pero si no se puede inicializar de esta manera:
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;
}
}
Después, al tomar un bloqueo, use la propiedad InternalSyncObject
para obtener un objeto para bloquear. No es necesario usar la propiedad si ha inicializado el objeto de sincronización interno en el constructor de clase. El código de inicialización del bloqueo de comprobación doble debe ser similar al de este ejemplo:
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;
}
}
Una nota sobre Lock(this)
Por lo general, es aceptable bloquear un objeto individual de acceso público. Sin embargo, si el objeto es un objeto singleton que podría provocar que un subsistema completo interbloquee, considere la posibilidad de usar también el patrón de diseño anterior. Por ejemplo, un bloqueo en el objeto SecurityManager podría causar un interbloqueo en el AppDomain inutilizando todo el AppDomain. Es recomendable no establecer un bloqueo sobre un objeto de este tipo que sea accesible públicamente. Sin embargo, un bloqueo en una colección o matriz individual generalmente no debe presentar un problema.
Regla de análisis de código
No tome bloqueos en tipos que es posible que se usen en dominios de aplicación o no tengan un sentido fuerte de identidad. No llame a Enter en un Type, MethodInfo, PropertyInfo, String, ValueType, Thread o cualquier objeto que derive de MarshalByRefObject.
Quitar las llamadas a GC.KeepAlive
Una cantidad significativa de código existente o bien no utiliza KeepAlive cuando debería, o lo utiliza cuando no es apropiado. Después de convertir a SafeHandle, no es necesario que las clases llamen a KeepAlive, suponiendo que no tienen un finalizador sino que se basan en SafeHandle para finalizar los identificadores de sistema operativo. Aunque el coste de rendimiento de conservar una llamada a KeepAlive puede ser insignificante, la percepción de que una llamada a KeepAlive es necesaria o suficiente para resolver un problema de vida útil que puede ya no existir hace que el código sea más difícil de mantener. Aun así, cuando se usan contenedores RCW de interoperabilidad COM, el código sigue necesitando KeepAlive.
Regla de análisis de código
Quitar KeepAlive.
Uso del atributo HostProtection
El HostProtectionAttribute (HPA) permite utilizar acciones de seguridad declarativas para determinar los requisitos de protección del host, lo que permite al host impedir que incluso el código de plena confianza llame a ciertos métodos que son inadecuados para el host específico, como Exit o Show para SQL Server.
HPA solo afecta a las aplicaciones no administradas que hospedan Common Language Runtime e implementan la protección de host, como SQL Server. Cuando se aplica, la acción de seguridad da lugar a la creación de una demanda de vínculo en función de los recursos host que expone la clase o el método. Si el código se ejecuta en una aplicación cliente o en un servidor que no está protegido por host, el atributo "evapora" ; no se detecta y, por tanto, no se aplica.
Importante
El propósito de este atributo es aplicar instrucciones del modelo de programación específicos del host, no el comportamiento de seguridad. Aunque la demanda de vínculo se usa para comprobar la conformidad con los requisitos del modelo de programación, el HostProtectionAttribute no constituye un permiso de seguridad.
Si el host no tiene requisitos de modelo de programación, no se producen las demandas de vínculo.
Este atributo identifica lo siguiente:
Métodos o clases que no se ajustan al modelo de programación de host, pero que de lo contrario son benignos.
Métodos o clases que no se ajustan al modelo de programación de host y podrían provocar una desestabilización del código de usuario administrado por el servidor.
Métodos o clases que no se ajustan al modelo de programación de host y podrían provocar una desestabilización del propio proceso del servidor.
Nota:
Si va a crear una biblioteca de clases que va a ser llamada por aplicaciones que se pueden ejecutar en un entorno de host protegido, debe aplicar este atributo a los miembros que exponen categorías de recursos de HostProtectionResource. Los miembros de la biblioteca de clases .NET Framework con este atributo hacen que solo se compruebe el llamador inmediato. El miembro de biblioteca también debe provocar una comprobación de su llamador inmediato de la misma manera.
Obtenga más información sobre HPA en HostProtectionAttribute.
Regla de análisis de código
Para SQL Server, todos los métodos que se usan para introducir la sincronización o subprocesos deben identificarse con el atributo HPA. Esto incluye métodos que comparten el estado, se sincronizan o administran procesos externos. Los HostProtectionResource valores que afectan a SQL Server son SharedState, Synchronizationy ExternalProcessMgmt. Sin embargo, cualquier método que exponga cualquier HostProtectionResource debe ser identificado por un HPA, no solo aquellos que usan recursos que afectan a SQL.
Evite bloquear indefinidamente en código no administrado
El bloqueo en código no administrado en vez de en código administrado puede provocar un ataque de denegación de servicio porque el CLR no puede abortar el subproceso. Un subproceso bloqueado impide al CLR descargar AppDomain, al menos sin tener que realizar algunas operaciones extremadamente no seguras. Bloquear el uso de un primitivo de sincronización de Windows es un ejemplo claro de algo que no se puede permitir. Si es posible, se debe evitar el bloqueo en una llamada a ReadFile
en un socket; idealmente, Windows API debería proporcionar un mecanismo para que una operación como esta tenga un tiempo de espera.
Cualquier método que llame a nativo debería usar idealmente una llamada win32 con un tiempo de espera razonable y finito. Si el usuario puede especificar el tiempo de espera, no se debe permitir que el usuario especifique un tiempo de espera infinito sin algún permiso de seguridad específico. Como guía, si un método se bloqueará durante más de unos 10 segundos, debe usar una versión que admita tiempos de espera o necesita compatibilidad adicional con CLR.
Estos son algunos ejemplos de API problemáticas. Se pueden crear canalizaciones (anónimas y con nombre) con un tiempo de espera, pero el código debe garantizar que nunca llama a CreateNamedPipe
ni WaitNamedPipe
con NMPWAIT_WAIT_FOREVER. Además, puede haber un bloqueo inesperado incluso si se especifica un tiempo de espera. Llamar a WriteFile
en una canalización anónima bloqueará hasta que se escriban todos los bytes, esto significa que, si el búfer contiene datos no leídos, la llamada a WriteFile
se bloqueará hasta que el lector libere espacio en el búfer de la canalización. Los sockets siempre deben usar alguna API que respete un mecanismo de tiempo de espera.
Regla de análisis de código
El bloqueo sin tiempo de espera en código no administrado es un ataque por denegación de servicio. No realice llamadas de invocación de plataforma a WaitForSingleObject
, WaitForSingleObjectEx
, WaitForMultipleObjects
, MsgWaitForMultipleObjects
y MsgWaitForMultipleObjectsEx
. No use NMPWAIT_WAIT_FOREVER.
Identificar cualquier característica de STA-Dependent
Identifique cualquier código que use contenedores uniproceso (STA) de COM. Los STA están deshabilitados en el proceso de SQL Server. Las características que dependen de CoInitialize
, como los contadores de rendimiento o el Portapapeles, deben deshabilitarse en SQL Server.
Asegurarse de que los finalizadores están libres de problemas de sincronización
Es posible que existan varios subprocesos de finalizador en versiones futuras de .NET Framework, lo que significa que los finalizadores para instancias diferentes del mismo tipo se ejecutan simultáneamente. No es necesario que sean totalmente seguros para subprocesos; el recolector de elementos no utilizados garantiza que solo un subproceso ejecutará el finalizador de una instancia de objeto determinada. Sin embargo, los finalizadores deben codificarse para evitar las condiciones de competencia y los bloqueos mutuos al ejecutarse simultáneamente en varias instancias de objeto. Cuando en un finalizador se usa un estado externo, como al escribir en un archivo de registro, los problemas de subprocesos se deben solucionar. No confíe en la finalización para proporcionar seguridad para subprocesos. No use el almacenamiento local del subproceso, administrado o nativo, para almacenar el estado en el subproceso de finalizador.
Regla de análisis de código
Los finalizadores deben estar libres de problemas de sincronización. No use un estado mutable estático en un finalizador.
Evitar la memoria no administrada si es posible
La memoria no administrada puede tener pérdidas, al igual que un identificador del sistema operativo. Si es posible, intente usar memoria de la pila mediante stackalloc o un objeto administrado anclado, como la instrucción fixed o un GCHandle mediante un byte []. El GC acabará por limpiarlos. Sin embargo, si debe asignar memoria no administrada, considere la posibilidad de usar una clase que derive de SafeHandle para encapsular la asignación de memoria.
Tenga en cuenta que hay al menos un caso en el que SafeHandle no es adecuado. Para las llamadas de métodos COM que asignan o liberan memoria, es habitual que un archivo DLL asigne memoria a través de CoTaskMemAlloc
y que otro archivo DLL libere esa memoria con CoTaskMemFree
. El uso de SafeHandle en estos lugares sería inadecuado, ya que intentará vincular la duración de la memoria no administrada a la duración de SafeHandle en lugar de permitir que la otra DLL controle la duración de la memoria.
Revisa todos los usos de Catch(Exception)
Los bloques catch que detectan todas las excepciones en lugar de una excepción específica ahora detectarán también las excepciones asincrónicas. Examine todos los bloques catch(Exception) en busca de código devuelto o de liberación de recursos que no sea importante y que se haya podido pasar por alto, así como comportamientos potencialmente incorrectos dentro del propio bloque catch para controlar una excepción ThreadAbortException, StackOverflowException u OutOfMemoryException. Tenga en cuenta que es posible que este código esté registrando o haciendo algunas suposiciones de que puede ver solo ciertas excepciones, o que siempre que ocurra una excepción, haya fallado por un motivo específico. Estas suposiciones pueden tener que actualizarse para incluir ThreadAbortException.
Considere la posibilidad de cambiar todos los lugares en que se detectan todas las excepciones por la detección de un tipo concreto de excepción que espera que se vaya producir, como una excepción FormatException de métodos de formato de cadena. Esto evita que el bloque catch se ejecute en excepciones inesperadas y ayudará a garantizar que el código no oculta los errores detectando excepciones inesperadas. Como regla general, nunca manejes una excepción en el código de biblioteca, ya que el código que requiere que captures una excepción puede indicar un error de diseño en el código que estás llamando. En algunos casos, puede que desee detectar una excepción y producir un tipo de excepción diferente para proporcionar más datos. Utilice excepciones anidadas en este caso, almacenando la causa real del error en la InnerException, propiedad de la nueva excepción.
Regla de análisis de código
Revise todos los bloques catch en código administrado que detectan todos los objetos o todas las excepciones. En C#, esto significa marcar tanto catch
{} como catch(Exception)
{}. Considere la posibilidad de hacer que el tipo de excepción sea muy específico o revise el código para asegurarse de que no actúa de forma incorrecta si detecta un tipo de excepción inesperado.
No asumas que un hilo administrado es un hilo Win32; es una fibra.
El uso del almacenamiento local del subproceso administrado funciona, pero es posible que no use almacenamiento local de subprocesos no administrados o suponga que el código se ejecutará de nuevo en el subproceso del sistema operativo actual. No cambie ajustes como la configuración regional del subproceso. No llame a InitializeCriticalSection
o CreateMutex
a través de la invocación de plataforma porque requieren que el subproceso del sistema operativo que entra en un bloqueo también salga del bloqueo. Como este no es el caso cuando se usan fibras, las secciones críticas de Win32 y las exclusiones mutuas no se pueden usar directamente en SQL. Tenga en cuenta que la clase administrada Mutex no controla estos problemas de afinidad de subprocesos.
Puede usar sin ningún riesgo la mayor parte del estado en un objeto Thread administrado, incluido el almacenamiento local de subprocesos administrados y la referencia cultural de la interfaz de usuario actual del subproceso. También puede usar ThreadStaticAttribute, lo que hace que el valor de una variable estática existente sea accesible solo por el hilo administrado actual (esta es otra manera de realizar el almacenamiento local de hilos en el CLR). Por motivos del modelo de programación, no puede cambiar la configuración cultural actual de un subproceso cuando se ejecuta en SQL.
Regla de análisis de código
SQL Server se ejecuta en modo de fibra; no use el almacenamiento local de subprocesos. Evite llamadas de invocación de plataforma a TlsAlloc
, TlsFree
, TlsGetValue
y TlsSetValue.
Permitir que SQL Server controle la suplantación
Dado que la suplantación opera en el nivel de subproceso y SQL se puede ejecutar en modo de fibra, el código administrado no debe suplantar a los usuarios y no debería llamar a RevertToSelf
.
Regla de análisis de código
Permitir que SQL Server controle la suplantación. No use RevertToSelf
, ImpersonateAnonymousToken
, DdeImpersonateClient
, ImpersonateDdeClientWindow
, ImpersonateLoggedOnUser
, ImpersonateNamedPipeClient
, ImpersonateSelf
, RpcImpersonateClient
, RpcRevertToSelf
, RpcRevertToSelfEx
ni SetThreadToken
.
No llamar a Thread::Suspend
La capacidad de suspender un subproceso parece una operación sencilla, pero puede producir interbloqueos. Si un subproceso que mantiene un bloqueo queda suspendido por un segundo subproceso y, después, el segundo subproceso intenta tomar el mismo bloqueo, se produce un interbloqueo. En la actualidad, Suspend puede interferir con la seguridad, la carga de clases, la comunicación remota y la reflexión.
Regla de análisis de código
No llame a Suspend. Considere la posibilidad de usar un primitivo de sincronización real en su lugar, como un Semaphore o un ManualResetEvent.
Protección de operaciones críticas con regiones de ejecución restringidas y contratos de confiabilidad
Al realizar una operación compleja que actualiza un estado compartido o que necesita determinísticamente o bien tener éxito por completo o fracasar por completo, asegúrese de que esté protegida por una región de ejecución restringida (CER). Esto garantiza que el código se ejecuta en todos los casos, incluso en una anulación abrupta del subproceso o una descarga abrupta de AppDomain.
Un CER es un bloque determinado try/finally
precedido inmediatamente por una llamada a PrepareConstrainedRegions.
Al hacer esto, se indica al compilador just-in-time que prepare todo el código del bloque finally antes de ejecutar el bloque try
. Esto garantiza que el código del bloque finally se compila y se ejecutará en todos los casos. No es raro en un CER tener un bloque vacío try
. El uso de una CER protege frente a anulaciones de subprocesos asincrónicos y excepciones de memoria insuficiente. Vea ExecuteCodeWithGuaranteedCleanup para obtener una forma de CER que además administra desbordamientos de pila para código excesivamente profundo.