[ resolved ] в коллекции "залипает" неверное значение

kill-still

Может кто работал/работает с хазелкастом?
Два потока, один пишет в хазелкаст инкрементящееся поле, второй неперерывно опрашивает коллекцию на предмет того, что в мапе лежит всё ещё старое значение (сверяет его по "эталонному" ConcurrentHashMap). Если обнаружено расхождение, то проверяющий поток засыпает на 100мс, и проверяет снова, и так 20 раз. Если и через 20 раз значение не обновилось, то умирает с кодом 100. По идее расхождение происходит часто, но умирать он никогда не должен. Однако, он может умереть как на втором инкременте, так и пару часов проработать нормально. Непонятно, баг хазелкаста ли это, или сконфигурено что-то не так.
Сорцы можно взять посмотреть тут: http://github.com/Kamapcuc/HazelcastTest
на класспасс надо положить хазелкаст: http://www.hazelcast.com/files/hazelcast-2.5.1.zip

kokoc88

Два потока, один пишет в хазелкаст инкрементящееся поле, второй неперерывно опрашивает коллекцию на предмет того, что в мапе лежит всё ещё старое значение (сверяет его по "эталонному" ConcurrentHashMap). Если обнаружено расхождение, то проверяющий поток засыпает на 100мс, и проверяет снова, и так 20 раз. Если и через 20 раз значение не обновилось, то умирает с кодом 100. По идее расхождение происходит часто, но умирать он никогда не должен. Однако, он может умереть как на втором инкременте, так и пару часов проработать нормально. Непонятно, баг хазелкаста ли это, или сконфигурено что-то не так.
Напиши код в одном классе максимально коротко. Желательно без двух-трёх уровней наследования, без Thread.sleep на Math.random(), с Integer вместо IntObj, и с разовой синхронизацией потоков через wait/notify вместо BlockingQueue.

schipuchka1

1. Добавь плиз в IntObj hashCode, иначе ты соглашения нарушаешь
2. У тебя вочдог бешено закидывает кью, так что когда брейкер тред стартует - там КУЧА значений и возможно ты видишь совсем разные значения по гету
3. Засыпаешь ты на мало, т.к. рандом между 0-м и 1-й

kokoc88

2. У тебя вочдог бешено закидывает кью, так что когда брейкер тред стартует - там КУЧА значений и возможно ты видишь совсем разные значения по гету
Что ты имеешь в виду?

schipuchka1

а, обознался, сорри

kill-still

Секунду, перезагрузится нужно.
UPD: закинул вариант с Integer и без наследования. Сейчас может переделаю с блокирующей очереди на атомик булеан, или семафор, или вэйт/нотифай.

kill-still

Падает в пределах 200 каждый 5-8ой запуск, не ждите долго - быстрее перезапустить.
Проблема фиксится раскомментированием строчки "uncomment to fix", или изменением в конфиге
<cache-value>false</cache-value>

kill-still

Закинул две ветки с AtomicBoolean и wait/notify.
С wait/notify ясен пень ничего не падает, да и толку от этой конструкции никакой.

kill-still

уменьшил время сна до 1мс - как выяснилось, он вообще ни на что не влияет.
при падении интересный вывод консоли:
2915 == 2915 0
2915 == 2915 0
2916 != 2915 1
2917 == 2917 0
2917 == 2917 0
2918 == 2918 0
2919 == 2919 0
2920 == 2920 0
2921 == 2921 0
2922 != 2923 -1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
2923 != 2922 1
Disconnected from the target VM, address: '127.0.0.1:63740', transport: 'socket'
Process finished with exit code 100
какого чёрта он в более поздний момент времени гетит из мапы значение, меньше чем пару мгновений назад?

kokoc88

С wait/notify ясен пень ничего не падает, да и толку от этой конструкции никакой.
Ты вместо этой конструкции используешь ArrayBlockingQueue, в методах get и put происходит то же самое. Код написан не эквивалентный: в первом случае ты ждёшь, потом кладёшь; во втором - наоборот. То есть в первом случае поток может сработать два раза подряд: прерваться сразу после успешного take(), продолжиться и не заблокироваться на втором вызове take().
У тебя замена hazelcast на ConcurrentHashMap работает без проблем?

kill-still

У тебя замена hazelcast на ConcurrentHashMap работает без проблем?
Попробовал, подождал три минуты, вроде не падает, даже расхождений почти нет.
Как я уже писал - я два пути решения проблемы нашёл, но меня интересует именно то, почему так происходит. Пока что я склоняюсь именно к багу хазелкаста - дебажу его исходники сейчас.

kokoc88

почему так происходит
Копать от двух быстрых вызовов put() подряд из одного потока.
Эти случаи должны отображаться в консоли как -2 при использовании ConcurrentHashMap.

kill-still

я не видел ни разу, чтобы на -2 расходилось.
а при использовании ConcurrentHashMap и на 1 редко расходится

kokoc88

я не видел ни разу, чтобы на -2 расходилось.
При использовании ConcurrentHashMap может иногда расходиться на -2, я уже написал причину таких расхождений.

kill-still

Ты прям получал такую ситуацию на своей машине?
тоже получилось, подождать пришлось только "немного":

kokoc88

Ты прям получал такую ситуацию на своей машине?
Если ты не можешь получить какую-то ситуацию, это вовсе не означает, что её не может быть.
Первый поток может отработать свой квант времени до записи нового значения, но после чтения очереди; другой поток в это время добавляет в очередь true, после чего переходит по циклу на проверку, где теряет квант времени после чтения из одного отображения; просыпается первый поток, записывает значение в отображение, сразу же читает очередь и записывает новое значение; просыпается второй поток и читает из второго отображения число +2.
Вообще так многопоточный код не пишется, потому что слишком сложно его анализировать.

kill-still

Не-не-не, Майк, я сейчас не про то. Я понял про то, что может быть -2 (а вот 3 уже нет). Расхождения - это нормально. Проблема в том, что когда два ConcurrentHashMap, то эти расхождения сходятся, а с хазелкастом - нет.
ConcurrentHashMap + ConcurrentHashMap:
43182217 == 43182217
43182217 == 43182217
43182217 == 43182217
43182217 != 43182219 +2
43182219 == 43182219
43182220 == 43182220
43182221 == 43182221

kill-still

ConcurrentHashMap + Hazelcast (при cache-value=false):

124854 == 124854
124854 == 124854
124854 != 124856 +2
124856 == 124856
124857 == 124857
124858 == 124858

kill-still

Попробую вкратце описать юзкейс:
Есть кластер с ElasticSearch. Данные с него кэшируются в кластере хазелкаста.
При обновлении записи нам надо версионно записать в эластик и после индексирования запись с той же самой версией положить в хазелкаст. Внутри одной этой "конструкции" всё работает замечательно.

Теперь кросс-кластера приходит Sync - это такая же запись, но у неё отстутствует поле версия (кросс-кластерная связь может отсутствовать, и в одном массиве кластеров запись могли поменять N раз, а в дугом M раз, поэтому некорректно передавать поле версия).
Какой-нибудь политикой смёрдживаем текущую и Sync, и если надо, пишем в "конструкцию". Проблема в том, что хазелкаст иногда забывает, что запись в нём меняли и возвращает старую версию. После этого другая транзакция не может выполнится, потому что берёт из хазелкаста старую версию и пытается версионно писать в эластик, а тот её законно посылает из-за конфликта версий. Лок на запись или тем более целую мапу брать мы не можем, потому что это медленно. То, что более старая запись затрёт более новую - не важно. Главное чтобы в поле версия в хазелкасте было актуальным, иначе эластик не даст записать Sync. Такая ситуация на реальных данных не возникает - только во время тестов, когда мы засыпаем сервер кучей Sync-ов.
Затереть версию в хазелкасте на актуальную не проблема. Но мне это не нравится и хочется разобраться, почему такая фигня происходит.
Я обрезал от кода всё лишнее, получилось вот то, что выложил на гитхаб. WatchDog это как бы та шняга, которая опрашивает кросскластера на наличие Synс-ов и скармливает их на запись. Пишет только один поток, и я что-то не могу понять, как так получается что после того, как он закончил работать данные различаются. То, что пока он работает они различаются - это нормально.

kill-still

Нашёл в общем где у них косяк:
http://github.com/hazelcast/hazelcast/blob/master/hazelcast...
 public Object getValue() {
if (cmap.isCacheValue()) {
final Object currentValue = valueObject;
if (currentValue != null) {
return currentValue;
}
synchronized (this) {
if (valueObject != null) {
return valueObject;
}
final Object v = toObject(value);
valueObject = v;
return v;
}

} else {
return toObject(value);
}
}

есть маленькая вероятность, что в valueObject закешируется значение отличное от того, что сейчас реально лежит в мапе.
Написал в поддержку, жду что ответят.

kill-still

кладёт записи в мапу он тут:
http://github.com/hazelcast/hazelcast/blob/master/hazelcast...
    class PutOperationHandler extends SchedulableOperationHandler {
.........
void doOperation(Request request) {
.........
cmap.put(request);

kill-still

тут патч, исправляющий баг:
Оставить комментарий
Имя или ник:
Комментарий: