LINUX.ORG.RU

Проверка предположений в Java

 


0

2

Как известно, треш, угар и shared mutable state являются основными ценностями человека (а для кого не является - тот не человек, например). Посему возникает животрепещущий вопрос

расскажите, как хорошим образом сделать глобальные инварианты для блока кода (в concurrent случае)

например, мы хотим проверить, что на протяжении всего исполнения произвольного блока кода, должно выполняться условие «x > 0». А этот x - это shared mutable state, и его может поменять другой поток, поэтому его недостаточно проверять только на входе метода, нужно проверять каждый чекпоинт («каждую строчку»).

но ведь в джаве нет такой фичи. Можно AOPить целиком методы, но не строчки (а что там генерит компилятор на самом деле вообще не видимо глазу чтобы сказать, что вот на этот минимально неделимый кусок intermediate representation я хочу повесить проверку).

и даже для куска IR у нас может случиться тупо мисалайнмент (direct byte buffers на jdk > 6?), мы прочитаем в x того, чего там и не было вообще, и проверка пойдет билгейцу под хвост (кстати, когда случается мисалаймент в джаве?)

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

можно запустить проверку в отдельном треде - тоже ведь не вариант, там будет происходить черти что. Да и всё равно мы не можем убить тред по своему желанию (если мы не конченые отморози - тогда можем), и нужно будет подпихнуть в целевой тред точки останова.

Ну можно «макросами» переписать сам исходник и реально обернуть каждую строчку в проверки перед сборкой. Или переписать в байткод?

Как это делают правильные, русские java мужики?

Связанный тред в фейсбуке: https://www.facebook.com/olegchir/posts/1157968160892856

★★★★☆

Последнее исправление: stevejobs (всего исправлений: 1)

его может поменять другой поток, поэтому его недостаточно проверять только на входе метода

А что нельзя прикрутить признак «значение менялось» и проверять его?

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

где прикрутить?

изначально предполагалась схема в общем виде: произвольный блок кода (совсем произвольный - в т.ч. проходящий через границы вызова методов) проверяется на произвольное предположние (совсем произвольное, «чо хочу то и проверил»)

если есть какие-то крутые частные случаи (например, статический анализатор?), давай их сюда

stevejobs ★★★★☆
() автор топика
Последнее исправление: stevejobs (всего исправлений: 1)
Ответ на: комментарий от stevejobs

где прикрутить?

В x. При изменении x признак устанавливается.

схема в общем виде

Так это же observer, не?

Если ты хочешь не просто проверять, а задать условие для выполнения кода (условие нарушено - код прерывается/бросается исключение) то ИМХО это невозможно для concurrent.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от stevejobs

Варинт 2, в духе stm

Делаем глобальный shared state x и признак «x менялся». В потоке имеем локальную копию x. При входе в защищённую секцию копируем глобальный x в локальный, работаем ничего не проверяем. По окончании, проверяем глобальный x. Если он не менялся, всё ок, условие выполнялось на протяжении всей защищённой секции, обновляем глобальный x. Если он менялся, условие было нарушено, делаем харакири.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от no-such-file

произвольное условие для выхода

синхронное (вылетаем на той же строчке где случилось нарушение) или асинхронное («догоняющая смерть»)

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

это возможно. Например так: реализуем java отладчик (или просто копипастим его из эклипсы или идеи) и запускаем код под ним, расставляем breakpoints на каждой строчке нужного блока, и для проверки условия используем тот же механизм что отладчики используют для watch list.

Один пример реалзизации есть - значит доказано что утверждение «это невозможно» не верно

но остается куча вопросов. Например, что за механизм позволяет watch list останавливать мир?

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

stevejobs ★★★★☆
() автор топика
Последнее исправление: stevejobs (всего исправлений: 1)

Берешь какой-нибудь bytecode editor типа javassist и трахаешься.

slyjoeh ★★★
()

Можешь написать java agent, который будет переписывать байткод нужных методов и вставлять там твои проверки. Чтобы данные были актуальны, заверни их в immutable объект и при изменении обновляй volatile указатель на этот объект.

Legioner ★★★★★
()
Ответ на: комментарий от stevejobs

реализуем java отладчик

Т.е. по факту свою кастомную vm. Это как бы выходит за рамки «возможно в java».

ставляем breakpoints на каждой строчке нужного блока

A это уже не concurrent.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 2)
Ответ на: комментарий от stevejobs

Например, если мы реально на каждой строчке руками пишем boilerplate для проверки предположения, в какой конструкции это выглядит наиболее изящно?

Это тупо не работает, т.к. значение x может измениться после успешной проверки перед выполнением строчки.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

A это уже не concurrent.

Почему не concurrent? Что случается с другими тредами, когда один из них попадает на точку останова? Каждый следующий переход по строчкам в дебаггере будет продвигать все треды на квант «времени», или только текущий, как это работает?

Это тупо не работает, т.к. значение x может измениться после успешной проверки перед выполнением строчки.

есть предложения лучше?

ну например замутить функцию compare-and-run ? Точно такую же как CAS, только R вместо S

stevejobs ★★★★☆
() автор топика
Ответ на: комментарий от stevejobs

Почему не concurrent?

Потому что дебагер работает в 1 поток и все потоки с брекпоинтами будут ждать пока дебагер их обработает. Т.е. фактически все будет выполняться в последовательно в 1 поток (дебагера). Если дебагер работает в многопоток, то проблема вылезает снова, но уже в дебагере.

no-such-file ★★★★★
()
Ответ на: комментарий от stevejobs

замутить функцию compare-and-run

А смысл? Ну допустим значение x поменяется во время «run» и что теперь, сделать и «run» атомарным? Это мутекс, вид сбоку. Нет пути.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от stevejobs

Тебя что смущает

Меня смущает, что это синхронное выполнение, а не concurrent. Т.е. можно так не заморачиваться, а просто использовать synchronized.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)

Правильные русские java мужики не парятся дурью. А если дурь и пришла, то есть observer со списком реакций на новое значение.

vtVitus ★★★★★
()

Открой для себя transactions.
Изменилось(ись) ли shared mutable state(s) - проверяй в конце транзакции и делай commit/rollback.
А потом посиди и подумай как реализовать прерывание выполнения транзакции,
если твои shared mutable states изменились.
Это уже на случай если подгорает от того факта, что транзакции будут выполняться до конца,
не зависимо от изменений твоих shared mutable states, и если это сильно ударит по перфомансу.

goodwin ★★
()
Ответ на: комментарий от goodwin

о, вот это уже интересно. в джаву завезли drop-in replacement обычной памяти на транзакционную? а в процессоры?

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

ну допустим мы пошли в ведро и прям руками выкрутили ручку, сказали что мы верим транакционной памяти. Как эту фичу теперь совместить с Джабой?

stevejobs ★★★★☆
() автор топика
Ответ на: комментарий от stevejobs

Не надо додумывать то, чего я не писал.
Если у тебя есть блок кода, который (по твоим хотелкам) должен «не выполниться»/прерваться при изменении состояния,
то это и есть транзакция.
И логично откатить изменения, которые она сделала. А иначе это полное undefined behavior.
В случае с данными реализация еще более-менее ясна (на крайняк примеры можно поизучать).
Какой у тебя случай (что ты делаешь, если не манипулируешь данными) - из твоего поста не ясно.
imho твоя проблема больше горе от ума (да).

goodwin ★★
()
Последнее исправление: goodwin (всего исправлений: 1)
Ответ на: комментарий от goodwin

мой случай - написание утилиты, которая вместо меня будет копаться в «отладчике» и собирать факты. Когда факт установлен, можно что-нибудь сделать - например тупо kill -9 на весь процесс, или убить тред где выполняется, или сделать что-то еще.

stevejobs ★★★★☆
() автор топика
Ответ на: комментарий от stevejobs

про «убить тред» - сам понимаешь...
если код тредов твой - то, к сожалению (а вообще-то к счастью, ибо ковырять потом такое «гэ» неприятно),
нужно писать так, что бы можно было завершить тред снаружи по нормальному (твой любимый mutable state).
или опять я не правильно понял - тебе нужно написать утилиту, которая будет запускать в дебаггере некий софт,
контролируя и корректируя его работу (убивая треды)? - это и есть треш и угар, о которых ты писал вначале.
я, в твоем случае, попытался бы воспользоваться декомпилятором.. (положительный опыт уже был пару раз)

goodwin ★★
()
Ответ на: комментарий от goodwin

это нужно не на постоянку на проде, а для исследования. Например, ты уже две недели безвылазно сидишь в отладчике в поиске баги. Средний палец уже стерся в кровь об нажатие кнопки F8 (step forward), а рука болит от постоянного лазания к мышке, чтобы копипастить из блокнотика условия в диалоговое окно evaluate expression. И тут тебе хочется, чтобы часть предположений проверяла за тебя автоматика, и не чтобы ты вручную сидел и пердолился с сотнями переменных в watch list в IDE.

stevejobs ★★★★☆
() автор топика
Ответ на: комментарий от stevejobs

Отвечал в предыдущем удаленном треде:

1) Если волнуешься, что даннные кто-то где-то меняет, то делай их иммутабельными (зачем убивать того, кто их меняет, если можно изначально сделать их неизменяемыми?).

2) Либо делай обертку, через которую будет проходить весь доступ к данным. При неправильном изменении - кидай эксепшн (см. Unmodifiable collections). Или без обертки - прямо в самих деструктивных методах объекта добавляй проверку.

3) Либо используй ThreadLocal или thread-safe объекты.

4) Транзакции

А так да, не совсем понятно что ты пытаешься сделать.

kovrik ★★★★★
()
Ответ на: комментарий от stevejobs

Если для того, чтобы найти багу в коде, тебе надо писать свой _особый_ дебаггер, то я бы серьезно задумался о качестве написанного кода.
Чем обычные дебаггеры не угодили?
Добавь в код логгирование в нужных местах, добавь assert'ы, размести breakpoint (или conditional breakpoints) - и вперед. Обычно этого за глаза хватает.

kovrik ★★★★★
()

такая проверка не атомарная. поэтому - никак. читать lock-free algorithms для собственного развития. там техники обеспечения подобных инвариантов используются (через copy-on-write в том числе)

dzidzitop ★★
()
Ответ на: комментарий от no-such-file

В x. При изменении x признак устанавливается.

Плюсую. Просто, наглядно, без хаков байткода/VM.

Только, судя по описанию, нужно не устанавливать признак при изменении, а добавить метод, устанавливающий этот флаг. Метод может при этом возвращать объект, реализующий AutoCloseable, который будет при закрытии снимать флаг, чтобы с try-with-resources было удобно использовать.

korvin_ ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.