Как стать автором
Обновить

Комментарии 15

Спасибо за статью. А зачем вы экспортируете accept_func? Она же нигде кроме этого модуля не используется.
Дело в том, что мы создаем процесс, вызывая функцию erlang:spawn_link/3 (spawn_link(?MODULE, accept_func, [LSocket]), чтобы вызов был успешен нужно экспортировать функцию accept_func. Использовать функцию с одним аргументом erlang:spawn_link/1 не получится, т.к. в accept_func нам дополнительно нужно передать слушающий сокет (LSocket).
О вот как, я почему то думал, что если делать spawn_link в том же модуле, то передаваемые функции экспортировать необязательно, тк они уже в его видимости. Но я только начал изучать erlang.
spawn_link/… — это вызов обычной функции, которая находится в другом модуле, в котором находится код, оперирующий вызовом accept_func, в доме, который построил Джек.
Используя spawn_link/1 можно не экспортировать функцию.
Статья не просто неполезная, она просто вредная.
В принципе, читать дальше в некоторых «костыльных» решениях это действительно нужно, в продакшене я бы предпочел использовать проверенные средства уже не стоит, потому как в этом контексте эта фраза говорит о том, что продакшна код автора ещё не видел.

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

Во-первых, он совершенно несовместим с горячей загрузкой кода. Вообще. Никак.

Если бы автор сделал ?MODULE:accept_func, то тогда с жуткими непонятными глюками а также малопонятными сообщениями об ошибках, вызванными тем, что старая версия кода не отпускалась бы акцептором и OTP тупо прибивал бы процесс-акцептор с ошибкой, этот код обновлял бы версию.

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

Но на практике не будет и этого и это самое страшное. Сообщений об ошибках не будет.
Ведь автор запускает код через spawn_link даже не представляя себе всю опасность этого механизма.

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

Теперь давайте поговорим про настоящий продакшн, а не про придуманный. Про тот продакшн, в котором prim_inet работает и правильный, асинхронный accept реально работает.

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

5. {ok, Pid} = tcp_client_sup:start_child(),
6. ok = gen_tcp:controlling_process(Socket, Pid),
7. tcp_fsm:set_socket(Pid, Socket),


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

В итоге, учитывая, что истинную причину увидеть не получится, в логах будет каша из {'EXIT', ...} без единого указания на реальную проблему.

Так что в реальном продакшне указанный здесь подход в таком исполнении вреден и неправилен.
А вот использование prim_inet действительно работает, притом работает предсказуемо и понятно.
Макс, кстати… Пользуясь случаем — ты в ЖЖ пару месяцев назад писал, что планируешь написать статью о том как правильно строить OTP приложения. Стоит ждать статью то?
стоит. Лежит черновиком.
У меня просто вопрос для понимая работы erlang:

Если в схеме автора поста в tcp_listener повесить process_flag(trap_exit, true), в handle_info слушать 'Exit' и по нему перезапускать слушателя, повысится ли стабильность системы?
повысится. Но я категорически не рекомендую использовать механизм link.

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

Мониторы лишены всех этих проблем.

Ещё в приведенном здесь коде есть другие проблемы.
Необходимо либо журналировать неизвестные сообщения и call-ы, либо падать по ним. Это очень важно, без этого легкий рефакторинг может обернуться большой головной болью.
Макс, спасибо за отличный комментарий! Буду дальше учиться. Запуск через spawn_link я отметил как минус. Для связи не OTP с OTP процессами можно использовать supervisor_bridge или вообще лучше этого не делать?
Что посоветуешь для углубления знаний?
Я советую не использовать вообще не-OTP процессы.
В erlyvideo есть пара мест, где были spawn-утые функции, но вроде осталась только одна в media_ticker-е.
Никаких плюсов от этого нет.

Мои рецепты простой — везде использовать gen_server (от gen_fsm много проблем), всегда запускать из под супервизора, падать на всех неотработанных handle_info и handle_call. Эти несложные рецепты сберегают кучу времени.
А для профессионального роста что посоветуешь почитать? Ведь по эрлангу не так много хороших ресурсов. Ты как в этом направлении двигался? Изучать чужой код?
Максим, сделайте пожалуйста полезную статью. Я знаю, что вы большой спец в эрланге и ваш видео сервер вызывает только чувство уважения к Вам! напишите пожалуйста статью, например в простых словах расскажите о плтформе Erlang. Сейчас к ней очень большой интерес, но многие люди не понимаю принципа ее работы, а ваша статья им поможет им (и мне в том числе) квидеть все а ПРАВИЛЬНОМ свете.

Напишите?
Автар, предлагаю тебе квест. Написать модуль так, чтобы он умел всё что надо, при этом одновременно (в смысле или то или то) для голого tcp так и для tcp с ssl. Я дам подсказку. В ssl нет той самой недокументированной async которая спасала для tcp :) я в своё время хотел это сделать но получилась гадость.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории