Подтвердить что ты не робот

Вызов OCaml-обернутого кода ZeroMQ из обработчика сигнала

Я написал некоторые привязки OCaml для CZMQ на основе руководства http://www.linux-nantes.org/~fmonnier/ocaml/ocaml-wrapping-c.php, которые, похоже, работают очень хорошо. Например, здесь zstr_send:

CAMLprim value
caml_zstr_send(value socket_val, value string_val)
{
    CAMLparam2 (socket_val, string_val);

    void *sock = CAML_CZMQ_zsocket_val(socket_val);
    char *string = String_val(string_val);
    int rc = zstr_send(sock, string);

    CAMLreturn (Val_int(rc));
}

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

open ZMQ
exception SocketBindFailure

let bg_ctx = zctx_new ();;
let pub_sock = zsocket_new bg_ctx ZMQ_PUB;;

let handler _ =
    print_endline "enter handler";
    print_endline (string_of_int (zstr_send pub_sock "hello"));
    print_endline "end handler";
;;

let () =
    (try (
        (* bind pub socket *)
        let rc = zsocket_bind pub_sock "tcp://*:5556" in
        if (rc < 0) then ( raise SocketBindFailure );

        Sys.set_signal 
            Sys.sigalrm
            (Sys.Signal_handle handler);

        ignore
             (Unix.setitimer
                 Unix.ITIMER_REAL
                 { Unix.it_interval = 0.01 ; Unix.it_value = 0.01 });

        (* do some work *)
    )
    with 
    | SocketBindFailure -> raise SocketBindFailure) 
;;

Из верхнего уровня это не работает с выходом:

enter handler
0
end handler
Fatal error: exception Sys_blocked_io

C-код, похожий на OCaml выше, работает отлично. Что добавляет OCaml в уравнение, вызывающее это исключение?

4b9b3361

Ответ 1

Существуют две потенциальные проблемы:

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

Причиной ограничения является то, что функция может быть вызвана в середине выполнения одной и той же функции. Таким образом, внутреннее состояние может быть повреждено. Очень мало функций - это безопасный асинхронный сигнал, а все, что выделяет память динамически, - нет. В OCaml многие распределения происходят "за кулисами", поэтому вполне вероятно, что ваш код не будет безопасен для асинхронного сигнала.

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

Вызов небезопасной функции из обработчика сигнала, когда сигнал был асинхронным (случай здесь) и сам прервался, небезопасная функция - это поведение undefined в C. Это означает, что все может случиться - включая вашу программу, работающую правильно, но также включая ошибки сегментации или другие ошибки, а также позволяет злоумышленнику выполнить произвольный код. Обычно это связано с низкоуровневыми языками, такими как C, и это то, что обычно не встречается в OCaml.

OCaml использует аккуратный трюк: когда принимается сигнал, для которого обработчик установлен в OCaml, он откладывает выполнение обработчика до безопасного пункта. Результатом является то, что в обработчике безопасно установить количество незаполненных в переменную ref. Однако другие функции, такие как print, возможно, не являются реентерабельными, поскольку они могут иметь внутреннее состояние. В общем, в обработчике сигнала вы должны стараться избегать делать больше, чем устанавливать флаг и быстро возвращаться. В OCaml флаг должен быть 31- или 63-битным целым числом или логическим, потому что они распаковываются. В C флаг должен быть либо volatile sig_atomic_t, либо (я не уверен в этом) атомным типом C11.

@TheCodeArtist дает другую возможную причину ошибки.