Соединение JVM-сигнала SIGPIPE

У нас есть приложение С++ со встроенным JVM (Sun). Поскольку мы регистрируем наши собственные обработчики сигналов, мы рекомендуем это сделать до инициализации JVM, поскольку он устанавливает собственные обработчики (см. Здесь).

Из того, что я понял, JVM знает внутренне, если сигнал возник из собственного кода, и если он не передает его по цепочке - нашим обработчикам.

То, что мы начали видеть, это то, что мы получаем SIGPIPE, причем стек вызовов выглядит примерно так (верхняя запись - наш обработчик сигналов):

/.../libos_independent_utilities.so(_ZN2os32smart_synchronous_signal_handlerEiP7siginfoPv+0x9) [0x2b124f7a3989]
/.../jvm/jre/lib/amd64/server/libjvm.so [0x2aaaab05dc6c]
/.../jvm/jre/lib/amd64/server/libjvm.so [0x2aaaab05bffb]
/.../jvm/jre/lib/amd64/server/libjvm.so(JVM_handle_linux_signal+0x718) [0x2aaaab05e878]
/.../jvm/jre/lib/amd64/server/libjvm.so [0x2aaaab05bf0e]
/lib64/libpthread.so.0 [0x3c2140e4c0]
/lib64/libpthread.so.0(send+0x91) [0x3c2140d841]
/.../jvm/jre/lib/amd64/libnet.so [0x2aaabd360269]
/.../jvm/jre/lib/amd64/libnet.so(Java_java_net_SocketOutputStream_socketWrite0+0xee) [0x2aaabd35cf4e]
[0x2aaaaeb3bf7f]

Кажется, что JVM решает, что SIGPIPE, который был поднят из send, должен быть передан нашему сигналу. Правильно ли это при этом?

Кроме того, почему стек вызовов неполный? Я имею в виду, очевидно, что он не может показать мне java-код до socketWrite0, но почему я не вижу стек перед java-кодом?

Ответ 1

JVM не может определить, пришел ли SIGPIPE из его собственного кода или вашего кода. Эта информация просто не указана сигналом. Поскольку он не хочет, чтобы вы пропустили какие-либо события, которые могли бы вас заинтересовать, он должен передать вам все SIGPIPE, даже те, которые, как оказалось, были из собственного кода.

Сигналы Unix поступают в двух вариантах - "синхронный" и "асинхронный". Несколько исключительных условий, когда просто выполнение кода может вызвать ловушки и привести к "синхронным" сигналам. Это такие вещи, как непринятый доступ к памяти (SIGBUS), доступ к нелегальной памяти, часто NULL (SIGSEGV), деление на ноль и другие математические ошибки (SIGFPE), неаудируемые инструкции (SIGILL) и т.д. Они имеют точный контекст выполнения и доставляются непосредственно в поток, который их вызвал. Обработчик сигнала может искать стек и видеть "эй, я получил незаконный доступ к памяти, выполняющий java-код, а указатель был NULL. Позвольте мне исправить это".

Напротив, сигналы, которые взаимодействуют с внешним миром, являются "асинхронным" разнообразием и включают такие вещи, как SIGTERM, SIGQUIT, SIGUSR1 и т.д. Они не имеют фиксированного контекста выполнения. Для поточных программ они доставляются практически в произвольном порядке в любой поток. Важно отметить, что SIGPIPE среди них. Да, в некотором смысле, это обычно связано с одним системным вызовом. Но вполне возможно (например) иметь два потока, прослушивающих два отдельных соединения, оба из которых закрываются до того, как запланирован любой поток. Ядро просто следит за ожиданием SIGPIPE (обычная реализация - как битмаска от ожидающих сигналов), и имеет дело с этим при перенастройке любого из потоков в процессе. Это лишь один из самых простых случаев, когда JVM может не иметь достаточной информации, чтобы исключить, что ваш клиентский код заинтересован в этом сигнале.

(Что касается событий чтения, они возвращают "произошла ошибка: EINTR" и продолжаются. В этот момент JVM может превратить это в исключение, но возврат происходит после доставки сигнала и сигнальный обработчик срабатывает.)

В итоге вы должны будете иметь дело с ложными срабатываниями. (И иметь дело с получением только одного сигнала, где можно было ожидать два.)