recv() n'est pas interrompu par un signal en environnement multithread
J'ai un fil qui se trouve dans un blocage recv()
boucle et je veux mettre fin (à supposer que cela ne peut pas être changé à select()
ou de toute autre approche asynchrone).
J'ai aussi un gestionnaire de signal que les prises SIGINT
et, théoriquement, il devrait faire recv()
de retour avec l'erreur et errno
ensemble de EINTR
.
Mais il ne l'est pas, ce qui je suppose a quelque chose à voir avec le fait que l'application est multi-thread. Il y a aussi un autre thread, qui est entre-temps d'attente sur un pthread_join()
appel.
Ce qui se passe ici?
EDIT:
OK, maintenant j'ai choisi de fournir le signal à l'ensemble de blocage recv()
threads via pthread_kill()
depuis le thread principal (qui se traduit dans le même SIGINT
gestionnaire de signal installé, même si de multiples appels sont bénignes). Mais recv()
appel n'est toujours pas débloqué.
EDIT:
J'ai écrit un exemple de code qui reproduit le problème.
- Thread principal se connecte à un socket à un comportement anormal de l'hôte distant qui ne permet pas de connexion aller.
- L'ensemble des signaux bloqués.
- Lire le thread thread est démarré.
- Principal débloque et installe gestionnaire pour
SIGINT
. - Lire fil débloque et installe gestionnaire pour
SIGUSR1
. - Thread principal du gestionnaire de signal envoie un
SIGUSR1
à la lecture du thread.
Fait intéressant, si je remplace le recv()
avec sleep()
il est interrompu à l'amende juste.
PS
Alternativement, vous pouvez simplement ouvrir un socket UDP au lieu d'utiliser un serveur.
client
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
static void
err(const char *msg)
{
perror(msg);
abort();
}
static void
blockall()
{
sigset_t ss;
sigfillset(&ss);
if (pthread_sigmask(SIG_BLOCK, &ss, NULL))
err("pthread_sigmask");
}
static void
unblock(int signum)
{
sigset_t ss;
sigemptyset(&ss);
sigaddset(&ss, signum);
if (pthread_sigmask(SIG_UNBLOCK, &ss, NULL))
err("pthread_sigmask");
}
void
sigusr1(int signum)
{
(void)signum;
printf("%lu: SIGUSR1\n", pthread_self());
}
void*
read_thread(void *arg)
{
int sock, r;
char buf[100];
unblock(SIGUSR1);
signal(SIGUSR1, &sigusr1);
sock = *(int*)arg;
printf("Thread (self=%lu, sock=%d)\n", pthread_self(), sock);
r = 1;
while (r > 0)
{
r = recv(sock, buf, sizeof buf, 0);
printf("recv=%d\n", r);
}
if (r < 0)
perror("recv");
return NULL;
}
int sock;
pthread_t t;
void
sigint(int signum)
{
int r;
(void)signum;
printf("%lu: SIGINT\n", pthread_self());
printf("Killing %lu\n", t);
r = pthread_kill(t, SIGUSR1);
if (r)
{
printf("%s\n", strerror(r));
abort();
}
}
int
main()
{
pthread_attr_t attr;
struct sockaddr_in addr;
printf("main thread: %lu\n", pthread_self());
memset(&addr, 0, sizeof addr);
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket < 0)
err("socket");
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) <= 0)
err("inet_pton");
if (connect(sock, (struct sockaddr *)&addr, sizeof addr))
err("connect");
blockall();
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
if (pthread_create(&t, &attr, &read_thread, &sock))
err("pthread_create");
pthread_attr_destroy(&attr);
unblock(SIGINT);
signal(SIGINT, &sigint);
if (sleep(1000))
perror("sleep");
if (pthread_join(t, NULL))
err("pthread_join");
if (close(sock))
err("close");
return 0;
}
serveur
import socket
import time
s = socket.socket(socket.AF_INET)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('127.0.0.1',8888))
s.listen(1)
c = []
while True:
(conn, addr) = s.accept()
c.append(conn)
OriginalL'auteur Alex B | 2010-08-27
Vous devez vous connecter pour publier un commentaire.
Normalement les signaux de ne pas interrompre les appels système avec
EINTR
. Historiquement, il y a deux signaux de livraison des comportements: la BSD comportement (appels sont automatiquement redémarrés, lorsqu'il est interrompu par un signal) et le Système Unix V comportement (appels renvoyer -1 avecerrno
ensemble deEINTR
lorsqu'il est interrompu par un signal). Linux (le noyau) a adopté le dernier, mais la bibliothèque C de GNU développeurs (correctement) considérée comme la BSD comportement beaucoup plus sain d'esprit, et donc sur les systèmes Linux modernes, appelantsignal
(qui est une fonction de la bibliothèque) des résultats de la BSD comportement.POSIX permet soit de comportement, il est donc recommandé de toujours utiliser
sigaction
où vous pouvez choisir de définir laSA_RESTART
drapeau ou de les omettre en fonction du comportement que vous souhaitez. Consultez la documentation desigaction
ici:http://www.opengroup.org/onlinepubs/9699919799/functions/sigaction.html
Donc, en utilisant
sigaction
et le réglage de laSA_RESTART
drapeau résolu votre problème?L'omission de la
SA_RESTART
drapeau donnerait le comportement de l'OP voulait.OK, donc j'ai finalement obtenu. Très utile post, merci!
poll
etselect
sont particulières en ce qu'elles ne redémarre jamais. C'est spécifiée par POSIX.OriginalL'auteur R..
Dans une application multi-thread, normal, les signaux peuvent être transmis à un thread de manière arbitraire. Utilisation
pthread_kill
pour envoyer le signal vers le thread spécifique d'intérêt.Avez-vous vérifié si le gestionnaire de signal a été invoqué, et si oui, à partir de ce thread? C'est le signal reçu après la
recv()
appel se termine?Oui. Aussi, j'ai mis à jour la question avec l'exemple de code.
OriginalL'auteur bdonlan
N'gestionnaire de signal invoqué dans le même thread qui attend dans recv()?
Vous devrez peut-être explicitement masque SIGINT dans tous les autres threads via pthread_sigmask()
Par souci d'exhaustivité: un nouveau thread hériter le masque de signal à partir de son parent. Vous pouvez le bloquer dans le parent, et de le débloquer après la création de thread. Il est peut-être la seule solution (que sais-je) si vous dépendez d'une bibliothèque que de créer des threads, qui vous sont incapables de changer.
Je ne pense pas que cela fonctionne pour moi. J'ai besoin de le thread principal pour arrêter le blocage de fil, ce qui signifie que le gestionnaire de signal doit être installé dans le thread ne s'appelle pas
recv()
.J'ai mis à jour la question avec l'exemple de code.
OriginalL'auteur blaze
Comme mentionné dans le post de
<R..
>, il est en effet possible de changer le signal d'activités.J'ai souvent créer mon propre "signal" de la fonction qui rend l'utilisation de sigaction. Voici ce que j'utilise
L'attribut en question ci-dessus est le " ou un require de la sa_flags champ. C'est à partir de la page de man pour "sigaction': SA_RESTART fournit la BSD-comme le comportement d'un système permettant de passer des appels en mode redémarrage à travers des signaux. SA_NODEFER permettent le signal reçu à partir de l'intérieur de son propre gestionnaire de signal.
Lorsque le signal d'appels sont remplacés par "_signal", le thread est interrompu. La sortie affiche "appel système interrompu" et recv renvoyé un -1 lorsque SIGUSR1 a été envoyé. Le programme arrêté complètement avec le même résultat lorsque SIGINT a été envoyé, mais l'abandon a été appelé à la fin.
Je n'ai pas écrit de la partie serveur du code, j'ai juste changé le type de socket pour "DGRAM UDP" pour permettre au client de commencer.
OriginalL'auteur sorton9999
Vous pouvez définir un délai d'attente sur Linux recv: Linux: est-il lire ou recv de socket par timeout?
Lorsque vous obtenez un signal, l'appel fait à la classe de faire le recevoir.
OriginalL'auteur edW