2012-01-03 22 views
6

Estoy leyendo el GAE oficial documentation on transactions y no puedo entender cuando se arroja un ConcurrentModificationException.¿Cuándo se lanza la ConcurrentModificationException en GAE?

vistazo a uno de los ejemplos que estoy copiar y pegar aquí:

int retries = 3; 
while (true) { 
    Transaction txn = datastore.beginTransaction(); 
    try { 
     Key boardKey = KeyFactory.createKey("MessageBoard", boardName); 
     Entity messageBoard = datastore.get(boardKey); 

     long count = (Long) messageBoard.getProperty("count"); 
     ++count; 
     messageBoard.setProperty("count", count); 
     datastore.put(messageBoard); 

     txn.commit(); 
     break; 
    } catch (ConcurrentModificationException e) { 
     if (retries == 0) { 
      throw e; 
     } 
     // Allow retry to occur 
     --retries; 
    } finally { 
     if (txn.isActive()) { 
      txn.rollback(); 
     } 
    } 
} 

Ahora, todas las escrituras en el almacén de datos (en este ejemplo) se envuelven en virtud de una transacción. Entonces, ¿por qué se lanzaría un ConcurrentModificationException?

¿Ocurre cuando algún otro código que no está envuelto en una transacción actualiza la misma entidad que está siendo modificada por el código anterior? Si me aseguro de que todo el código que actualiza una entidad siempre esté envuelto en una transacción, ¿se garantiza que no recibiré un ConcurrentModificationException?

Respuesta

1

Encontré la respuesta en la lista de correo de GAE.

Tenía una noción errónea de cómo funcionan las transacciones en GAE. Imaginé que comenzar una transacción bloqueará las actualizaciones concurrentes en el almacén de datos hasta que la transacción se comprometa. Eso hubiera sido una pesadilla de rendimiento ya que todas las actualizaciones se bloquearían en esta transacción y estoy feliz de que este no sea el caso.

En cambio, lo que ocurre es que la primera actualización gana, y si se detecta una colisión en las actualizaciones posteriores, se lanza una excepción.

Esto me sorprendió al principio, porque significa que muchas transacciones necesitarán una lógica de reintento. Pero parece similar a la semántica de PostgreSQL para el nivel de "aislamiento serializable", aunque en PostgreSQL también tiene la opción de bloquear filas y columnas individuales.

+0

¿Cómo es eso diferente del segundo párrafo de la cita que proporcioné? – Kiril

+0

Lirik, no es muy diferente de la cita que incluyó. Pero su resumen debajo de la cita es diferente al mío, y hay algunas suposiciones y citas no relacionadas en la respuesta. Por lo tanto, hice una nueva respuesta mientras aclaraba mis propias confusiones. Gracias por participar en esta búsqueda. – HRJ

+0

Bueno, lo importante es que tienes una respuesta:) ... – Kiril

0

Parece que estás haciendo lo que te sugieren no debe hacer: http://code.google.com/appengine/docs/java/datastore/transactions.html#Uses_for_Transactions

Advertencia! La muestra anterior representa un incremento transaccional de un contador solo por simplicidad. Si su aplicación tiene contadores que se actualizan con frecuencia, no debe incrementarlos transaccionalmente, ni siquiera dentro de una sola entidad. Una mejor práctica para trabajar con contadores es usar una técnica conocida como contra-sharding.

Tal vez la advertencia anterior no se aplica, pero lo que sigue después de lo que parece hacer alusión a la cuestión que se está viendo:

Esto requiere una transacción debido a que el valor puede ser actualizado por otro usuario después de este código busca el objeto, pero antes guarda el objeto modificado. Sin una transacción, la solicitud del usuario utiliza el valor de recuento antes de la actualización del otro usuario, y el guardado sobrescribe el nuevo valor. Con una transacción, se informa a la aplicación sobre la actualización del otro usuario. Si la entidad se actualiza durante la transacción, la transacción falla con un ConcurrentModificationException. La aplicación puede repetir la transacción para usar los datos nuevos.

En otras palabras: parece que alguien está modificando su entidad sin utilizar una transacción al mismo tiempo que está actualizando la misma entidad con una transacción.

Nota: En casos extremadamente raros, la transacción está totalmente comprometida incluso si una transacción devuelve un tiempo de espera o una excepción de error interno. Por esta razón, es mejor hacer las transacciones idempotentes siempre que sea posible.

Una advertencia justa: No estoy familiarizado con la biblioteca, pero las citas anteriores se tomaron de las operaciones de la muestra documentación que demuestre (que parece idéntico a lo que has publicado en la pregunta original).

+0

Esa advertencia es por una razón completamente diferente: el rendimiento. En el contexto de una transacción, es un ejemplo perfectamente válido. – HRJ

+0

HRJ, en el párrafo después de la advertencia, también establece que la transacción puede fallar con una 'ConcurrentModificationException' si la entidad se actualiza durante la transacción. Supongo que esto es lo que está sucediendo en este caso. – Kiril

+0

gracias por intentar responder la pregunta. Ya sé lo que hay en la documentación. Necesito una respuesta sin suposiciones. – HRJ

Cuestiones relacionadas