OVH Cloud OVH Cloud

Traitement des signaux asynchrones

47 réponses
Avatar
JKB
Bonjour à tous,

J'ai un petit doute sur la gestion des signaux POSIX installés grâce
à sigaction(). J'ai accès à un certain nombre d'Unix dont Linux,
Tru64, Solaris, NetBSD, OpenBSD et FreeBSD. Et NetBSD me pose un
petit problème. Je n'arrive pas à savoir s'il s'agit d'un problème
dans mon code ou d'u truc qui m'aurait échappé.

Considérons le code suivant :

do
{
code_retour = nanosleep(&temporisation, &temporisation);
erreur = errno;

...
...
} while((code_retour == -1) && (erreur == EINTR));

Je simplifie. J'interromps nanosleep() grâce à un SIGINT (ctrl+C) et
je vois le gestionnaire se lancer (j'ai collé un printf() dans le
gestionnaire en question). Sous NetBSD, le gestionnaire de signal
est lancé dans un thread spécifique (et non le thread de
l'appelant). Ça ne semble pas être en contradiction avec les specs
POSIX. Mais nanosleep() sort avec un code d'erreur EINTR _avant_ que
le signal soit effectivement traité. Or dans la suite des
instructions de la boucle se trouve un test sur une variable volatile
et atomique positionnée par ce gestionnaire de signal. La plupart du
temps, ce test échoue car le gestionnaire de signal s'exécutant dans
un autre thread n'a pas encore positionné la variable à la bonne
valeur.

D'où une série de questions ;-)
1/ Est-ce un comportement attendu ou un bug de la gestion des
signaux de NetBSD ? Je n'observe ce comportement que sous ce
système.
2/ Comment s'assurer de façon propre que le gestionnaire de signal
a terminé ses traitement sans y coller un sem_post() (j'aimerais
éviter) ?

Merci de votre attention,

JKB

PS: Nicolas George, pas la peine de répondre, je t'ai plonké depuis
longtemps.

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr

10 réponses

1 2 3 4 5
Avatar
JKB
Le Wed, 10 Oct 2012 10:10:37 +0200,
Damien Wyart écrivait :
* JKB in fr.comp.os.unix:
[...] Je suis en train de creuser le truc avec les gens de NetBSD,
mais pour l'instant, rien ne se dessine.



En privé ? J'ai rien vu sur les listes de diffusion...



En privé, oui. Je reviendrai ici lorsqu'il y aura une réponse autre
que 'ça explique certains bugs' ;-)

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Lucas Levrel
Le 8 octobre 2012, JKB a écrit :

Considérons le code suivant :

do
{
code_retour = nanosleep(&temporisation, &temporisation);
erreur = errno;

...
...
} while((code_retour == -1) && (erreur == EINTR));

Je simplifie. J'interromps nanosleep() grâce à un SIGINT (ctrl+C) et
je vois le gestionnaire se lancer (j'ai collé un printf() dans le
gestionnaire en question). Sous NetBSD, le gestionnaire de signal
est lancé dans un thread spécifique (et non le thread de
l'appelant).



Ne peux-tu pas utiliser sigtimedwait à la place de nanosleep ?

Quel est l'effet de kill(0,SIGINT), il lance un thread ? Si tu places
ce kill() après nanosleep, et que nanosleep est interrompu par ^C, tu te
retrouves avec deux threads en plus de main() ?

--
LL
Avatar
Alain Ketterlin
Paul Gaborit writes:

[...]
Ce n'est normalement nécessaire que dans les processus qui utilisen t des
threads. Dans le cas contraire, il n'y a pas de concurrence, le
gestionnaire fait sa cuisine, et quand le programme reprend son cours il
voit l'effet du gestionnaire (typiquement, positionner un flag).

Le problème, c'est que JKB est tombé sur un cas où il n'a pas demandé de
thread mais en a quand même. Du coup il a tous les inconvénien ts, sans
les avantages : la séquence est brisée, son programme reprend son cours
avant que le gestionnaire ait fini son travail.



Je pense bien avoir compris la situation.

Ce que j'aimerai savoir c'est, lorsqu'on écrit un programme, quel es t le
critère pour déterminer s'il va tomber dans l'un ou l'autre des deux cas
(le cas "normal" ou il n'y a pas concurrence et celui où le signal e st
traité par un thread).



(tout système et le programme est multi-threadé) ou (NetBSD).

-- Alain.
Avatar
JKB
Le Wed, 10 Oct 2012 13:39:18 +0200,
Lucas Levrel écrivait :
Le 8 octobre 2012, JKB a écrit :

Considérons le code suivant :

do
{
code_retour = nanosleep(&temporisation, &temporisation);
erreur = errno;

...
...
} while((code_retour == -1) && (erreur == EINTR));

Je simplifie. J'interromps nanosleep() grâce à un SIGINT (ctrl+C) et
je vois le gestionnaire se lancer (j'ai collé un printf() dans le
gestionnaire en question). Sous NetBSD, le gestionnaire de signal
est lancé dans un thread spécifique (et non le thread de
l'appelant).



Ne peux-tu pas utiliser sigtimedwait à la place de nanosleep ?



Si, mais qu'est-ce que cela changerait ?

Quel est l'effet de kill(0,SIGINT), il lance un thread ? Si tu places
ce kill() après nanosleep, et que nanosleep est interrompu par ^C, tu te
retrouves avec deux threads en plus de main() ?



Je ne sais pas, je n'ai pas essayé.

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Paul Gaborit
À (at) Wed, 10 Oct 2012 13:59:34 +0200,
Alain Ketterlin écrivait (wrote):

Paul Gaborit writes:


Ce que j'aimerai savoir c'est, lorsqu'on écrit un programme, quel est le
critère pour déterminer s'il va tomber dans l'un ou l'autre des deux cas
(le cas "normal" ou il n'y a pas concurrence et celui où le signal est
traité par un thread).



(tout système et le programme est multi-threadé) ou (NetBSD).



Heu ?!? Comment faut-il interpréter cette formule ?


--
Paul Gaborit - <http://perso.mines-albi.fr/~gaborit/>
Avatar
Alain Ketterlin
Paul Gaborit writes:

À (at) Wed, 10 Oct 2012 13:59:34 +0200,
Alain Ketterlin écrivait (wrote):

Paul Gaborit writes:


Ce que j'aimerai savoir c'est, lorsqu'on écrit un programme, quel est le
critère pour déterminer s'il va tomber dans l'un ou l'autre d es deux cas
(le cas "normal" ou il n'y a pas concurrence et celui où le signal est
traité par un thread).



(tout système et le programme est multi-threadé) ou (NetBSD).



Heu ?!? Comment faut-il interpréter cette formule ?



si le programme est multi-threadé alors
c'est pénible
sinon si le système est NetBSD alors
c'est aussi pénible
sinon
tout va bien, en sortie d'un appel interrompu le handler est exécu té

Ici "c'est pénible" signifie qu'il faut recourir à sem_wait/sem_p ost.

-- Alain.
Avatar
JKB
Le Thu, 11 Oct 2012 09:14:00 +0200,
Alain Ketterlin écrivait :
Paul Gaborit writes:

À (at) Wed, 10 Oct 2012 13:59:34 +0200,
Alain Ketterlin écrivait (wrote):

Paul Gaborit writes:


Ce que j'aimerai savoir c'est, lorsqu'on écrit un programme, quel est le
critère pour déterminer s'il va tomber dans l'un ou l'autre des deux cas
(le cas "normal" ou il n'y a pas concurrence et celui où le signal est
traité par un thread).



(tout système et le programme est multi-threadé) ou (NetBSD).



Heu ?!? Comment faut-il interpréter cette formule ?



si le programme est multi-threadé alors
c'est pénible
sinon si le système est NetBSD alors
c'est aussi pénible
sinon
tout va bien, en sortie d'un appel interrompu le handler est exécuté

Ici "c'est pénible" signifie qu'il faut recourir à sem_wait/sem_post.



Même pas... Parce que j'ai passé la journée d'hier à mettre sur
papier une machine à état qui fonctionnait partout. Le couple
sem_wait()/sem_post() arrive à des conditions bloquantes dans
certains cas. Je crois avoir trouvé un début de solution en lançant
le traitement du gestionnaire de signal dans un thread dédié (mais
pas le gestionnaire, sinon je ne peux pas interrompre les appels
systèmes lents) et en effectuant un raise() pour réveiller
l'appel système lent en fin de traitement du gestionnaire.

L'intérêt est de garder le même traitement quel que soit l'OS cible.
Plus de nouvelles lorsque j'aurai implanté la chose, donc pas avant
la semaine prochaine.

Cordialement,

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Lucas Levrel
Le 10 octobre 2012, JKB a écrit :

Le Wed, 10 Oct 2012 13:39:18 +0200,
Lucas Levrel écrivait :
Ne peux-tu pas utiliser sigtimedwait à la place de nanosleep ?



Si, mais qu'est-ce que cela changerait ?



J'avais dû lire de travers la rationale du man.

Pourquoi un truc comme suit ne marcherait pas ?

do
{
code_retour = nanosleep(&temporisation, &temporisation);
erreur = errno;
if((code_retour == -1) && (erreur == EINTR))
while(fait==0) ;
...
...
} while((code_retour == -1) && (erreur == EINTR));

Et dans le gestionnaire :

traitement();
fait=1;

Évidemment, si le traitement ne doit être fait que dans le gestionnaire de
SIGINT, il faudra créer un gestionnaire pour tous les signaux qui fasse
aussi fait=1; .

--
LL
Avatar
JKB
Le Thu, 11 Oct 2012 10:44:54 +0200,
Lucas Levrel écrivait :
Le 10 octobre 2012, JKB a écrit :

Le Wed, 10 Oct 2012 13:39:18 +0200,
Lucas Levrel écrivait :
Ne peux-tu pas utiliser sigtimedwait à la place de nanosleep ?



Si, mais qu'est-ce que cela changerait ?



J'avais dû lire de travers la rationale du man.

Pourquoi un truc comme suit ne marcherait pas ?

do
{
code_retour = nanosleep(&temporisation, &temporisation);
erreur = errno;
if((code_retour == -1) && (erreur == EINTR))
while(fait==0) ;
...
...
} while((code_retour == -1) && (erreur == EINTR));

Et dans le gestionnaire :

traitement();
fait=1;

Évidemment, si le traitement ne doit être fait que dans le gestionnaire de
SIGINT, il faudra créer un gestionnaire pour tous les signaux qui fasse
aussi fait=1; .



Parce que ce serait trop simple ;-)

Je cherche un bug dans un programme qui est multithreadé (et qui
doit traiter le signal SIGUSR1 par thread en plus d'un SIGINT qui
peut être traité plus globalement). Je suis tombé sur
quelque chose d'incompréhensible qui m'a fait faire un exemple
minimal monothreadé qui présentait le même souci avec le seul
SIGINT.

Je sais bien qu'un esprit fort comme Michel Talon va me dire que ce
n'est pas bien, mais en l'occurrence, c'est comme ça que c'est conçu
et ça ne dépend pas de moi (un processus externe récupère des données
depuis plusieurs périphériques et envoie un signal au processus de
lecture, lequel réveille avec un mécanisme un peu sioux le bon thread de
traitement).

Utiliser une variable ne serait possible qu'en utilisant les
variables globales par thread, mais ces fonctions ne peuvent pas
être appelées depuis un gestionnaire de signal...

Cordialement,

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Lucas Levrel
Le 11 octobre 2012, JKB a écrit :

Utiliser une variable ne serait possible qu'en utilisant les
variables globales par thread, mais ces fonctions ne peuvent pas
être appelées depuis un gestionnaire de signal...



Et un « bête » tableau global (à chaque thread sa case) ? Je suppose que
le nombre de threads est variable... Une liste chaînée ?

--
LL
1 2 3 4 5