Отключить сигнал SIGPIPE при вызове write (2) в библиотеке

Вопрос

Можно ли отключить повышение сигнала (SIGPIPE) при записи в pipe() FD, без установки моего собственного обработчика сигналов или отключения / маскирования сигнала глобально?


Фон

Я работаю над небольшой библиотекой, которая иногда создает канал и fork()s временный дочерний / фиктивный процесс, который ожидает сообщения от родителя. Когда дочерний процесс получает сообщение от родителя, он умирает (намеренно).


Проблема

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

Это приводит к тому, что я иногда пытаюсь write() подключиться к каналу с уже мертвым / закрытым концом дочернего процесса, и это вызывает SIGPIPE в родительском процессе. Я нахожусь в библиотеке, которую будут использовать другие клиенты, поэтому моя библиотека должна быть как можно более автономной и прозрачной для вызывающего приложения. Установка специального обработчика сигналов может нарушить код клиента.


Пока работаю

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

Я также нашел здесь аналогичный вопрос, касающийся SO, который запрашивает то же самое, но, к сожалению, использование _9 _ / _ 10_ не будет атомарным, и существует удаленная (но возможная) вероятность того, что дочерний процесс умрет между моими select() и write() вызовами.


Вопрос (сокращение)

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


person Cloud    schedule 16.12.2016    source источник
comment
Вы можете установить SIGPIPE в SIG_IGN.   -  person EOF    schedule 16.12.2016
comment
Вы также можете использовать сокет домена Unix вместо канала, в этом случае вы сможете использовать setsockopt(). И я думаю, что полученный код будет проще и легче поддерживать, сохраняя при этом функциональность.   -  person Iharob Al Asimi    schedule 16.12.2016
comment
@EOF Я обновлю вопрос. К сожалению, маскирование этого сигнала может повлиять на родительское приложение, использующее мою библиотеку.   -  person Cloud    schedule 16.12.2016
comment
На первый взгляд, вы можете сделать следующее: Используйте sigaction(), чтобы (атомарно AFAIK) изменить старый обработчик сигналов на новый, который улавливает SIGPIPE. Внутри обработчика проверьте, является ли ответственный канал тем, который был создан вашей библиотекой. Если да, войдите. В противном случае верните обработчик сигнала к старому raise(SIGPIPE);.   -  person EOF    schedule 16.12.2016
comment
@EOF Итак, самый простой способ сделать это - просто использовать sigaction() перед моим write() вызовом, проверить, что PID отправителя является моим собственным через getpid(), а затем проверить, совпадает ли FD в вопросе sa.si_fd с записью моего канала FD?   -  person Cloud    schedule 16.12.2016
comment
@DevNull: Звучит правдоподобно. Главное уродство в том, что старый обработчик сигналов должен быть доступен новому обработчику сигналов, что означает глобальный обработчик сигналов. Кроме того, может возникнуть состояние гонки, если после настройки обработчика сигналов другой поток вызовет SIGPIPE перед read() в вашем потоке. OTOH, я не думаю, что SIGPIPE хоть сколько-нибудь близко к этому популярному.   -  person EOF    schedule 16.12.2016
comment
@EOF Я только что понял это сейчас. Так близко.   -  person Cloud    schedule 16.12.2016
comment
@DevNull: обратите внимание, что если вы использовали домен Unix пара сокетов, вы можете использовать send () с флагом MSG_NOSIGNAL, чтобы избежать повышения SIGPIPE. Это стандартизировано в POSIX.1-2008, поэтому оно также переносимо.   -  person Nominal Animal    schedule 16.12.2016
comment
Обратите внимание, что si_fd не является членом siginfo_t, предписанного POSIX.   -  person Jonathan Leffler    schedule 16.12.2016


Ответы (3)


Можно ли отключить повышение сигнала (SIGPIPE) при записи в pipe() FD [...]?

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

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

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

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

person John Bollinger    schedule 16.12.2016
comment
Вы также можете установить конец записи как O_NONBLOCK - в этом случае запись никогда не будет тупиковой, но они могут вернуть EWOULDBLOCK или EAGAIN, если ребенок умер или отстал. - person Chris Dodd; 18.12.2016

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

  • Используйте socketpair(PF_LOCAL, SOCK_STREAM, 0, fd) вместо труб.
  • Создайте "жертвенный" подпроцесс через fork(), который может завершиться с ошибкой, если поднят SIGPIPE.

Я пошел по маршруту socketpair. Я не хотел этого, поскольку это требовало переписывания изрядной части логики конвейера, но это не было слишком болезненно.

Спасибо!

person Cloud    schedule 19.12.2016
comment
Как насчет того, чтобы сторона чтения канала оставалась открытой в родительском элементе? - person employee of the month; 22.12.2016

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

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

Если это не нормально, пожалуйста, подробно расскажите о проблеме.

person employee of the month    schedule 16.12.2016