Cómo prevenir los SIGPIPEs (o manejarlos correctamente)


Tengo un pequeño programa de servidor que acepta conexiones en un socket TCP o UNIX local, lee un comando simple y, dependiendo del comando, envía una respuesta. El problema es que el cliente puede no tener interés en la respuesta a veces y sale temprano, por lo que escribir en ese socket causará un SIGPIPE y hará que mi servidor se bloquee. ¿Cuál es la mejor práctica para prevenir el accidente aquí? ¿Hay alguna manera de comprobar si el otro lado de la línea sigue leyendo? (select () no parece funcionar aquí, ya que siempre dice que el socket es escribible). ¿O debería coger el SIGPIPE con un manejador e ignorarlo?

Author: Aristotle Pagaltzis, 2008-09-20

10 answers

Generalmente desea ignorar el SIGPIPE y manejar el error directamente en su código. Esto se debe a que los manejadores de señales en C tienen muchas restricciones sobre lo que pueden hacer.

La forma más portátil de hacer esto es establecer el controlador SIGPIPE a SIG_IGN. Esto evitará que cualquier escritura de socket o tubería cause una señal SIGPIPE.

Para ignorar la señal SIGPIPE, use el siguiente código:

signal(SIGPIPE, SIG_IGN);

Si está utilizando la llamada send(), otra opción es usar la opción MSG_NOSIGNAL, que desactivará el comportamiento SIGPIPE por llamada. Tenga en cuenta que no todos los sistemas operativos admiten el indicador MSG_NOSIGNAL.

Por último, es posible que también desee considerar el indicador de socket SO_SIGNOPIPE que se puede establecer con setsockopt() en algunos sistemas operativos. Esto evitará que SIGPIPE sea causado por escrituras solo en los sockets en los que está configurado.

 205
Author: dvorak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-18 21:58:10

Otro método es cambiar el socket para que nunca genere SIGPIPE en write(). Esto es más conveniente en bibliotecas, donde es posible que no desee un manejador de señal global para SIGPIPE.

En la mayoría de los sistemas basados en BSD (macOS, FreeBSD...) sistemas, (suponiendo que está utilizando C / C++), puede hacer esto con:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

Con esto en efecto, en lugar de generar la señal SIGPIPE, se devolverá EPIPE.

 142
Author: user55807,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-29 11:06:07

Llego muy tarde a la fiesta, pero SO_NOSIGPIPE no es portátil, y podría no funcionar en su sistema (parece ser una cosa BSD).

Una buena alternativa si estás en, digamos, un sistema Linux sin SO_NOSIGPIPE sería establecer la bandera MSG_NOSIGNAL en tu llamada send(2).

Ejemplo sustituyendo write(...) por send(...,MSG_NOSIGNAL) (véase el comentario de nobar)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
 107
Author: sklnd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:35

En este post describí una posible solución para el caso Solaris cuando ni SO_NOSIGPIPE ni MSG_NOSIGNAL están disponibles.

En su lugar, tenemos que suprimir temporalmente SIGPIPE en el subproceso actual que ejecuta el código de la biblioteca. He aquí cómo hacer esto: para suprimir SIGPIPE primero comprobamos si está pendiente. Si lo hace, esto significa que está bloqueado en este hilo, y no tenemos que hacer nada. Si la biblioteca genera SIGPIPE adicional, se fusionará con el pendiente si SIGPIPE no está pendiente entonces lo bloqueamos en este hilo, y también comprobamos si ya estaba bloqueado. Entonces somos libres de ejecutar nuestras escrituras. Cuando vamos a restaurar SIGPIPE a su estado original, hacemos lo siguiente: si SIGPIPE estaba pendiente originalmente, no hacemos nada. De lo contrario, comprobamos si está pendiente ahora. Si lo hace (lo que significa que las acciones out han generado uno o más SIGPIPEs), entonces lo esperamos en este hilo, borrando así su estado pendiente (para hacer esto usamos sigtimedwait () con cero tiempo de espera; esto es para evitar el bloqueo en un escenario donde un usuario malicioso envía SIGPIPE manualmente a todo un proceso: en este caso lo veremos pendiente, pero otro hilo puede manejarlo antes de que tengamos un cambio para esperarlo). Después de borrar el estado pendiente desbloqueamos SIGPIPE en este hilo, pero solo si no estaba bloqueado originalmente.

Código de ejemplo en https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156

 28
Author: kroki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-15 08:48:42

Manejar SIGPIPE localmente

Por lo general, es mejor manejar el error localmente en lugar de en un controlador de eventos de señal global, ya que localmente tendrá más contexto en cuanto a lo que está pasando y qué recurso tomar.

Tengo una capa de comunicación en una de mis aplicaciones que permite que mi aplicación se comunique con un accesorio externo. Cuando se produce un error de escritura, lanzo una excepción en la capa de comunicación y la dejo burbujear hasta un bloque try catch para manejarlo alli.

Código:

El código para ignorar una señal SIGPIPE para que pueda manejarla localmente es:

// We expect write failures to occur but we want to handle them where 
// the error occurs rather than in a SIGPIPE handler.
signal(SIGPIPE, SIG_IGN);

Este código evitará que la señal SIGPIPE se eleve, pero obtendrá un error de lectura / escritura cuando intente usar el socket, por lo que tendrá que comprobarlo.

 19
Author: Sam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-27 16:04:09

No puede evitar que el proceso en el extremo lejano de una tubería salga, y si sale antes de que haya terminado de escribir, obtendrá una señal SIGPIPE. Si SIG_IGN la señal, entonces su escritura volverá con un error - y usted necesita notar y reaccionar a ese error. Simplemente capturar e ignorar la señal en un controlador no es una buena idea must debe tener en cuenta que la tubería ahora está desaparecida y modificar el comportamiento del programa para que no escriba en la tubería de nuevo (porque la señal será generado de nuevo, e ignorado de nuevo, y lo intentarás de nuevo, y todo el proceso podría continuar durante un largo tiempo y perder una gran cantidad de energía de la CPU).

 13
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-20 14:45:34

¿O debería simplemente coger el SIGPIPE con un manejador e ignorarlo?

Creo que es correcto. Usted quiere saber cuando el otro extremo ha cerrado su descriptor y eso es lo que SIGPIPE le dice.

Sam

 4
Author: Sam Reynolds,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-20 13:45:23

El manual de Linux decía:

EPIPE El extremo local se ha apagado en una conexión orientada zócalo. En este caso el proceso también recibirá un SIGPIPE a menos que MSG_NOSIGNAL esté establecido.

Pero para Ubuntu 12.04 no está bien. Escribí una prueba para ese caso y siempre recibo EPIPE con SIGPIPE. SIGPIPE se genera si intento escribir en el mismo zócalo roto por segunda vez. Por lo tanto, no es necesario ignorar SIGPIPE si esta señal ocurre significa error lógico en su programa.

 2
Author: talash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-26 14:41:00

¿Cuál es la mejor práctica para evitar el accidente aquí?

O deshabilita los sigpipes según everybody, o captura e ignora el error.

¿Hay alguna manera de comprobar si el otro lado de la línea sigue leyendo?

Sí, use select().

Select() no parece funcionar aquí, ya que siempre dice que el socket es escribible.

Debe seleccionar en los bits read. Probablemente puede ignorar el escribir trozo.

Cuando el extremo lejano cierra su controlador de archivo, select le dirá que hay datos listos para leer. Cuando vaya y lea eso, obtendrá 0 bytes, que es cómo el sistema operativo le dice que el controlador de archivo se ha cerrado.

La única vez que no puede ignorar los bits de escritura es si está enviando grandes volúmenes, y existe el riesgo de que el otro extremo se retrase, lo que puede causar que sus búferes se llenen. Si eso sucede, entonces tratar de escribir en el controlador de archivo puede causar su programa / hilo para bloquear o fallar. Probar select antes de escribir te protegerá de eso, pero no garantiza que el otro extremo esté sano o que tus datos vayan a llegar.

Tenga en cuenta que puede obtener un sigpipe desde close(), así como cuando escribe.

Close limpia cualquier dato almacenado en búfer. Si el otro extremo ya ha sido cerrado, entonces close fallará, y recibirá un sigpipe.

Si está utilizando TCPIP en búfer, entonces una escritura exitosa solo significa sus datos se ha puesto en cola para enviar, no significa que se ha enviado. Hasta que llame a close con éxito, no sabe que sus datos se han enviado.

Sigpipe te dice que algo ha salido mal, no te dice qué, o qué debes hacer al respecto.

 1
Author: Ben Aveling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-15 23:57:28

Bajo un sistema POSIX moderno (es decir, Linux), puede usar la función sigprocmask().

#include <signal.h>

void block_signal(int signal_to_block /* i.e. SIGPIPE */ )
{
    sigset_t set;
    sigset_t old_state;

    // get the current state
    //
    sigprocmask(SIG_BLOCK, NULL, &old_state);

    // add signal_to_block to that existing state
    //
    set = old_state;
    sigaddset(&set, signal_to_block);

    // block that signal also
    //
    sigprocmask(SIG_BLOCK, &set, NULL);

    // ... deal with old_state if required ...
}

Si desea restaurar el estado anterior más tarde, asegúrese de guardar el old_state en un lugar seguro. Si llama a esa función varias veces, necesita usar una pila o solo guardar el primero o el último old_state... o tal vez tener una función que elimina una señal bloqueada específica.

Para más información lea la página de manual .

 -1
Author: Alexis Wilke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-21 23:43:57