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
Alain Ketterlin
JKB writes:

[...]
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 s pecs
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écutan t dans
un autre thread n'a pas encore positionné la variable à la bon ne
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.



Cela me semble conforme aux threads POSIX. Si tu ne veux pas que le
signal soit traité par un autre thread, il faut le demander via
pthread_sigmask. Sinon, tu risqueras toujours ce que tu observes.

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) ?



Bloquer tous les signaux dans les threads qui ne font pas nanosleep().
De toute façon, il n'y a pas moyen d'envoyer un signal à un thread
particulier, donc tu ne peux pas "réveiller" différents nanosleep ()
concurrents.

-- Alain.
Avatar
talon
JKB wrote:
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.




Dans Butenhof il y a un chapitre entier sur ce sujet. Je te cite un
court extrait:

All signal actions are process-wide. A program must coordinate any use
of sigaction between threads. This is nonmodular, but also relatively
simple, and signals have never been modular. A function that dynamically
modifies signal actions, for example, to catch or ignore sigfpe while it
performs floating-point operations, or sigpipe while it performs network
I/O, will be tricky to code on a threaded system. While modifying the
process signal action for a signal number is itself thread-safe, there
is no protection against some other thread setting a new signal action
immediately afterward. Even if the code tries to be "good" by saving the
original signal action and restoring it, it may be foiled by another
thread, as shown in Figure 6.1. Signals that are not "tied" to a
specific hardware execution context are delivered to one
arbitrary thread within the process. That means a sigchld raised by a
child process termination, for example, may not be delivered to the
thread that created the child. Similarly, a call to kill results in a
signal that may be delivered to any thread. The synchronous "hardware
context" signals, including sigfpe, sigsegv, and SIGTRAP, are delivered
to the thread that caused the hardware condition, never to another
thread. You cannot kill a thread by sending it a SIGKILL or stop a
thread by sending it a SIGSTOP Any signal that affected a process still
affects the process when multiple threads are active, which means that
sending a SIGKILL to a process or to any specific thread in the
process (using pthread_kill, which we'll get to in Section 6.6.3) will
terminate the process. Sending a SIGSTOP will cause all threads to stop
until a sigcont is received. This ensures that existing process control
functions continue to work otherwise most threads in a process
could continue running when you stopped a command by sending a sigstop.
This also applies to the default action of the other signals, for
example, SIGSEGV, if not handled, will terminate the process and
generate a core file it will not terminate only the thread that
generated the SIGSEGV.

Plus loin on explique que l'usage des sémaphores est en effet le moyen
le plus simple pour résoudre ce genre de problème.

Although mutexes and condition variables provide an ideal solution to
most synchronization needs, they cannot meet all needs. One example of
this is a need to communicate between a POSIX signal-catching function
and a thread waiting for some asynchronous event. In new code, it is
best to use sigwait or sigwait-info rather than relying on a
signal-catching function, and this neatly avoids this problem. However,
the use of asynchronous POSIX signal-catching functions is well
established and widespread, and most programmers working with threads
and existing code will probably encounter this situation.

Avec bien entendu des exemples détaillés d'utilisation.



--

Michel TALON
Avatar
JKB
Le Mon, 08 Oct 2012 16:45:57 +0200,
Alain Ketterlin écrivait :
JKB writes:

[...]
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.



Cela me semble conforme aux threads POSIX. Si tu ne veux pas que le
signal soit traité par un autre thread, il faut le demander via
pthread_sigmask. Sinon, tu risqueras toujours ce que tu observes.

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) ?



Bloquer tous les signaux dans les threads qui ne font pas nanosleep().
De toute façon, il n'y a pas moyen d'envoyer un signal à un thread
particulier, donc tu ne peux pas "réveiller" différents nanosleep()
concurrents.



Mais je ne veux pas bloquer les threads. NetBSD crée un thread pour
appeler le gestionnaire de signal (même pthread_self() renvoie une
valeu différente). Dans un programme monothread, le fait de recevoir
un signal le transforme un court instant en programme multithreadé
parce qu'en plus le gestionnaire de signal s'exécute de façon
concurrente au programme principal !

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
JKB
Le Mon, 8 Oct 2012 15:54:49 +0000 (UTC),
Michel Talon écrivait :
Plus loin on explique que l'usage des sémaphores est en effet le moyen
le plus simple pour résoudre ce genre de problème.

Although mutexes and condition variables provide an ideal solution to
most synchronization needs, they cannot meet all needs. One example of
this is a need to communicate between a POSIX signal-catching function
and a thread waiting for some asynchronous event. In new code, it is
best to use sigwait or sigwait-info rather than relying on a
signal-catching function, and this neatly avoids this problem. However,
the use of asynchronous POSIX signal-catching functions is well
established and widespread, and most programmers working with threads
and existing code will probably encounter this situation.

Avec bien entendu des exemples détaillés d'utilisation.



Certes. Mon but est simple, récupérer un signal dans un programme
qui n'est pas multithreadé. Ce programme comporte un nanosleep() qui
attend une heure. Lorsque le signal arrive, la donnée doit être
traitée puis le nanosleep doit rendormir le processus pour le temps
restant (il y a une correction de la durée de traitement, mais je ne
veux pas entrer là-dedans).

Sous tous les Unix sauf NetBSD, lorsque le signal arrive,
nanosleep() est interrompu, le gestionnaire de signal est
appelé, puis nanosleep() retourne en positionnant errno. Toujours
dans cet ordre.

Sous NetBSD, nanosleep() retourne immédiatement après avoir lancé le
gestionnaire de signal dans un thread dédié (visiblement dans l'état
'detached' pour tout simplifier). Je n'observe cela qu'avec BetBSD
5.1, le 4.0 ne faisait pas ça.

La question est donc de savoir si c'est conforme aux specs POSIX
et comment le contourner simplement parce qu'entre nous, la seule
fonction asyc safe là-dedans est si ma mémoire est bonne sem_post().
Ni les mutrexes, ni sem_wait() ne peuvent être utilisées...

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
talon
JKB wrote:

La question est donc de savoir si c'est conforme aux specs POSIX
et comment le contourner simplement parce qu'entre nous, la seule
fonction asyc safe là-dedans est si ma mémoire est bonne sem_post().
Ni les mutrexes, ni sem_wait() ne peuvent être utilisées...




Je ne vais surement pas perdre mon temps à essayer de décrypter des
specs parfaitement imbaisables. Pour la programmation des threads
"posix" il y a une référence et une seule, Butenhof (mec qui a participé
à la rédaction de la spec, donc en connait parfaitement
l'interprétation). Il commence son chapitre en disant qu'il faut éviter
autant que possible l'utilisation conjointe des threads et des signaux,
ce que tout le monde se tue à te dire. Si néanmoins on persiste, il dit
qu'il faut utiliser des sémaphores pour se dépatouiller et donne un
exemple. Les choses que tu observes sur tel ou tel système marchent par
hasard, c'est tout. Utiliser des sémaphores c'est merdique, pour
d'autres raisons, c'est tout le temps un truc à problèmes. Par exemple
ça a du mal à tourner dans une jail de freebsd. Donc la vraie solution
c'est d'utiliser uniquement des techniques de pthreads (variables de
condition, etc.) et ne pas envoyer de signaux. Tu le sais dès le début.
Pour la n° fois je te file la référence de Butenhof
http://lib.org.by/info/Cs_Computer%20science/CsPl_Programming%20languages/Butenhof.%20Programming%20with%20POSIX%20threads%20(AW,%201997)(T)(ISBN%200201633922)(398s).djvu


JKB




--

Michel TALON
Avatar
JKB
Le Tue, 9 Oct 2012 12:54:58 +0000 (UTC),
Michel Talon écrivait :
JKB wrote:

La question est donc de savoir si c'est conforme aux specs POSIX
et comment le contourner simplement parce qu'entre nous, la seule
fonction asyc safe là-dedans est si ma mémoire est bonne sem_post().
Ni les mutrexes, ni sem_wait() ne peuvent être utilisées...




Je ne vais surement pas perdre mon temps à essayer de décrypter des
specs parfaitement imbaisables. Pour la programmation des threads
"posix" il y a une référence et une seule, Butenhof (mec qui a participé
à la rédaction de la spec, donc en connait parfaitement
l'interprétation). Il commence son chapitre en disant qu'il faut éviter
autant que possible l'utilisation conjointe des threads et des signaux,
ce que tout le monde se tue à te dire.



Je vais être très clair avec toi, je ne fais pas ça de gaîté de
coeur. Je cherche à récupérer un CTRL+C au clavier. Mais tu vas
m'expliquer sans doute comment le faire proprement et immédiatement
(donc en interrompant un appel système lent) sans utiliser un
gestionnaire de signal (parce que je ne peux pas me permettre de
sortir brutalement du programme en question, je dois libérer un
certain nombre de ressources persistantes).

Si néanmoins on persiste, il dit
qu'il faut utiliser des sémaphores pour se dépatouiller et donne un
exemple. Les choses que tu observes sur tel ou tel système marchent par
hasard, c'est tout.



Ah oui ? Je te signale que j'ai posé une question _simple_ et que
personne ne m'a encore répondu de façon définitive. Je te la repose rien
que pour toi. Est-ce normal qu'un signal provoque dans un programme
monothreadé (as-tu bien vue cette fois-ci le monothreadé ?)
l'exécution du gestionnaire de signal dans un autre thread
spécifique à ce gestionnaire de signal. Ta réponse est du type,
c'est un hasard. La réponse est pourtant : c'est autorisé (ou ça ne
l'est pas) par les spesc POSIX. Et aujourd'hui, je n'ai pas la
réponse. Et la suite est 'comment contourner le problème ?' et ton
Butenhof que tu mets à toutes les sauces ne donne pas de solution
utilisable dans ce cas bien précis (sauf à supposer que le signal
arrivera forcément sur cet appel système lent ce qui est un
hypothèse pour le moins foireuse).

Pour ton information aussi, j'ai une conversation constructive avec
un développeur de NetBSD juste sur ce sujet, parce que visiblement,
ça explique un certain nombre de dysfonctionnement dans le système.
Donc la réponse n'est pas aussi triviale que tu sembles le dire.

Utiliser des sémaphores c'est merdique, pour
d'autres raisons, c'est tout le temps un truc à problèmes. Par exemple
ça a du mal à tourner dans une jail de freebsd. Donc la vraie solution
c'est d'utiliser uniquement des techniques de pthreads (variables de
condition, etc.) et ne pas envoyer de signaux. Tu le sais dès le début.
Pour la n° fois je te file la référence de Butenhof
http://lib.org.by/info/Cs_Computer%20science/CsPl_Programming%20languages/Butenhof.%20Programming%20with%20POSIX%20threads%20(AW,%201997)(T)(ISBN%200201633922)(398s).djvu



C'est très gentil à toi, mais ça ne répond pas à la question posée.
Je sais parfaitement utiliser les pthreads (y compris les signaux,
les masques de signaux et autres joyeusetés). J'ai juste un
problème, SIGINT arrête un peu trop brutalement un programme. Tu me
réponds qu'il faut utiliser des sémaphores, ce qui n'est pas
possible dans mon cas (je ne sais pas quand l'utilisateur aura
l'idée d'envoyer un CTRL+C, donc je ne peux pas protéger cet appel
asynchrone par un sémaphore).

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
Alain Ketterlin
JKB writes:

[...]
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.



Cela me semble conforme aux threads POSIX. Si tu ne veux pas que le
signal soit traité par un autre thread, il faut le demander via
pthread_sigmask. Sinon, tu risqueras toujours ce que tu observes.

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) ?



Bloquer tous les signaux dans les threads qui ne font pas nanosleep().
De toute façon, il n'y a pas moyen d'envoyer un signal à un th read
particulier, donc tu ne peux pas "réveiller" différents nanosl eep()
concurrents.



Mais je ne veux pas bloquer les threads.



Je parlais de bloquer les signaux.

NetBSD crée un thread pour appeler le gestionnaire de signal
(même pthread_self() renvoie une valeu différente). Dans un
programme monothread, le fait de recevoir un signal le
transforme un court instant en programme multithreadé parce
qu'en plus le gestionnaire de signal s'exécute de façon
concurrente au programme principal !



Alors là... Honnêtement, c'est un problèmede NetBSD. Si il c ommence à
lancer des threads sans qu'on lui demande, on peut pas lutter.

-- Alain.
Avatar
JKB
Le Tue, 09 Oct 2012 15:57:37 +0200,
Alain Ketterlin écrivait :
JKB writes:

[...]
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.



Cela me semble conforme aux threads POSIX. Si tu ne veux pas que le
signal soit traité par un autre thread, il faut le demander via
pthread_sigmask. Sinon, tu risqueras toujours ce que tu observes.

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) ?



Bloquer tous les signaux dans les threads qui ne font pas nanosleep().
De toute façon, il n'y a pas moyen d'envoyer un signal à un thread
particulier, donc tu ne peux pas "réveiller" différents nanosleep()
concurrents.



Mais je ne veux pas bloquer les threads.



Je parlais de bloquer les signaux.



Mais je ne veux justement pas bloquer SIGINT ;-) D'autant plus que
le problème apparaît déjà dans un programme monothreadé...

NetBSD crée un thread pour appeler le gestionnaire de signal
(même pthread_self() renvoie une valeu différente). Dans un
programme monothread, le fait de recevoir un signal le
transforme un court instant en programme multithreadé parce
qu'en plus le gestionnaire de signal s'exécute de façon
concurrente au programme principal !



Alors là... Honnêtement, c'est un problèmede NetBSD. Si il commence à
lancer des threads sans qu'on lui demande, on peut pas lutter.



Donc pas moyen de contourner le truc facilement... Vu le peu de
fonctions async-safe, le workaround s'il existe risque d'être
amusant.

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
Jean-Marc Bourguet
JKB writes:

Donc pas moyen de contourner le truc facilement... Vu le peu de
fonctions async-safe, le workaround s'il existe risque d'être
amusant.



Si tu le trouves, ca m'interesse.

A+

--
Jean-Marc
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
JKB
Le Tue, 09 Oct 2012 16:29:36 +0200,
Jean-Marc Bourguet écrivait :
JKB writes:

Donc pas moyen de contourner le truc facilement... Vu le peu de
fonctions async-safe, le workaround s'il existe risque d'être
amusant.



Si tu le trouves, ca m'interesse.



En fait, j'arrive à contourner le problème pour un appel système
lent, mais ça pose des problèmes ailleurs dans le code... Je suis en
train de creuser le truc avec les gens de NetBSD, mais pour
l'instant, rien ne se dessine. S'il y a un début de solution, je ne
manquerai pas de le faire savoir ici.

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
1 2 3 4 5