Cómo prevenir los SIGPIPEs(o manejarlos adecuadamente)
io signals (10)
Tengo un pequeño programa de servidor que acepta conexiones en un socket TCP o UNIX local, lee un comando simple y, según el comando, envía una respuesta. El problema es que el cliente puede no tener interés en la respuesta a veces y sale antes, 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 forma de verificar si el otro lado de la línea todavía está leyendo? (select () no parece funcionar aquí ya que siempre dice que el socket es de escritura). ¿O debería simplemente capturar el SIGPIPE con un manejador e ignorarlo?
¿Cuál es la mejor práctica para prevenir el accidente aquí?
Deshabilite sigpipes según todos, o atrape e ignore el error.
¿Hay alguna forma de verificar si el otro lado de la línea todavía está leyendo?
Sí, utilice select ().
Select () no parece funcionar aquí ya que siempre dice que el socket se puede escribir.
Es necesario seleccionar en los bits de lectura . Probablemente puedas ignorar los bits de escritura .
Cuando el extremo remoto cierre su identificador de archivo, seleccionar le indicará que hay datos listos para leer. Cuando vaya y lea eso, obtendrá 0 bytes, que es como el sistema operativo le dice que el identificador 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 atrase, lo que puede causar que los buffers se llenen. Si eso sucede, entonces intentar escribir en el identificador de archivo puede hacer que su programa / hilo se bloquee o falle. La selección de pruebas antes de escribir lo protegerá de eso, pero no garantiza que el otro extremo esté saludable o que sus datos lleguen.
Tenga en cuenta que puede obtener un sigpipe desde close (), así como cuando escribe.
Cerrar vacía cualquier dato almacenado. Si el otro extremo ya se ha cerrado, el cierre fallará y recibirá un tubo de escape.
Si está utilizando TCPIP en búfer, una escritura exitosa simplemente significa que sus datos se enviaron a la cola, no significa que se enviaron. Hasta que no llame con éxito, no sabrá que sus datos han sido enviados.
Sigpipe le dice que algo salió mal, no le dice qué o qué debe hacer al respecto.
¿O debería simplemente capturar el SIGPIPE con un manejador e ignorarlo?
Creo que eso es correcto. Desea saber cuándo el otro extremo ha cerrado su descriptor y eso es lo que SIGPIPE le dice.
Sam
Manejar SIGPIPE localmente
Por lo general, es mejor manejar el error localmente que en un controlador de eventos de señal global, ya que localmente tendrá más contexto en cuanto a lo que está sucediendo y a qué recurso recurrir.
Tengo una capa de comunicación en una de mis aplicaciones que me permite comunicarme con un accesorio externo. Cuando se produce un error de escritura, lanzo una excepción en la capa de comunicación y la dejo en un bloque catch catch para manejarlo allí.
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 se genere la señal SIGPIPE, pero obtendrá un error de lectura / escritura cuando intente usar el zócalo, por lo que deberá verificarlo.
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 primer o último estado old_state
... o tal vez tener una función que elimine una señal bloqueada específica.
Para más información lea la página del manual .
El manual de Linux decía:
EPIPE El extremo local se ha cerrado en un zócalo orientado a la conexión. En este caso, el proceso también recibirá un SIGPIPE a menos que se establezca MSG_NOSIGNAL.
Pero para Ubuntu 12.04 no está bien. Escribí una prueba para ese caso y siempre recibo EPIPE sin SIGPIPE. SIGPIPE se genera si intento escribir en el mismo socket roto la segunda vez. Por lo tanto, no necesita ignorar SIGPIPE si esta señal ocurre, significa un error lógico en su programa.
En este post describí una posible solución para el caso de Solaris cuando ni SO_NOSIGPIPE ni MSG_NOSIGNAL están disponibles.
En su lugar, tenemos que suprimir temporalmente SIGPIPE en el hilo actual que ejecuta el código de la biblioteca. A continuación le indicamos cómo hacerlo: para suprimir SIGPIPE, primero verificamos si está pendiente. Si lo hace, esto significa que está bloqueado en este hilo, y no tenemos que hacer nada. Si la biblioteca genera un SIGPIPE adicional, se fusionará con el pendiente, y eso es un no-op. Si SIGPIPE no está pendiente, lo bloqueamos en este hilo y también verificamos 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 nuestras acciones han generado uno o más SIGPIPEs), entonces lo esperamos en este hilo, borrando así su estado pendiente (para hacer esto usamos sigtimedwait () con tiempo de espera cero; esto es para evitar el bloqueo en un escenario en el que un usuario malintencionado envió SIGPIPE manualmente a todo un proceso: en este caso lo veremos pendiente, pero otro subproceso puede manejarlo antes de que tengamos que esperar un cambio). 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
No puede evitar que el proceso en el otro extremo de una tubería salga y, si sale antes de que haya terminado de escribir, obtendrá una señal SIGPIPE. Si SIGUESTAS la señal, entonces tu escritura volverá con un error, y debes tener en cuenta y reaccionar ante ese error. No es una buena idea simplemente captar e ignorar la señal en un controlador. Debe tener en cuenta que la tubería ahora está inactiva y modificar el comportamiento del programa para que no vuelva a escribir en la tubería (porque la señal se generará nuevamente y se ignorará). de nuevo, e intentará nuevamente, y todo el proceso podría durar mucho tiempo y desperdiciar una gran cantidad de energía de la CPU).
Otro método es cambiar el socket para que nunca genere SIGPIPE en write (). Esto es más conveniente en las bibliotecas, donde es posible que no desee un controlador de señal global para SIGPIPE.
En la mayoría de los sistemas basados en BSD (MacOS, FreeBSD ...), (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 que se genere la señal SIGPIPE, se devolverá EPIPE.
SO_NOSIGPIPE
muy tarde a la fiesta, pero SO_NOSIGPIPE
no es portátil y puede que no funcione en tu sistema (parece ser una cosa BSD).
Una buena alternativa si está encendido, por ejemplo, un sistema Linux sin SO_NOSIGPIPE
sería establecer el indicador MSG_NOSIGNAL
en su llamada de envío (2).
Ejemplo de reemplazo de write(...)
por send(...,MSG_NOSIGNAL)
(ver el comentario de nobar )
char buf[888];
//write( sockfd, buf, sizeof(buf) );
send( sockfd, buf, sizeof(buf), MSG_NOSIGNAL );
Por lo general, 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 configurar el controlador SIGPIPE
en 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
llamada 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 SO_SIGNOPIPE
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.