When does a ConcurrentModificationException occur in GAE?

I am reading the official GAE transaction documentation and I cannot figure out when ConcurrentModificationException is ConcurrentModificationException .

Take a look at one of the examples I copied here:

 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(); } } } 

Now all records in the data warehouse (in this example) are completed by a transaction. So why was ConcurrentModificationException ?

Does this happen when any other code that is not in a transaction updates the same object that is modified by the above code? If I guarantee that all the code that updates Entity is always wrapped in a transaction, is it guaranteed that I will not get a ConcurrentModificationException ?

+6
source share
2 answers

I found the answer on the GAE mailing list.

I had a misconception about how transactions work in GAE. I assumed that the start of the transaction blocks any simultaneous updates to the data store until the transaction is complete. This would be a performance nightmare, since all updates would block this transaction, and I am glad that it is not.

Instead, the first update wins, and if a collision is detected in subsequent updates, an exception is thrown.

At first it surprised me because it means that many transactions will require retry logic. But it is similar to the PostgreSQL semantics for the “serialized isolation” layer, although in PostgreSQL you also have the option to block individual rows and columns.

+1
source

It seems that you are doing what they offer you should not : http://code.google.com/appengine/docs/java/datastore/transactions.html#Uses_for_Transactions p>

Attention! The above example shows a transactional counter increment for simplicity only. If your application has counters that are updated frequently, you should not increase them transactionally or even within the same object. The best practice for working with counters is to use a method known as counter-counting.

The above warning may not apply, but what follows after it seems to hint at the problem you are seeing:

This requires a transaction because the value can be updated by another user after this code retrieves the object, but before it saves the modified object. Without a transaction, the user request uses the count value before another user update, and the save overwrites the new value. During a transaction, the application informs about another user update. If the object is updated during a transaction, the transaction ends with a ConcurrentModificationException . An application may retry a transaction to use the new data.

In other words: it seems that someone is modifying your entity without using a transaction, at the same time as you are updating the same object with a transaction.

Note. In extremely rare cases, a transaction is fully executed, even if the transaction returns a timeout or an internal error exception. For this reason, it is best to make transactions idempotent when possible.

Fair warning: I am not familiar with the library, but the above quotes are taken from the documentation showing transaction examples (which is similar to what you sent in the original question).

0
source

Source: https://habr.com/ru/post/905016/


All Articles