vulnerability seccion race hilos ejemplo critica condiciones condicion competencia carrera c# asp.net azure cloud

c# - seccion - race condition vulnerability



¿La mejor manera de evitar condiciones de carrera en un entorno web de varias instancias? (5)

Supongamos que tiene una acción en ASP.NET MVC en un entorno de varias instancias que se parece a esto *:

public void AddLolCat(int userId) { var user = _Db.Users.ById(userId); user.LolCats.Add( new LolCat() ); user.LolCatCount = user.LolCats.Count(); _Db.SaveChanges(); }

Cuando un usuario presiona repetidamente un botón o se actualiza, se producen condiciones de carrera, lo que hace posible que LolCatCount no sea similar a la cantidad de LolCats.

Pregunta

¿Cuál es la forma común de solucionar estos problemas? Podría arreglarlo desde el lado del cliente en JavaScript, pero eso no siempre es posible. Es decir, cuando sucede algo en una actualización de página, o porque alguien está jodiendo en Fiddler.

  • ¿Supongo que tienes que hacer algún tipo de bloqueo basado en la red?
  • ¿De verdad tienes que sufrir la latencia extra por llamada?
  • ¿Puedes decirle a una Acción que solo se puede ejecutar una vez por Usuario?
  • ¿Existe algún patrón común que puedas usar? Como un filtro o atributo?
  • ¿Vuelve temprano, o realmente bloquea el proceso?
  • Cuando regresa temprano, ¿hay un código de respuesta / respuesta "establecido" que deba devolver?
  • Cuando usa un candado, ¿cómo evita el ahogamiento de hilos con procesos (semi) largos?

* solo un estúpido ejemplo mostrado por brevedad. Los ejemplos del mundo real son mucho más complicados.


Una posible solución es evitar la redundancia que puede conducir a datos inconsistentes. es decir, si se puede determinar LolCatCount en tiempo de ejecución, entonces determine en tiempo de ejecución en lugar de persistir esta información redundante.


Por "multi-instancia", obviamente se está refiriendo a una granja de servidores web o tal vez a una situación de huerto web donde simplemente usar un mutex o monitor no será suficiente para serializar las solicitudes.

Entonces ... ¿tienes una sola base de datos en el back-end? ¿Por qué no usar una transacción de base de datos?

Parece que probablemente no desee forzar el acceso serializado a esta sección de código para todas las identificaciones de usuario, ¿verdad? ¿Desea serializar las solicitudes por ID de usuario?

Me parece que lo correcto al respecto es serializar el acceso a los datos fuente, que son los registros de LolCats en la base de datos.

Me gusta la idea de desactivar el botón o enlace en el navegador mientras dure una solicitud, para evitar que el usuario presione el botón una y otra vez antes de que las solicitudes anteriores finalicen el procesamiento y el retorno. Parece un paso bastante fácil con muchos beneficios.

Pero dudo que eso sea suficiente para garantizar el acceso serializado que desea aplicar.

También podría implementar el estado de sesión compartida e implementar algún tipo de bloqueo en un objeto basado en sesión, pero probablemente necesitaría ser una colección (de identificación de usuario) para aplicar el paradigma de serialización por usuario.

Yo votaría por usar una transacción de base de datos.


Respuesta 1: (El enfoque general)
Si el almacén de datos admite transacciones, puede hacer lo siguiente:

using(var trans = new TransactionScope(.., ..Serializable..)) { var user = _Db.Users.ById(userId); user.LolCats.Add( new LolCat() ); user.LolCatCount = user.LolCats.Count(); _Db.SaveChanges(); trans.Complete(); }

esto bloqueará el registro del usuario en la base de datos y hará que otras solicitudes esperen hasta que se haya confirmado la transacción.

Respuesta 2: (Solo es posible con un solo proceso)
La habilitación de sesiones y el uso de la sesión generarán un bloqueo implícito entre las solicitudes del mismo usuario (sesión).

Session["TRIGGER_LOCKING"] = true;

Respuesta 3: (Ejemplo específico)
Deducir el número de LolCats de la colección en lugar de seguirlo en un campo separado y así evitar problemas de inconsistencia.

Respuestas a tus preguntas específicas:

  • ¿Supongo que tienes que hacer algún tipo de bloqueo basado en la red?
    sí, los bloqueos de la base de datos son comunes

  • ¿De verdad tienes que sufrir la latencia extra por llamada?
    ¿Que qué?

  • ¿Puedes decirle a una Acción que solo se puede ejecutar una vez por Usuario?
    Podría implementar un atributo que use el bloqueo de sesión implícito o alguna variante personalizada del mismo, pero eso no funcionará entre procesos.

  • ¿Existe algún patrón común que puedas usar? Como un filtro o atributo?
    La práctica común es usar bloqueos en la base de datos para resolver el problema de varias instancias. No hay filtro o atributo que yo sepa.

  • ¿Vuelve temprano, o realmente bloquea el proceso?
    Depende de tu caso de uso. Comúnmente esperas ("bloquear el proceso"). Sin embargo, si tu tienda de base de datos admite el patrón async / await, harías algo como

    var user = await _Db.Users.ByIdAsync(userId);

    esto liberará el hilo para hacer otro trabajo mientras espera el bloqueo.

  • Cuando regresa temprano, ¿hay un código de respuesta / respuesta "establecido" que deba devolver?
    No lo creo, elija algo que se adapte a su caso de uso.

  • Cuando usa un candado, ¿cómo evita el ahogamiento de hilos con procesos (semi) largos?
    Supongo que deberías considerar usar colas.


Bueno, MVC es apátrida, lo que significa que tendrás que manejarlo manualmente. Desde una perspectiva purista, recomendaría evitar las prensas múltiples utilizando un bloqueo del lado del cliente , aunque mi preferencia es desactivar el botón y aplicar una CSSClass apropiada para demostrar su estado desactivado. Supongo que mi razonamiento es que no podemos determinar completamente al consumidor de la acción, por lo tanto, mientras proporciona el ejemplo de Fiddler, no hay manera de determinar verdaderamente si se aplican o no varios clics.

Sin embargo, si desea utilizar un mecanismo de bloqueo del lado del servidor, este artículo proporciona un ejemplo que almacena la información del solicitante en el caché del lado del servidor y devuelve una respuesta adecuada en función del tiempo de espera / acciones que desea implementar.

HTH


Sugiero, y personalmente uso mutex en este caso.

He escrito aquí: problemas de liberación de Mutex en el código ASP.NET C # , una clase que maneja mutex pero puede hacer la suya propia.

Así que basándote en la clase de esta respuesta, tu código se verá así:

public void AddLolCat(int userId) { // I add here some text in front of the number, because I see its an integer // so its better to make it a little more complex to avoid conflicts var gl = new MyNamedLock("SiteName." + userId.ToString()); try { //Enter lock if (gl.enterLockWithTimeout()) { var user = _Db.Users.ById(userId); user.LolCats.Add( new LolCat() ); user.LolCatCount = user.LolCats.Count(); _Db.SaveChanges(); } else { // log the error throw new Exception("Failed to enter lock"); } } finally { //Leave lock gl.leaveLock(); } }

Aquí el bloqueo se basa en el usuario, por lo que diferentes usuarios no se bloquearán entre sí.

Acerca del bloqueo de sesión

Si usa la sesión asp.net en su llamada, entonces puede ganar un "boleto" de bloqueo gratis de la sesión. La sesión bloquea cada llamada hasta que se devuelve la página.

Lea sobre eso en este q / a:
Aplicación web bloqueada al procesar otra aplicación web al compartir la misma sesión
¿Los formularios web de ASP.NET evitan un envío de doble clic?
Las llamadas jQuery Ajax al servicio web parecen ser síncronas