migmit: (Default)
[personal profile] migmit
Уже в нескольких местах рассказывал одну и ту же байку. Надо бы оформить постом, и потом только ссылку давать.

Итак. Года два-три назад писал я на Эрланге. И вот возник один баг: некий процесс, скажем, Игрек, через некоторое время после запуска вдруг падает. Причём, скотина, падает даже без каких-либо сообщений в логах. А так как занят он довольно важным делом — теряется куча информации. Соответственно, релиз пришлось откатить (то ещё удовольствие) и начать разбираться.

Как оказалось, падает Игрек только под нагрузкой, так что скормить ему какие-то простенькие входные данные и аккуратно отследить, где именно его скрючит, не представляется возможным. Добавленные во все мыслимые места логи показывают, что падает процесс, в общем-то, где попало. Точнее не совсем где попало, в некоей небольшой области, но не в строго определённом месте. Я поначалу грешил на оптимизатор (случалось сталкиваться с тем, что лог выведен, а предыдущая строчка таки не выполнилась из-за того, что процесс упал), но потом понял, что наш случай таки выходит за пределы бардака, который может устроить оптимизатор. А самое смешное, что, судя по логам git, ни сам Игрек, ни вообще что-либо в непосредственной близости от него, никто не трогал уже несколько месяцев. С чего он вдруг падать начал?

Что оказалось в итоге. Был некий другой процесс, скажем, Ипсилон. И он-то как раз занимался совершенно простенькой деятельностью; он принимал сообщение, смотрел, куда его сунуть, и отсылал дальше. Причём он дожидался ответа от конечного получателя ("ага, получили, спасибо"), но сам никакого ответа отправителю не возвращал. Реализован он был через gen_server, но реально никакого внутреннего состояния там не было; если точнее, внутреннее состояние всегда оставалось [].

Но какой-то гений, который его писал (я уже не помню, кто это был) слегка ошибся. Если кто не знает, то gen_server требует, чтобы в сервере была реализована функция handle_cast, которая, собственно, и решает, как именно следует реагировать на поступившее сообщение. Бизнес-логику реализует, если угодно. Так вот, эта функция должна была бы возвращать {noreply, []} (говоря тем самым "ответа не будет, новое состояние []"), но вместо этого возвращала {ok, []}. Нарушение типизации такое, ага.

gen_server такого ответа не понимает. Ну, тупой он, не знает, что ok — значит, типа, всё здорово, давай дальше в том же духе. Поэтому, по завершении handle_cast, он, в полном соответствии с идеологией Erlang "let it crash" (т.е., "гори оно огнём") падает. Но дальше, опять-таки в полном соответствии с упомянутой идеологией, в дело включается супервизор. Который видит, что Ипсилон упал — и перезапускает его. А так как состояния у Ипсилона нет — всё работает как и должно. Вместо того, чтобы сидеть и дожидаться нового сообщения, Ипсилон падает, стартует заново, и дальше сидит и ждёт нового сообщения. И так оно работало не то чтобы месяцами — годами.

И вот что изменилось: сам Ипсилон мы тоже не трогали — а вот в одном из конечных получателей кое-что таки подкрутили и пооптимизировали. В результате ответ Ипсилону стал приходить гораздо быстрее. И Ипсилон стал, в результате, работать быстрее. А так как его работа — это постоянные падения, то и падать он стал чаще. Если под нагрузкой, конечно; когда приходит одно сообщение в час, то он и падать будет раз в час.

Супервизор не железный, у него есть предел выносливости, заданный в настройках. Так что он увидел, что один из процессов, за которыми он следит, слишком часто падает, сказал "вы меня достали", убил всех своих подопечных, после чего убился сам. Ну и, как вы понимаете, одним из его подопечных оказался Игрек, который вообще ни в чём не виноват. И чем именно занимается Игрек в этот момент — предсказать невозможно, но, с хорошей вероятностью, тем, чем он занимается чаще всего — а это довольно небольшая область. И даже пискнуть Игрек не может, убиение его супервизором совершается мгновенно и безжалостно.

Date: 2016-01-31 12:10 pm (UTC)
sergey_cheban: (Аракчеев)
From: [personal profile] sergey_cheban
Ха. У меня была похожая история, когда я, играясь с линуксом, включил в настройках ядра watchdog timer.

Date: 2016-01-31 01:40 pm (UTC)
From: [identity profile] migmit.livejournal.com
А он чем занимается?

Date: 2016-01-31 01:59 pm (UTC)
From: [identity profile] nealar.livejournal.com
Судя по названию, ждёт периодических подтверждений "да, мы ещё живы", в случае отсутствия, перезагружает машину.

Date: 2016-01-31 05:31 pm (UTC)
sergey_cheban: (Аракчеев)
From: [personal profile] sergey_cheban
Угу, именно так. При этом подтверждений он ждёт на /dev/watchdog. А туда никто не писал.

Date: 2016-01-31 02:30 pm (UTC)
From: [personal profile] zaharchenko
А факт самовыпиливая супервизора как-то логируется? Или из-за let it crash это становится настолько частой ситуацией что на неё не обратили внимания?

Date: 2016-01-31 02:34 pm (UTC)
From: [identity profile] migmit.livejournal.com
Если я правильно помню, то следы этой ситуации (не только самовыпил супервизора, но и падение Ипсилона с перезапуском) попадали в логи как ворнинги. Но это большая система, там логов за день набегает десятки гигабайт (я не преувеличиваю), читать их все глазами просто невозможно. Эти ворнинги просто затерялись в общем потоке.

Date: 2016-01-31 03:06 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
Интересно, как ипсилон и игрек оказались в одном супервизоре? И да, dialyzer на {ok, []} в коллбэке должен был выругаться.

Date: 2016-01-31 03:33 pm (UTC)
From: [identity profile] migmit.livejournal.com
В солюшене — штук тридцать проектов. Эти два оказались в одном проекте — их писали, имея в виду одну и ту же, довольно крупную, фичу. Ну и результат.

А диалайзер — особая головная боль. Я как-то для студентов нарисовал одну программку, строк на десять, где КАЖДАЯ строка содержала хотя бы одну ошибку типизации. И натравил на это диалайзер. Получил 1 (один) ворнинг. Причём неправильный. То, что программа вообще никакого смысла не имеет и работать не может, он не заметил. Добавить сюда ещё случаи, когда диалайзер даёт false positives (логично даёт, но от этого не легче), и пожалуйста — пользоваться им невозможно.

Date: 2016-01-31 03:40 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
Я как-то для студентов нарисовал одну программку, строк на десять, где КАЖДАЯ строка содержала хотя бы одну ошибку типизации. И натравил на это диалайзер. Получил 1 (один) ворнинг. Причём неправильный.
интересно. продублируйте, пожалуйста, сюда.

Date: 2016-01-31 04:19 pm (UTC)
From: [identity profile] migmit.livejournal.com
Не думаю, чтобы оно у меня сохранилось. Это было года два назад, и я больше там не работаю.

Date: 2016-01-31 08:27 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
ну эм, хоть что-нибудь?

Date: 2016-02-01 08:41 am (UTC)
From: [identity profile] dmitriid.livejournal.com
Если будет возможность вспомнить/восстановить, ОЧЕНЬ большая просьба выложить пример. Тогда можно будет отправить пример в рассылку эрланговскую. Думаю, авторы диалайзера будут только благодарны :)

Date: 2016-02-01 07:59 pm (UTC)
From: [identity profile] avnik.livejournal.com
Ну вот даже выбрасывая язык/уходя с проекта -- такие штуки надо репортить.
Улучшает собственную карму

Date: 2016-02-01 08:03 pm (UTC)
From: [identity profile] migmit.livejournal.com
Да мне лениво было искать, куда репортить. Не говоря уже о том, что Диалайзер и не должен ловить ВСЕ ошибки типизации, так что я понятия не имел, что из этого нормально, а что нет.

Date: 2016-02-03 01:31 am (UTC)
From: [identity profile] love5an.livejournal.com
Оффтоп конечно, но подумалось, давно же все это было, блин. Алерт, вот это все. Как-то даже взгрустнулось.

Date: 2016-02-03 11:18 am (UTC)
From: [identity profile] migmit.livejournal.com
Чего-чего, а тоски по алерту я не испытываю от слова "совсем". Паршивое местечко.

Date: 2016-02-01 09:10 am (UTC)
From: [identity profile] love5an.livejournal.com
В логе, помнится, в каждом сервере была функция оборачивающая эти {ok, ...} или {error, ...}, т.е. внутренние конвенции, в ответы gen_server.
Но писать их, эти функции, надоедало конечно, тут бы нормальные макросы помогли, но в эрланге макросов нормальных нет. Так что не вижу при чем тут типизация :) Все от копипасты.

Date: 2016-02-01 09:22 am (UTC)
From: [identity profile] kurilka.livejournal.com
Ну вот Юра, к примеру, покрывал генсервера спеками - http://yzh44yzh.by/post/dialyzer.html какая-никакая, но всёж типизация.

Date: 2016-02-01 10:37 am (UTC)
From: [identity profile] migmit.livejournal.com
Эм... в статически-типизированном языке подобная шняга, скорее всего, вообще не скомпилировалась. Это чистая ошибка типизации; посмотри тип функции handle_cast в документации к OTP.

Date: 2016-02-01 11:22 am (UTC)
From: [identity profile] b00ter.livejournal.com
Есть жи LFE.

Date: 2016-02-03 01:32 am (UTC)
From: [identity profile] love5an.livejournal.com
Да это игрушка, с нормальными лиспами не имеющая ничего общего.

Date: 2016-02-01 11:25 am (UTC)
From: [identity profile] grundik.livejournal.com
И что, в эрланге нет решения (имеется в виду рабочего, про диализер прочитал уже в комментах), чтобы подобных проблем не ловить в рантайме?

Date: 2016-02-01 01:34 pm (UTC)
From: [identity profile] migmit.livejournal.com
Кажется, Вадлер и Марлоу пробовали сделать систему типов для Эрланга. Не получилось.

Вообще, по-моему, в инфраструктуре Эрланга многое сделано плохо. То же let it crash, например — да, кривой код, написанный джуниором, вряд ли приведёт к проблемам... немедленным. Их придётся фиксить потом, через годы.

Date: 2016-02-01 12:53 pm (UTC)
From: [identity profile] jamhed.livejournal.com
А типа dialyzer такие ошибки {ok, wtf} вместо {noreply, wtf} не покажет? Покажет же, еще до запуска.

Date: 2016-02-01 01:32 pm (UTC)
From: [identity profile] migmit.livejournal.com
Не знаю. Dializer много чего не кажет, одновременно давая false positives. На проекте в целом его никогда не гоняли.

Date: 2016-02-01 01:40 pm (UTC)
From: [identity profile] jamhed.livejournal.com
только что попробовал -- показывает.

srv_cleanup.erl:35: The inferred return type of handle_cast/2 ({'ok',_}) has nothing in common with {'noreply',_} | {'noreply',_,'hibernate' | 'infinity' | non_neg_integer()} | {'stop',_,_}, which is the expected return type for the callback of gen_server behaviour

Хотя если на проекте нет практики применения dialyzer то какое-то время придется провести чтобы он начал показывать осмысленные результаты без лишнего шума.

Date: 2016-02-01 03:27 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
странно, у меня в заглушке handle_call работает, а в нескольких клозах handle_cast — нет.

Date: 2016-02-01 03:39 pm (UTC)
From: [identity profile] jamhed.livejournal.com
А можно подробнее где работает а где нет?

Date: 2016-02-01 06:58 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
в общем, поигрался я немного. на
handle_cast(Request, State) ->
	_ = Request,
	{dafuq, State};
handle_cast(_Msg, State) ->
	{noreply, State}.
dialyzer ругается
teest.erl:34: The inferred return type of handle_cast/2 ({'dafuq',_}) has nothing in common with {'noreply',_} | {'noreply',_,'hibernate' | 'infinity' | non_neg_integer()} | {'stop',_,_}, which is the expected return type for the callback of gen_server behaviour
, как и требовалось. А вот на такое
handle_cast({dafuq, _Ugh}, State) ->
	{dafuq, State};
handle_cast(_Msg, State) ->
	{noreply, State}.
не ругается.
Т.е. клозы с паттернматчингом почему-то успешно пропускаются.

Вопрос — кто здесь неправ?

Date: 2016-02-01 07:04 pm (UTC)
From: [identity profile] jamhed.livejournal.com
Я так подозреваю что вызова handle_cast({dafuq, wtf}, State) не делалось? Вот и нет предупреждения потому что в первую клозу код не доходит, а вторая как более общая возвращает все правильно.

Date: 2016-02-01 07:20 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
Конечно, не делалось — это коллбэк, он не вызывается вручную.

Date: 2016-02-01 07:23 pm (UTC)
From: [identity profile] jamhed.livejournal.com
Ну в смысле такая конструкция была?

api_handle_cast(Pid) -> gen_server:cast(Pid, {dafuq, wtf});
Edited Date: 2016-02-01 07:25 pm (UTC)

Date: 2016-02-01 07:32 pm (UTC)
From: [identity profile] kurilka.livejournal.com
Чтот не о том речь, там же тип определяется на уровне спек behavior, при чём здесь call-site для модуля gen_server?

Date: 2016-02-01 07:34 pm (UTC)
From: [identity profile] jamhed.livejournal.com
Ну вот если я правильно понимаю то dialyzer пытается выстроить дерево всех вызовов, потом по этому дереву сверяет типы где указано + inferred. Если совпадает то хорошо, если нет вывалит варнинг, если в ветку не попадает то не анализируется. Т.е. это не совсем statical type check, а где-то между.
Edited Date: 2016-02-01 07:36 pm (UTC)

Date: 2016-02-01 07:44 pm (UTC)
From: [identity profile] kurilka.livejournal.com
это былаб фантастика еслиб диалайзер смог из api-функции протянуть типы прям до handle_XXX через все кишки gen_XXX и не споткнувшись по пути...
ну и комбинаторный взрыв бы все асимптотам сделал грустно, думаю...
тут всёж тип задаётся на уровне колбэка, а почему диалайзер сдюживает былоб интересно узнать, конечно.

Date: 2016-02-01 07:53 pm (UTC)
From: [identity profile] jamhed.livejournal.com
> это былаб фантастика еслиб диалайзер смог из api-функции протянуть

Да, не срабатывает. Причем и даже если явно spec написать перед handle_cast.

Date: 2016-02-01 07:23 pm (UTC)
From: [identity profile] migmit.livejournal.com
А чего тогда в первом случае ругается?

Date: 2016-02-01 07:26 pm (UTC)
From: [identity profile] jamhed.livejournal.com
В первом две клозы неразличимы по сигнатуре, выполнится первая всегда с неверным типом.

Date: 2016-02-01 07:28 pm (UTC)
From: [identity profile] kurilka.livejournal.com
Думается, что это косяк success typing диалайзера, ссылку на точное описание не дам, но вроде как в нифига недоисанной книжке Лоика упоминается, что функция может иметь тип не только A -> B, но и A->B ; C -> D. Только вот почему второе может быть валидным подтипом первого не очень понятно.

Date: 2016-02-01 07:30 pm (UTC)
From: [identity profile] kurilka.livejournal.com
ну а в первом варианте 2-й клоз смысла же не имеет

Date: 2016-02-02 10:40 am (UTC)
develop7: (dero)
From: [personal profile] develop7
не имеет, верно. однако же и на
handle_cast({argh, _Ugh}, State) ->
	{noreply, dafuq, State};
handle_cast({dafuq, _Ugh}, State) ->
	{dafuq, State}.
всё равно не ругается.

Date: 2016-02-02 10:52 am (UTC)
From: [identity profile] kurilka.livejournal.com
Ну ситуация же про ту же странность варианта A->B ; C -> D по-моему, про которую я писал выше.
Былоб неплохо если кто-нибудь не поленился и спросил в рассылке, может Костис чего-нибудь бы и ответил :)

Date: 2016-02-01 02:42 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
не покажет, если не прописать {ok, dafuq} в спеке коллбэка. только что проверил.

Date: 2016-02-01 02:43 pm (UTC)
From: [identity profile] jamhed.livejournal.com
зачем это прописывать в спеке коллбэка?

Date: 2016-02-01 02:44 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
с другой стороны, на спеку-заглушку (-> any().) он ругается.
Edited Date: 2016-02-01 02:44 pm (UTC)

Date: 2016-02-01 02:46 pm (UTC)
develop7: (dero)
From: [personal profile] develop7
с третьей стороны хочется писать -> gen_server:call_reply() вместо копипастинга или таскания одного и того же хедера из проекта в проект

Date: 2016-05-08 07:08 am (UTC)
From: (Anonymous)
Супервизор при падении пишет в логи капс-локом. gen_server пишет CRASH REPORT про badmatch.
Мораль байки -- надо быть сказочным идиотом, чтобы в первую очередь не грепнуть логи на такие записи?

Date: 2016-05-08 07:22 am (UTC)
From: [identity profile] migmit.livejournal.com
Нет. Нет.