Fork в приложениях использующих event loop

    Существуют разные способы реализовать одновременную обработку данных: fork, threads, event loop… и, насколько я понимаю, вместе они уживаются довольно паршиво.

    Давайте возьмём event loop и fork. Есть ли смысл использовать их в одном приложении? На первый взгляд — конечно, есть! Event loop будет нормально работать только при условии, что обработчики событий отрабатывают достаточно быстро. И как только какой-то обработчик начинает требовать много времени для работы, первое что приходит в голову — отforkнуть его в отдельный процесс (в принципе есть ещё и нити, но в perl с ними туго, так что этот вариант даже не рассматриваем).

    Но это на первый взгляд. А если копнуть глубже…

    Например, приложение на базе event loop может иметь кучу предустановленных таймеров, которые сработают через некоторое время и вызовут определённые события. И тут делается fork… и что, порождённый процесс должен унаследовать все эти таймеры и события? Или всё-таки нет? Или должен унаследовать их частично, выборочно? Бред…

    Ещё один момент: обычно когда fork вызывается в обычном, линейном приложении, мы четко представляем себе состояние приложения в момент вызова fork, и как это состояние нужно изменить в порождённом процессе сразу после вызова fork (закрыть лишние FD, разобраться с сигналами, etc.). В случае же event loop мы, находясь в обработчике конкретного события, общее состояние приложения представляем себе очень приблизительно, мягко говоря!

    Вообще, у нормального приложения на базе event loop весь код находится в функциях — обработчиках событий. Т.е. когда вызывается fork, мы по определению находимся в такой функции. И куда мы из неё выйдем, в порождённом процессе? А куда нас выкинет по исключению (die)?

    Более жизненный пример. Приложение использует epoll для мультиплексирования множества открытых сокетов. И тут, обработчик какого-то события делает fork. Во-первых порождённый процесс унаследует FD самого epoll. Во-вторых он унаследует кучу сокетов (FD) открытых в родителе. В результате родитель начинает получать от epoll самые неожиданные и странные события, по разным причинам.

    Вообще-то этот вопрос достаточно абстрактный, и конкретно с Perl или Linux/epoll не связан.

    Я протестировал CPAN-модули Event и Event::Lib — оба не содержат никакой особой обработки fork, и даже не упоминают все эти, связанные с fork, проблемы в документации.

    Perl6 будет поддерживать event loop прямо в ядре… и я пока не вижу, как он может обойти эти проблемы.

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

    Подробнее
    Реклама
    Комментарии 12
    • 0
      Ещё один любопытный вопрос - что из себя будет представлять порождённый процесс: обычный линейный код? А если он тоже захочет работать в event loop?
      • 0
        Порожденный процесс представляет собой полную копию своего родителя. В случае event loop лучше использовать потоки, если конечно модель позволяет :))
        • 0
          perl не позволяет, к сожалению. Я тестирую производительность нитей каждые несколько версий perl, и в последний раз она всё ещё находилась ниже плинтуса.
          • 0
            Так в перле и event loop полноценного нет (если имеется ввиду aio, iocp, kqueue, kevent), разве только poll/select, но это очень медленно.

            А использовать имеет смысл, если SMP.

            Обработки форк и не должно быть. Процессы нужно форкать _до_.
            • 0
              Event loop - есть. Вполне полноценные. Моя реализация использует epoll, а CPAN-модуль Event::Lib использует libevent (т.е. тот же epoll под линухом, kqueue под *BSD, и другие методы в других системах).

              SMP здесь не при чём, Вы, вероятно, не поняли суть вопроса. Запускать какие-то задачи в отдельных процессах смысл есть не зависимо от наличия SMP. Вопрос в том, есть ли смысл запускать их через fork, без exec?

              Форкать _до_ далеко не всегда возможно, задачи требующие обработки в отдельном процессе могут возникать в процессе получения каких-то событий, на все случаи жизни тут заранее ненафоркаешься.
              • 0
                Вроде грабли с fork связаны с особенностями epoll в linux - наследованием дескрипторов
                • 0
                  Это частный случай. Посмотрите на ситуацию более широко: есть процесс, в котором настроены какие-то таймеры, сигналы, есть даже очередь ещё не обработанных пользовательских событий (ну, типа одна часть кода другой сообщение послала) - это помимо событий I/O. И вот fork этот процесс дублирует, и таких процессов становится два.

                  Если второй процесс просто будет 20 минут обсчитывать пачку данных, сохранит результат на винт и выйдет - один разговор. В этом случае, действительно, большинство побочных эффектов будет связано с особенностями реализации epoll и FD. Но что если второму процессу нужно делать что-то более сложное, и он тоже хочет использовать event loop? А у него в памяти осталась куча настроенных событий от родительского процесса...

                  Вообще, идея сдублировать процесс в котором настроена куча событий и продолжить выполнять оба таких процесса одновременно воспринимается достаточно дико.
                  • 0
                    Да, у меня была ситуация - форкнуть и запустить другой скрипт, использующий свой event loop. В результате получилась ж...
      • 0
        Если честно, не очень понял суть проблемы. Как то сумбурно все понял. То ли примеров не хватает из жизни, то ли тормоз.

        А как в вашем понимании эта проблема должна быть решена? Как эта же технология евент-лупов реализована в других языках/системах?
        • 0
          А вот так и реализована — без обращения внимания на проблему fork. В частности, под виндой, насколько я слышал, fork нет вообще; а нет fork - нет проблемы.

          Похоже, эту проблему автоматически решить нельзя. Только ручками: в каждом приложении используещем event loop внимательно следить за всеми fork-ами (в т.ч. и находящимся внутри используемых этим приложением чужих библиотек!), думать какие проблемы эти fork-и вызовут и как их обойти. Посколько гимор всё это просто дикий, то рекомендуется одновременно event loop и fork не использовать. Либо на-fork-ать нужные процессы до входа в event loop, либо вместо fork использовать fork+exec (и закрывать все fd) создавая новый процесс никак не могущий нарушить работу event loop в основном процессе.
          • 0
            В другом вашем посте вы упоминули, что библиотека libev решает или по крайней мере пытается решать эту проблему функциями ev_default_fork, ev_loop_fork. Насколько я понял из документации, эти функции выполняют переинициализацию используемого объекта (например epoll) на уровне ядра, после чего потомок может использовать функции библиотеки. Вот только что скрывается за этой "переинициализацией"?
            • 0
              Помимо epoll хватает и других событий — таймеры, пользовательские, сигналы, etc. Всё это добро автоматически переинициализировать невозможно — ведь если делается fork без exec в процессе работы event loop, то предполагается что оба процесса продолжат работать в event loop... так что просто автоматически при fork почистить все обработчики событий в child как-то не правильно и не логично.

              Насколько я понял, libev решает эту проблему не какой-то магической переинициализацией epoll_fd, а предоставляя возможность пользователю установить свой обработчик fork — который будет вызван при fork и позволит юзеру самому разрулить, какие обработчики событий оставить в child, а какие в parent. Это достаточно сложная задача, т.к. зачастую для этого нужно знать текущее состояние приложения, а оно в event-loop based приложениях зависит от слишком многих факторов. Впрочем, понятно, что в разных процессах должен быть разный epoll_fd, так что libev действительно может создавать новый epoll_fd автоматически в child процессе — но это не сильно облегчает жизнь программисту и основная сложность всё равно ложится на его обработчик события "fork".

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