Что произойдет, если обработчик сигнала вызывается в момент отмены?

Предположим, что приложение заблокировано в точке отмены, например read, и принимается сигнал и вызывается обработчик сигнала. Glibc/NPTL реализует точки отмены, позволяя асинхронное аннулирование на время syscall, насколько я могу судить, асинхронное аннулирование останется в силе на всю длительность обработчика сигнала. Это, конечно, было бы ужасно неправильным, так как существует множество функций, которые не являются безопасными для асинхронного режима, но которые должны быть безопасны для вызова из обработчиков сигналов.

Это оставляет мне два вопроса:

  • Я ошибаюсь или это поведение glibc/NPTL действительно опасно сломано? Если да, то такое опасное поведение соответствует?
  • Что, по мнению POSIX, должно произойти, если обработчик сигнала вызывается, когда процесс выполняет функцию, которая является точкой отмены?

Изменить: Я почти убедил себя, что любой поток, который является потенциальной целью pthread_cancel, должен гарантировать, что функции, которые являются точками отмены, никогда не могут быть вызваны из обработчика сигнала в этом контексте потока

С одной стороны, любой обработчик сигнала, который может быть вызван в потоке, который может быть отменен и который использует любые функции aync-cancel-unsafe , должен отключать отмену перед вызовом любой функции, которая является отменой точка. Это связано с тем, что с точки зрения кода, прерванного сигналом, любое такое аннулирование будет эквивалентно асинхронному аннулированию. С другой стороны, обработчик сигнала не может отключать отмену, если только код, который будет запущен при вызове обработчика сигнала, использует только функции с поддержкой асинхронного сигнала, поскольку pthread_setcancelstate не является асинхронным сигналом -safe.

Ответ 1

Чтобы ответить на первую половину моего вопроса: glibc действительно демонстрирует поведение, которое я предсказал. Обработчики сигналов, которые работают при блокировке в точке отмены, выполняются при асинхронном аннулировании. Чтобы увидеть этот эффект, просто создайте поток, который вызывает точку отмены, которая будет блокироваться вечно (или в течение длительного времени), подождать немного, отправить сигнал, снова подождать, отменить и присоединиться к ней. Обработчик сигнала должен возиться с некоторыми изменчивыми переменными таким образом, чтобы было ясно, что он выполнял непредсказуемое количество времени, прежде чем его прерывали асинхронно.

Что касается того, поддерживает ли POSIX такое поведение, я все еще не уверен на 100%. POSIX утверждает:

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

  • Нить приостанавливается в точке отмены, и событие, для которого оно ожидает, происходит

  • Истек срок действия указанного тайм-аута

до того, как будет применен запрос на отмену.

Предположительно, выполнение обработчика сигнала не является случаем "приостановлено", поэтому я склоняюсь к интерпретации поведения glibc здесь как несоответствующего.

Ответ 2

Рич,

Я столкнулся с этим вопросом, выполнив обзор документации, совместимой с AC, о том, что Алекс Олива работает над glibc.

Я считаю, что реализация библиотеки GNU C (nptl-based) не нарушена. Хотя верно, что асинхронное отменение разрешено вокруг блокировки системных вызовов (которые должны быть точками отмены), такое поведение должно по-прежнему соответствовать.

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

Также верно, что если другой поток вызывает pthread_cancel с сигнальным потоком в качестве цели, то такое отмена будет действовать немедленно. Это все еще соответствует формулировке POSIX "до возвращения функции" (в этом случае чтение не было возвращено, а целевой поток находится в обработчике сигнала).

Проблема с сигналом заключается в том, что он вызывает поток в двух одновременных состояниях, как постоянно в точке отмены, так и при выполнении инструкций. Если поступит запрос об аннулировании, я считаю, что он соответствует тому, чтобы действовать немедленно. Хотя группа Austin может уточнить.

Проблема с реализацией glibc заключается в том, что она требует всех обработчиков сигналов, выполняемых отмененным потоком. только для вызова функций с асинхронной отменой. Это неочевидное требование, которое не вытекает из стандарта, но не делает его несоответствующим.

Потенциальное решение для решения хрупкости обработчиков сигналов:

  • Не включайте асинхронную отмену для блокировки системных вызовов, вместо этого включите новый бит IN_SYSCALL в реализации отмены.

  • Когда вызывается pthread_cancel, и целевой поток имеет IN_SYSCALL, то отправляйте SIGCANCEL в поток, как обычно, для асинхронного отмена, но обработчик SIGCANCEL ничего не делает (кроме побочного эффекта прерывания syscall).

  • Обертка вокруг системных вызовов будет искать отмену отправки и отменить поток до возвращения оболочки.

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

Я думаю, что дальнейшая дискуссия должна произойти в списке рассылки Austin Group в рамках обсуждения стандартов POSIX или должна произойти в libc-alpha как обсуждение обсуждения glibc.

Ответ 3

Я думаю, что вы ищете комбинацию из двух вещей:

Некоторые системные вызовы могут быть прерваны сигналами, что приводит к возврату ошибки EINTR. Это нормальное поведение, но я никогда не понимал, что произойдет, если, например, вы находитесь в середине read - ничего не читается из потока? Возможно, кто-то может прокомментировать это, чтобы помочь уточнить.

Системные вызовы, которые не следует прерывать, например те, которые вас беспокоят, должны быть обернуты вызовами sigprocmask (или pthread_sigmask в потоке), чтобы они не прерывались. После повторного включения сигналов любые сигналы, полученные при блокировке, будут доставлены. Как и с прерываниями, если вы слишком долго блокируете, вы можете пропустить некоторые из-за перезаписи (получение одного и того же сигнала несколько раз, считая одним ожидающим сигналом).