Cómo prevenir los SIGPIPE (o manejarlos adecuadamente)

9 minutos de lectura

avatar de usuario
jkramer

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 y, a veces, sale temprano. Así que escribir en ese socket causará un SIGPIPE y hacer que mi servidor se bloquee.

¿Cuál es la mejor práctica para evitar el bloqueo aquí? ¿Hay alguna manera de verificar si el otro lado de la línea todavía está leyendo? (select() no parece funcionar aquí, ya que siempre dice que se puede escribir en el socket). ¿O debería simplemente atrapar el SIGPIPE con un controlador e ignorarlo?

avatar de usuario
dvorak

Por lo general, desea ignorar el SIGPIPE y maneje el error directamente en su código. Esto se debe a que los controladores 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 SIGPIPE controlador de SIG_IGN. Esto evitará que cualquier escritura de socket o tubería cause una SIGPIPE señal.

para ignorar el SIGPIPE señal, utilice el siguiente código:

signal(SIGPIPE, SIG_IGN);

Si estás usando el send() llamada, otra opción es usar el MSG_NOSIGNAL opción, que convertirá la SIGPIPE comportamiento desactivado por llamada. Tenga en cuenta que no todos los sistemas operativos admiten el MSG_NOSIGNAL bandera.

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

  • Para hacer esto explícito: señal (SIGPIPE, SIG_IGN);

    – jhclark

    4 de marzo de 2012 a las 18:38

  • Esto puede ser necesario si obtiene un código de retorno de salida de 141 (128 + 13:SIGPIPE). SIG_IGN es el controlador de señales ignoradas.

    – velcro

    8 de noviembre de 2012 a las 20:51


  • Para sockets, es realmente más fácil usar send() con MSG_NOSIGNAL, como dijo @talash.

    – Pawel Veselov

    14 mayo 2014 a las 22:25

  • ¿Qué sucede exactamente si mi programa escribe en una tubería rota (un zócalo en mi caso)? SIG_IGN hace que el programa ignore SIG_PIPE, pero ¿eso da como resultado que send() haga algo indeseable?

    – sudo

    22 mayo 2014 a las 22:16

  • Vale la pena mencionarlo, ya que lo encontré una vez, luego lo olvidé y me confundí, luego lo descubrí por segunda vez. Los depuradores (sé que gdb lo hace) anulan el manejo de su señal, ¡así que las señales ignoradas no se ignoran! Pruebe su código fuera de un depurador para asegurarse de que SIGPIPE ya no ocurra. stackoverflow.com/questions/6821469/…

    – Jetski tipo S

    19 de enero de 2016 a las 6:08


avatar de usuario
usuario55807

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…), (asumiendo que está usando 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.

  • Esto suena bien, pero SO_NOSIGPIPE no parece existir en Linux, por lo que no es una solución ampliamente portátil.

    –Brent Bradburn

    2 de diciembre de 2010 a las 7:26

  • hola a todos, me pueden decir en que lugar tengo que poner este codigo? no entiendo gracias

    – R. Dewi

    12 de septiembre de 2011 a las 11:55

  • En Linux, veo la opción MSG_NOSIGNAL en las banderas de enviar

    – Adishri

    3 de octubre de 2013 a las 7:45

  • Funciona perfecto en Mac OS X

    – kirugan

    8 de marzo de 2015 a las 11:44

  • ¿Cómo configuro este indicador que funciona para socket, tuberías y no aparece un error cuando lo configuro en un archivo?

    – 12431234123412341234123

    3 de agosto de 2021 a las 15:51

avatar de usuario
sklnd

Llego muy tarde a la fiesta, pero SO_NOSIGPIPE no es portátil y es posible que no funcione en su sistema (parece ser algo de BSD).

Una buena alternativa si tiene, digamos, un sistema Linux sin SO_NOSIGPIPE sería establecer el MSG_NOSIGNAL marcar en su llamada de envío (2).

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

  • En otras palabras, use send(…,MSG_NOSIGNAL) como reemplazo de write() y no obtendrá SIGPIPE. Esto debería funcionar bien para sockets (en plataformas compatibles), pero send() parece estar limitado al uso con sockets (no tuberías), por lo que esta no es una solución general para el problema SIGPIPE.

    –Brent Bradburn

    25 de enero de 2011 a las 17:19

  • @Ray2k ¿Quién diablos sigue desarrollando aplicaciones para Linux < 2.2? Esa es en realidad una pregunta semi-seria; la mayoría de las distribuciones se envían con al menos 2.6.

    – Tiro Parto

    12 de junio de 2014 a las 17:54

  • @Parthian Shot: Deberías repensar tu respuesta. El mantenimiento de los sistemas integrados antiguos es una razón válida para cuidar las versiones anteriores de Linux.

    – kirsche40

    20 de agosto de 2014 a las 12:27

  • @ kirsche40 No soy el OP, solo tenía curiosidad.

    – Tiro Parto

    03/09/2014 a las 23:01

  • @sklnd esto funcionó para mí perfectamente. estoy usando un qt QTcpSocket objeto de envolver el uso de sockets, tuve que reemplazar write llamada de método por un sistema operativo send (utilizando socketDescriptor método). Alguien conoce una opción más limpia para configurar esta opción en un QTcpSocket ¿clase?

    – Emilio González Montaña

    5 de febrero de 2018 a las 9:05


avatar de usuario
kroki

En esto correo 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 subproceso actual que ejecuta el código de la biblioteca. Así es como se hace esto: para suprimir SIGPIPE, primero verificamos si está pendiente. Si lo hace, significa que está bloqueado en este hilo y no tenemos que hacer nada. Si la biblioteca genera SIGPIPE adicional, se fusionará con el pendiente, y eso no funciona. 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 las acciones han generado uno o más SIGPIPE), 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 bloqueos en un escenario en el que un usuario malicioso envió SIGPIPE manualmente a todo un proceso: en este caso lo veremos pendiente, pero otro subproceso puede manejarlo antes de que tuviéramos 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

Manejar SIGPIPE localmente

Por lo general, es mejor manejar el error localmente en lugar de un controlador de eventos de señal global, ya que localmente tendrá más contexto sobre lo que está sucediendo 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 ocurre un error de escritura, lanzo una excepción en la capa de comunicación y dejo que suba a un bloque de captura de prueba 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 eleve la señal SIGPIPE, pero obtendrá un error de lectura/escritura cuando intente usar el zócalo, por lo que deberá verificarlo.

  • ¿Debe hacerse esta llamada después de conectar un enchufe o antes? donde es mejor colocar. Gracias

    – Ahmed

    14 de febrero de 2013 a las 2:14

  • @Ahmed, yo personalmente lo puse applicationDidFinishLaunching:. Lo principal es que debe ser antes de interactuar con una conexión de socket.

    – sam

    14 de febrero de 2013 a las 4:53


  • pero obtendrá un error de lectura/escritura cuando intente usar el zócalo, por lo que deberá verificarlo. – ¿Puedo preguntar cómo es esto posible? La señal (SIGPIPE, SIG_IGN) funciona para mí, pero en el modo de depuración devuelve un error. ¿Es posible ignorar también ese error?

    – nominal

    3 de octubre de 2013 a las 1:56

  • @ Dreyfus15 Creo que acabo de envolver llamadas usando el socket en un bloque de prueba / captura para manejarlo localmente. Ha pasado un tiempo desde que vi esto.

    – sam

    3 oct 2013 a las 14:36

avatar de usuario
jonathan leffler

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 SIG_IGN la señal, entonces su escritura regresará con un error, y debe anotar y reaccionar ante ese error. Simplemente capturar e ignorar la señal en un controlador no es una buena idea; 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, y lo intentará de nuevo, y todo el proceso podría continuar por un largo tiempo y desperdiciar mucha potencia de CPU).

  • ¿Debe hacerse esta llamada después de conectar un enchufe o antes? donde es mejor colocar. Gracias

    – Ahmed

    14 de febrero de 2013 a las 2:14

  • @Ahmed, yo personalmente lo puse applicationDidFinishLaunching:. Lo principal es que debe ser antes de interactuar con una conexión de socket.

    – sam

    14 de febrero de 2013 a las 4:53


  • pero obtendrá un error de lectura/escritura cuando intente usar el zócalo, por lo que deberá verificarlo. – ¿Puedo preguntar cómo es esto posible? La señal (SIGPIPE, SIG_IGN) funciona para mí, pero en el modo de depuración devuelve un error. ¿Es posible ignorar también ese error?

    – nominal

    3 de octubre de 2013 a las 1:56

  • @ Dreyfus15 Creo que acabo de envolver llamadas usando el socket en un bloque de prueba / captura para manejarlo localmente. Ha pasado un tiempo desde que vi esto.

    – sam

    3 oct 2013 a las 14:36

avatar de usuario
sam reynolds

¿O debería simplemente atrapar el SIGPIPE con un controlador e ignorarlo?

Creo que está bien. Quiere saber cuándo el otro extremo ha cerrado su descriptor y eso es lo que le dice SIGPIPE.

Sam

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad