Когда в GAE выбрасывается исключение ConcurrentModificationException? - PullRequest
6 голосов
/ 04 января 2012

Я читаю официальную документацию GAE по транзакциям и не могу понять, когда выбрасывается ConcurrentModificationException.

Посмотрите на один из примеров, которые я копирую здесь:

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

Теперь все записи в хранилище данных (в этом примере) заключены в транзакцию. Так почему бы ConcurrentModificationException быть брошенным?

Случается ли это, когда какой-то другой код, который не заключен в транзакцию, обновляет ту же сущность, которая модифицируется вышеуказанным кодом? Если я гарантирую, что весь код, который обновляет сущность, всегда заключен в транзакцию, гарантируется ли, что я не получу ConcurrentModificationException?

Ответы [ 2 ]

1 голос
/ 04 января 2012

Я нашел ответ в списке рассылки GAE.

У меня было неверное представление о том, как транзакции работают в GAE.Я предполагал, что начало транзакции заблокирует любые одновременные обновления хранилища данных, пока транзакция не будет зафиксирована.Это было бы кошмаром производительности, так как все обновления блокировали бы эту транзакцию, и я рад, что это не так.

Вместо этого происходит следующее: первое обновление выигрывает, и если обнаруживается конфликтв последующих обновлениях выдается исключение.

Сначала это меня удивило, потому что это означает, что для многих транзакций потребуется логика повторов.Но это похоже на семантику PostgreSQL для уровня «сериализуемой изоляции», хотя в PostgreSQL у вас также есть возможность блокировать отдельные строки и столбцы.

0 голосов
/ 04 января 2012

Кажется, что вы делаете то, что вам предлагают не должно делать: http://code.google.com/appengine/docs/java/datastore/transactions.html#Uses_for_Transactions

Внимание! Вышеприведенный пример показывает транзакционное увеличение счетчика только для простоты. Если в вашем приложении есть счетчики, которые часто обновляются, вы не должны увеличивать их транзакционно или даже внутри одной сущности. Лучшей практикой для работы со счетчиками является использование техники, известной как контр-шардинг.

Возможно, вышеупомянутое предупреждение не применимо, но то, что следует после того, как оно, кажется, намекает на проблему, которую вы видите:

Для этого требуется транзакция, поскольку значение может быть обновлено другим пользователем после того, как этот код выберет объект, но до того, как он сохранит измененный объект. Без транзакции запрос пользователя использует значение count до обновления другого пользователя, и сохранение перезаписывает новое значение. При транзакции приложению сообщается об обновлении другого пользователя. Если объект обновляется во время транзакции, то транзакция завершается с ошибкой ConcurrentModificationException. Приложение может повторить транзакцию, чтобы использовать новые данные.

Другими словами: кажется, что кто-то изменяет вашу сущность без использования транзакции, в то время как вы обновляете ту же сущность с помощью транзакции.

Примечание. В крайне редких случаях транзакция полностью фиксируется, даже если транзакция возвращает исключение тайм-аута или внутренней ошибки. По этой причине лучше делать транзакции идемпотентными, когда это возможно.

Справедливое предупреждение: я не знаком с библиотекой, но приведенные выше цитаты были взяты из документации, показывающей примеры транзакций (что похоже на то, что вы разместили в исходном вопросе).

...