Llamar código ZeroMQ envuelto en OCaml desde el controlador de señales
(1)
Hay dos problemas potenciales:
Dentro de un manejador de señales, solo puede llamar a funciones seguras de señales asíncronas . La mayoría de las funciones no son seguras para señales asíncronas.
La razón de la restricción es que una función podría ser llamada en medio de la ejecución de la misma función. Por lo tanto, el estado interno podría estar corrompido. Muy pocas funciones son seguras para señales asíncronas, y cualquier cosa que asigne memoria dinámicamente no. En OCaml, muchas asignaciones ocurren "detrás de la escena", por lo que es probable que su código no sea seguro para una señal asíncrona.
En su caso, está llamando a una función que escribe en la salida estándar. En C, esto nunca es seguro para señales asíncronas, con una excepción: la función primitiva de write()
. Esta es la llamada al sistema sin formato (que funciona en un descriptor de archivo) y es segura para la señal asíncrona por la sencilla razón de que al núcleo en sí no le importa que esté en un controlador de señales, y se habrá limpiado por completo antes de devolverle el control.
Cuando se invoca una función insegura de un controlador de señales, cuando la señal era asíncrona (el caso aquí) y se interrumpe, una función insegura es un comportamiento indefinido en C. Esto significa que cualquier cosa podría suceder , incluso si su programa funciona correctamente, pero también incluye fallas de segmentación o otros errores, así como 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 utiliza un buen truco: cuando se recibe una señal para la cual se estableció un manejador en OCaml, difiere la ejecución del manejador hasta un punto seguro. El resultado es que en un controlador es seguro establecer una cantidad sin caja en una variable de ref
. Sin embargo, es posible que otras funciones como la print
no sean reentrantes, ya que pueden tener un estado interno. En general, dentro de un manejador de señales, debe tratar de evitar hacer algo más que establecer una bandera y regresar rápidamente. En OCaml, la bandera debe ser un número entero de 31 o 63 bits, o un valor booleano, ya que estos están sin caja. 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.
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 usando estos enlaces en la mayoría de mi código, muy bien. Sin embargo, tengo un escenario en el que me gustaría hacer envíos y recepciones dentro de un controlador de señales, hasta el final de hacer el paso de mensajes en el fondo de algún otro código. Tomemos 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?