Pregunta

Tenemos una tarea asíncrona que realiza un cálculo potencialmente larga de un objeto. El resultado se almacena en caché en el objeto. Para evitar múltiples tareas de repetir el mismo trabajo, hemos añadido bloqueo con una actualización de SQL atómica:

UPDATE objects SET locked = 1 WHERE id = 1234 AND locked = 0

El bloqueo es sólo para la tarea asíncrona. El objeto en sí todavía puede ser actualizada por el usuario. Si eso sucede, cualquier tarea pendiente para una versión antigua del objeto debe descartar sus resultados, ya que es probable fuera de fecha. Esto también es bastante fácil de hacer con una actualización de SQL atómica:

UPDATE objects SET results = '...' WHERE id = 1234 AND version = 1

Si el objeto se ha actualizado, su versión no coincide con lo que los resultados serán descartados.

Estas dos actualizaciones atómicas deben manejar las posibles condiciones de carrera. La cuestión es cómo comprobar que en las pruebas unitarias.

El primer semáforo es fácil de probar, ya que es simplemente una cuestión de la creación de dos pruebas diferentes con los dos escenarios posibles: (1) cuando el objeto está bloqueado y (2) cuando el objeto no está bloqueado. (No es necesario para probar la atomicidad de la consulta SQL como esa debe ser la responsabilidad del proveedor de base de datos.)

¿Cómo se puede probar la segunda semáforo? El objeto necesita ser cambiado por un tercero, algún tiempo después del primer semáforo, pero antes de la segunda. Esto requeriría una pausa en la ejecución para que la actualización se puede realizar de forma fiable y consistente, pero no conozco ningún apoyo para la inyección de puntos de ruptura con RSpec. ¿Hay alguna forma de hacer esto? ¿O hay alguna otra técnica estoy pasando por alto para simular las condiciones de carrera?

¿Fue útil?

Solución

Puede prestada una idea de la fabricación de productos electrónicos y poner ganchos de prueba directamente en el código de producción. Del mismo modo que una placa de circuito se puede fabricar con lugares especiales para el equipo de prueba para el control y para sondear el circuito, podemos hacer lo mismo con el código.

Supongamos que tenemos un cierto código insertar una fila en la base de datos:

class TestSubject

  def insert_unless_exists
    if !row_exists?
      insert_row
    end
  end

end

Sin embargo, este código se ejecuta en varios equipos. Hay una condición de carrera, entonces, ya que otros procesos pueden insertar la fila entre nuestra prueba y nuestra inserción, causando una excepción DuplicateKey. Queremos probar que nuestro código controla la excepción de que el resultado de que la condición de carrera. Con el fin de hacer eso, nuestra prueba tiene que insertar la fila después de la llamada a row_exists? pero antes de la llamada a insert_row. Así que vamos a añadir un gancho de prueba allí mismo:

class TestSubject

  def insert_unless_exists
    if !row_exists?
      before_insert_row_hook
      insert_row
    end
  end

  def before_insert_row_hook
  end

end

Cuando se ejecuta en la naturaleza, el gancho no hace nada excepto comer una pequeña cantidad de tiempo de CPU. Pero cuando el código está siendo probado para la condición de carrera, la prueba de mono-parches before_insert_row_hook:

class TestSubject
  def before_insert_row_hook
    insert_row
  end
end

No es astuto? Al igual que una larva de avispa parásita que ha secuestrado el cuerpo de una oruga desprevenido, la prueba secuestró el código bajo prueba para que se va a crear la condición exacta que necesitamos a prueba.

Esta idea es tan simple como el cursor XOR, así que sospecho que muchos programadores han inventado independientemente ella. He encontrado que es útil en general para el ensayo de código con las condiciones de carrera. Espero que ayude.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top