2011-09-16 32 views
6

Tengo que escribir una prueba de unidad que provoca una condición de carrera para poder probar si probablemente solucioné el problema más adelante. El problema es que la condición de carrera solo ocurre muy raramente, tal vez porque mi computadora tiene solo dos núcleos.Provocar una condición de carrera en Java

El código es algo como lo siguiente:

class MyDateTime { 
    String getColonTime() { 
    // datetime is some kind of lazy caching variable declared somewhere(does not matter) 
    if (datetime == null) { 
     initDateTime(); //Uses lazy to initlialize variable, takes some time 
    } 
    // Colon time stores hh:mm as string 
    if (datetime.colonTime == null) { 
     StringBuilder sb = new StringBuilder(); 
     //Now do some steps to build the hh:mm string 
     //... 
     //set colon time 
     datetime.colonTime = sb.toString(); 
    } 
    return datetime.colonTime; 
    } 
} 

Explicación: initDateTime asigna una nueva instancia de fecha y hora, para ello, datetime.colonTime es nulo después (ya que queremos inicializarlo perezoso, como indiqué antes de). Ahora, si el subproceso A ingresa al método y luego el programador lo detiene justo antes de que pueda ejecutar initDateTime(). El subproceso B ahora runst getColonTime(), ve que datetime sigue siendo nulo y lo inicializa. datetime.colonTime es nulo, por lo tanto, el segundo bloque if se ejecuta y datetime.colonTime obtiene el valor de StringBuilder. Si el programador detiene el hilo entre esta línea y la declaración de retorno y reanuda el hilo A, ocurrirá lo siguiente: Como A se detuvo justo antes de llamar a initDateTime, A ahora llama a initDateTime(), que tipo de reiniciará objeto datetime, estableciendo datetime.colonTime para anular nuevamente. Luego, el hilo A ingresará al segundo bloque if, pero el programador interrumpirá A antes de datetime.colonTime = sb.toString(); se llama. Como conclusión, dateTime.colonTime sigue siendo nulo. Ahora el planificador reanuda B y el método devuelve nulo.

Me trataron de provocar la condición de carrera por tener un número de hilos de llamada getColonTime() a una sola instancia (final) de MyDateTime, pero sólo falla en algunos casos raros extreeemly :( Alguna pista cómo escribir un JUnit "prueba"?

+3

Podría intentar primero utilizar el depurador para provocar la condición de carrera. Es decir. inicie un hilo, péguelo en un punto de interrupción (como entre if) y comience otro - y así sucesivamente. Después de que tengas la idea de cómo ocurre RC (parece que ahora no tienes nada de eso) podrías escribir una prueba de unidad exitosa – pupssman

+0

No veo cómo podrías llegar al 'datetime de devolución '.colonTime; 'y recuperar null. ¿Estás seguro de que no hay problema con la forma en que estás construyendo la cadena hh: mm? Tal vez agregue ese código a su pregunta para que podamos ver eso. – Windle

+0

Agregué algunas explicaciones adicionales sobre por qué puede suceder. Tengo que admitir que no es muy obvio – user3001

Respuesta

4

Puede consultar Thread Weaver, o puede haber otros marcos para probar código de subprocesos múltiples. No lo he usado, pero el Users' Guide parece como si estuviera diseñado para este tipo de prueba.

+0

Ya lo vi, pero no sé qué tan adecuado es. Experiencias con eso, ¿alguien? – user3001

+0

Probé con el tejedor de hilos. Funciona muy bien para probar si sabes dónde está el problema. Sin embargo, no puede hacer pruebas N x N sobre líneas de un método (pregunté en el grupo de discusión). – user3001

7

Como mencionas, las condiciones de carrera son extremadamente difíciles de reproducir consistentemente. Sin embargo, la ley de promedios está de tu lado. Si creas una prueba que esperas fallar quizás una en cien veces, y luego haz que suceda miles de veces, es probable que detectes el error de manera bastante consistente en tu código anterior. Por lo tanto, de acuerdo con los principios TDD, debes comenzar con el código de la forma en que era antes, crear una prueba itera suficientes veces fallar consistentemente contra el código anterior, luego cambie a su nuevo código y asegúrese de que no falle.

+0

Puede tener una buena probabilidad de que la prueba falle, pero a mayor probabilidad de que exija, más tiempo tardará la prueba en ejecutarse. Se supone que las pruebas unitarias son rápidas. (Pero no tengo una mejor idea.) –

0

Sé que esta publicación es bastante antigua, pero me encuentro en una situación similar. Lo que tiendo a hacer es favorecer la condición de carrera con duerme.

En su caso, me gustaría hacer algo como

class MyDateTime { 
     String getColonTime() throws InterruptedException{ 
      if (datetime == null) { 
       Thread.sleep(new Random().nextInt(100); //Wait to enhance the chances that multiple threads enter here and reset colonTime. 
       initDateTime(); 
      } 
      Thread.sleep(new Random().nextInt(100); //Wait to enhance the chances that colonTime stays null for a while. 
      if (datetime.colonTime == null) { 
       StringBuilder sb = new StringBuilder(); 
       datetime.colonTime = sb.toString(); 
      } 
      Thread.sleep(new Random().nextInt(100); //Wait to favour reset of colonTime by another thread in the meantime. 
      return datetime.colonTime; 
     } 
    } 

Pero está claro que esto se convierte en desordenado con bastante rapidez. Ojalá hubiera alguna manera de forzar al programador a explorar todos los caminos dados algunos "puntos de quiebre".

Como la publicación es un poco vieja, me preguntaba si encontraste buenas formas de probar las condiciones de carrera en Java. ¿Algún consejo para compartir?

Gracias

+1

Como dije en la respuesta aceptada, Thread weaver fue una gran herramienta y creo que ahora hay otras. Por ejemplo, IntelliJ recibió soporte de depuración multiproceso si lo recuerdo correctamente. Además, las estadísticas están de su parte: si repite una prueba con la suficiente frecuencia, con toda seguridad le pegará a la condición de carrera de vez en cuando :) También vea https://github.com/junit-team/junit4/wiki/Multithreaded-code y concurrencia – user3001

Cuestiones relacionadas