Llamar al código ZeroMQ envuelto en OCaml desde el controlador de señal

4 minutos de lectura

He escrito algunos enlaces OCaml para CZMQ basados ​​en la guía en http://www.linux-nantes.org/~fmonnier/ocaml/ocaml-wrapping-c.php, que parecen funcionar bastante bien. Por ejemplo, aquí está 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));
}

Puedo enviar y recibir mensajes utilizando estos enlaces en la mayor parte de mi código sin problemas. Sin embargo, tengo un escenario en el que me gustaría enviar y recibir dentro de un controlador de señales, hasta el final de pasar mensajes en el fondo de algún otro código. Tome este ejemplo simplificado:

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) 
;;

Desde el nivel superior, esto falla con la salida:

enter handler
0
end handler
Fatal error: exception Sys_blocked_io

El código C similar al OCaml anterior funciona bien. ¿Qué está agregando OCaml a la ecuación que está causando esta excepción?

  • En términos generales, llamar a cualquier rutina desde dentro de un controlador de señal es una señal de alerta cuando estoy auditando código. Las razones son variadas, pero la esencia es que debe estar 100% seguro de que la rutina es asíncrona y segura. Vea esto para más detalles.

    –David Newman

    20 mayo 2013 a las 20:59

  • No puedo decirte por qué obtuviste la excepción, excepto para decirte que ya estabas en terrenos peligrosos al intentar IO desde dentro del controlador, ya sea C u OCaml. Creo que el enlace anterior le brinda algunas recetas para registrar de manera segura las invocaciones del controlador de señales.

    –David Newman

    20 mayo 2013 a las 21:05


  • Gracias por la información, Dave. Tomaré un enfoque diferente entonces.

    – usuario1494672

    21 de mayo de 2013 a las 3:06

  • Tal vez tienes un Unix.set_nonblock() en su código OCaml. Con eso, se sabe que OCaml a menudo genera Fatal error: exception Sys_blocked_io al intentar cualquier E/S de bloqueo.

    – El Artista del Código

    30 de julio de 2013 a las 8:04

  • ¿Podría darnos el código C también?

    – haneefmubarak

    30 de agosto de 2013 a las 20:28

Hay dos problemas potenciales:

Dentro de un controlador de señal, solo puede llamar señal asíncrona segura funciones La mayoría de las funciones no son seguras para la señal asíncrona.

El motivo de la restricción es que se podría llamar a una función en medio de la ejecución de la misma función. Por lo tanto, el estado interno podría corromperse. Muy pocas funciones son seguras para la señal asíncrona, y cualquier cosa que asigne memoria dinámicamente no lo es. En OCaml, muchas asignaciones ocurren “detrás de escena”, por lo que es probable que su código no sea seguro para la señal asíncrona.

En su caso, está llamando a una función que escribe en la salida estándar. En C, esto es nunca señal asíncrona segura, con una excepción: la primitiva write() función. Esta es la llamada del sistema sin procesar (que opera en un descriptor de archivo) y es segura para la señal asíncrona por la simple razón de que al kernel en sí no le importa que esté en un controlador de señal, y se habrá limpiado por completo antes de devolverle el control.

Llamar a una función insegura desde un controlador de señal, cuando la señal era asíncrona (el caso aquí) y ella misma interrumpió una función insegura es comportamiento indefinido en C. Esto significa Cualquier cosa podría pasar — incluyendo que su programa funcione correctamente, pero también incluyendo fallas de segmentación u otros errores, además de permitir que un atacante ejecute código arbitrario. Esto normalmente se asocia con lenguajes de bajo nivel como C, y es algo que normalmente no ocurre en OCaml.

OCaml usa un buen truco: cuando se recibe una señal para la cual se ha configurado un controlador en OCaml, difiere la ejecución del controlador hasta un punto seguro. El resultado es que en un controlador es seguro establecer un sin caja cantidad en un ref variable. Sin embargo, otras funciones como print posiblemente no sean reentrantes, ya que pueden tener un estado interno. En general, dentro de un controlador de señales, debe intentar evitar hacer algo más que establecer una bandera y regresar rápidamente. En OCaml, el indicador debe ser un número entero de 31 o 63 bits, o un valor booleano, porque no están encuadrados. En C, la bandera debe ser volatile sig_atomic_t o (no estoy seguro de esto) un tipo atómico C11.

@TheCodeArtist da la otra posible razón del error.

¿Ha sido útil esta solución?