Обработка сигналов Boost.asio и UNIX

Введение

У меня многопоточное приложение, работающее через Boost.Asio. Для всего приложения существует только один boost::asio::io_service, и все вещи выполняются внутри него группой потоков. Иногда необходимо создавать дочерние процессы с использованием fork и exec. Когда ребенок заканчивается, мне нужно сделать waitpid на нем, чтобы проверить код выхода a, чтобы собрать зомби. Недавно я добавил boost::asio::signal_set, но столкнулся с проблемой в древних системах с ядрами linux-2.4. * (Которые, к сожалению, все еще используются некоторыми клиентами). В старых ядрах Linux потоки фактически являются особыми случаями процессов, и поэтому, если ребенок был порожден одним потоком, другой поток не может дождаться его, используя семейство системных вызовов waitpid. Asio signal_set отправляет обработчик сигналов на io_service, и любой поток, выполняющий эту службу, может запускать этот обработчик, что неуместно для моего случая. Поэтому я решил обрабатывать сигналы по старому хорошему сигналу/сигмату - все потоки имеют тот же обработчик, который вызывает waitpid. Итак, есть еще одна проблема:

Проблема

Когда сигнал улавливается обработчиком, и процесс успешно выполняется, как я могу "отправить" это в мой io_service из обработчика? Как мне кажется, очевидный метод io_service::post() невозможен, потому что он может зайти в тупик во внутренних мьютексах io_service, если сигнал приходит в неправильное время. Единственное, что мне пришло в голову, это использовать какую-либо трубку или сокет-пару для записи уведомлений там и async_wait на другом конце, поскольку иногда это делается для обработки сигналов в циклах событий poll().

Есть ли лучшие решения?

Ответы

Ответ 1

Я не занимался boost:: asio, но я решил подобную проблему. Я считаю, что мое решение работает как для LinuxThreads, так и для новых потоков NPTL.

Я предполагаю, что причина, по которой вы хотите "отправлять" сигналы на ваш * io_service *, - это прерывать системный вызов, чтобы поток/программа выходила чисто. Это верно? Если нет, возможно, вы можете лучше описать свою конечную цель.

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

Ключ состоит в использовании sigaction() в потоке обработки сигнала с помощью SA_RESTART, чтобы создать обработчики для всех сигналов, которые вы хотите поймать, разоблачить эти сигналы, используя pthread_sigmask (SIG_UNBLOCK, sig_set, 0) в потоке обработки сигнала и замаскируйте тот же самый сигнал, установленный во всех других потоках. Обработчик не должен ничего делать. Простое изменение обработчика поведения, а не настройка SA_RESTART позволяет прерывать системные вызовы (например, писать()) для прерывания. Если вы используете sigwait(), системные вызовы в других потоках не прерываются.

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

Дело в том, что если вы это сделаете, вам может не понадобиться отправлять сигналы на ваш * io_service *, потому что вы можете просто проверить свои системные вызовы на коды возврата прерываний. Я не знаю, как это работает с boost:: asio.

Итак, конечный результат всего этого заключается в том, что я могу поймать сигналы, которые я хочу, такие как SIGINT, SIGTERM, SIGHUO и SIGQUIT, чтобы выполнить чистое завершение работы, но мои другие потоки все еще прерывают системные вызовы и могут также выйти с вне всякой связи между сигнальной нитью и остальной частью системы, не делая ничего опасного в обработчике сигнала, и одна реализация работает как на LinuxThreads, так и на NPTL.

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

ПРИМЕЧАНИЕ. Если вы хотите выяснить, работает ли система LinuxThreads, вы можете сделать это, создав нить, а затем сравните ее с идентификатором PID с основным PID потока. Если они отличаются от LinuxThreads. Затем вы можете выбрать наилучшее решение для типа потока.

Ответ 2

Если вы уже опросили свой IO, другое возможное решение, которое очень просто, состоит в том, чтобы просто использовать логическое значение для передачи сигналов другим потокам. Булево значение всегда равно нулю или нет, поэтому нет возможности частичного обновления и состояния гонки. Затем вы можете просто установить этот логический флаг без каких-либо мьютексов, которые читают другие потоки. Такие инструменты, как valgrind, не нравятся, но на практике это работает.

Если вы хотите быть более правильным, вы можете использовать gcc atomics, но это специфично для компилятора.