Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

[POSIX] SIGSEGV dans pthread_kill()

3 réponses
Avatar
JKB
Bonjour à tous,

Je viens de trouver un truc bizarre dans pthread_kill(). Je suis
contraint d'envoyer des signaux à des threads spécifiques (ou de tester
la présence de threads) grâce à pthread_kill(). De temps en temps, le
truc me fait un segfault sous Linux. Visiblement, c'est le cas lorsque
le thread est terminé.

Or je vois dans la doc :

RETURN VALUE
Upon successful completion, the function shall return a value of zero.
Otherwise, the function shall return an error number. If the
pthread_kill() function fails, no signal shall be sent.

ERRORS
The pthread_kill() function shall fail if:

ESRCH No thread could be found corresponding to that specified by the
given thread ID.

EINVAL The value of the sig argument is an invalid or unsupported sig‐
nal number.

The pthread_kill() function shall not return an error code of [EINTR].

Je ne comprends pas pourquoi l'implantation threads posix sous Linux
ne renvoie pas ESRCH. Le type développant la bibliothèque en question
n'étant pas ouvert à la discussion ou très peu
(http://sources.redhat.com/bugzilla/show_bug.cgi?id=60)
quelqu'un pourrait-il me dire si mon interprétation est la bonne (à
savoir pthread_kill() renvoit une valeur non nulle et errno positionné à
ESRCH) ?

S'il s'agit d'un bug, comment faire pour envoyer un signal à un
thread de façon _sûre_ ? Je suis en train d'écrire un workaround pas
propre, mais j'aimerais comprendre.

Cordialement,

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.

3 réponses

Avatar
Nicolas George
JKB wrote in message :
Je ne comprends pas pourquoi l'implantation threads posix sous Linux
ne renvoie pas ESRCH.



Ce que tu cites de la norme dit que la fonction _peut_ retourner cette
erreur, mais pas qu'elle _doit_ le faire. C'est assez systématique dans la
rédaction de ces normes, il faut en prendre l'habitude pour ne pas se
tromper.

n'étant pas ouvert à la discussion ou très peu
(http://sources.redhat.com/bugzilla/show_bug.cgi?id`)



Ulrich Drepper est imbuvable comme à son habitude, mais il n'a pas tort dans
ce qu'il dit dans le commentaire 4 : envoyer un signal à un thread qui
n'existe pas n'est pas une erreur de circonstance (comme un filesystem plein
ou un réseau inaccessible) mais une erreur dans la structure du programme :
si le programmeur a compris qu'il ne devait pas le faire, une fois son
programme débuggé, ça n'arrive plus.

Et si ça n'arrive pas dans un programme débuggé, alors on peut se dispenser
des tests dans la bibliothèque, ça fait que ça s'exécute plus vite. C'est
peu important pour une bibliothèque comme Gtk+ où de toutes façons il va y
avoir énormément de temps passé en communication avec le serveur X11 ou sur
le rendu de glyphes vectoriels, mais pour la libc, en particulier les
communications inter-threads, on s'attend à ce que ça aille le plus vite
possible.

Si tu veux débugger, tu lies tes programmes à une version de la glibc
compilée pour le débuggage. Il y a ce qu'il faut dans pthread_kill pour
détecter certaines des erreurs (et retourner ESRCH).

Note que, sous GNU, un thread_t est un pointeur, et pthread_kill le
déréférence. Donc autant tester un pointeur nul pour géré le cas du bug que
tu cites est facile, autant tester le cas d'un pointeur déjà libéré est
essentiellement impossible.

S'il s'agit d'un bug, comment faire pour envoyer un signal à un
thread de façon _sûre_ ?



C'est simple : tu n'envoies pas de signal à un thread qui s'est déjà
terminé. C'est assez facile, parce que la condition « qui s'est déjà
terminée » pour le cas qui nous intéresse est active : ce n'est pas le fait
que le thread ait atteint pthread_exit qui compte, mais le fait qu'il ait
été achevé avec pthread_join, ou détaché avec pthread_detach.

Donc la démarche est simple : si tu fais un pthread_join ou un
pthread_detach sur un certain pthread_t, tu n'appelles plus pthread_kill
dessus. Idéalement, tu vires carrément le pthread_t de tes structures de
données, puisqu'il n'est de toutes façons plus valide. C'est valable aussi
pour un thread qui a été créé détaché (pthread_attr_setdetachstate).

Bien sûr, le mieux serait de t'abstenir d'utiliser des signaux avec des
threads, parce que c'est fondamentalement chercher les ennuis.
Avatar
JKB
Le 06-12-2008, ? propos de
Re: [POSIX] SIGSEGV dans pthread_kill(),
Nicolas George ?crivait dans fr.comp.os.unix :
JKB wrote in message :
Je ne comprends pas pourquoi l'implantation threads posix sous Linux
ne renvoie pas ESRCH.



Ce que tu cites de la norme dit que la fonction _peut_ retourner cette
erreur, mais pas qu'elle _doit_ le faire. C'est assez systématique dans la
rédaction de ces normes, il faut en prendre l'habitude pour ne pas se
tromper.

n'étant pas ouvert à la discussion ou très peu
(http://sources.redhat.com/bugzilla/show_bug.cgi?id`)



Ulrich Drepper est imbuvable comme à son habitude, mais il n'a pas tort dans
ce qu'il dit dans le commentaire 4 : envoyer un signal à un thread qui
n'existe pas n'est pas une erreur de circonstance (comme un filesystem plein
ou un réseau inaccessible) mais une erreur dans la structure du programme :
si le programmeur a compris qu'il ne devait pas le faire, une fois son
programme débuggé, ça n'arrive plus.



Dans la théorie, c'est bien. Dans la pratique, tester qu'un thread
fonctionne toujours n'est pas simple et se termine à grands coups de
mutexes avec une variable test. C'est _sale_, alors que pthread_kill()
pourrait fonctionner comme kill().

Et si ça n'arrive pas dans un programme débuggé, alors on peut se dispenser
des tests dans la bibliothèque, ça fait que ça s'exécute plus vite. C'est
peu important pour une bibliothèque comme Gtk+ où de toutes façons il va y
avoir énormément de temps passé en communication avec le serveur X11 ou sur
le rendu de glyphes vectoriels, mais pour la libc, en particulier les
communications inter-threads, on s'attend à ce que ça aille le plus vite
possible.

Si tu veux débugger, tu lies tes programmes à une version de la glibc
compilée pour le débuggage. Il y a ce qu'il faut dans pthread_kill pour
détecter certaines des erreurs (et retourner ESRCH).

Note que, sous GNU, un thread_t est un pointeur, et pthread_kill le
déréférence. Donc autant tester un pointeur nul pour géré le cas du bug que
tu cites est facile, autant tester le cas d'un pointeur déjà libéré est
essentiellement impossible.



Sauf que si tu cherches à utiliser pthread_kill(tid, 0), ce n'est
pas valable.

S'il s'agit d'un bug, comment faire pour envoyer un signal à un
thread de façon _sûre_ ?



C'est simple : tu n'envoies pas de signal à un thread qui s'est déjà
terminé. C'est assez facile, parce que la condition « qui s'est déjà
terminée » pour le cas qui nous intéresse est active : ce n'est pas le fait
que le thread ait atteint pthread_exit qui compte, mais le fait qu'il ait
été achevé avec pthread_join, ou détaché avec pthread_detach.



Je ne vois pas ce que pthread_detach vient faire là-dedans, surtout
vu ce qui est inscrit sur
<http://udrepper.livejournal.com/tag/programming+posix>

Donc la démarche est simple : si tu fais un pthread_join ou un
pthread_detach sur un certain pthread_t, tu n'appelles plus pthread_kill
dessus. Idéalement, tu vires carrément le pthread_t de tes structures de
données, puisqu'il n'est de toutes façons plus valide. C'est valable aussi
pour un thread qui a été créé détaché (pthread_attr_setdetachstate).

Bien sûr, le mieux serait de t'abstenir d'utiliser des signaux avec des
threads, parce que c'est fondamentalement chercher les ennuis.



En théorie, c'est bien. En pratique, ce n'est pas forcément
faisable.

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.
Avatar
Nicolas George
JKB wrote in message :
Dans la théorie, c'est bien. Dans la pratique, tester qu'un thread
fonctionne toujours n'est pas simple et se termine à grands coups de
mutexes avec une variable test. C'est _sale_,



Ce n'est pas sale du tout. C'est même l'idée de base de la programmation
threadée.

alors que pthread_kill()
pourrait fonctionner comme kill().



Ce serait _coûteux_, et ça le serait même pour les programmes qui n'en ont
pas besoin.

Sauf que si tu cherches à utiliser pthread_kill(tid, 0), ce n'est
pas valable.



L'utilité de pthread_kill(tid, 0) me paraît plus que douteuse, à la base.
kill(pid, 0), déjà, c'est très casse gueule. C'est de la vieille
programmation Unix moisie, comme tous les codes qui utilisent access ou
stat. On aimerait bien la voir disparaître en faveur de codes qui ne soient
pas des trous de sécurité ambulants.

La manière fiable de savoir si un processus est vivant, c'est d'être son
père : le processus est vivant (éventuellement mort-vivant) jusqu'à ce qu'on
ait appelé wait dessus, et après il ne l'est plus. Les autres méthodes sont
au mieux bancales. Avoir un pipe connecté au processus est à peu près sûr
mais fragile. Quant à tester si le PID existe, c'est tout simplement une
connerie : les PID peuvent être réutilisés par le système.

Je ne vois pas ce que pthread_detach vient faire là-dedans



C'est simple : de même qu'un processus est vivant jusqu'à ce que son père
ait appelé wait, un thread est vivant (et on peut fiablement appeler
pthread_kill dessus) jusqu'à ce que quelqu'un ait appelé pthread_join.

pthread_detach place un pthread_join implicite et automatique quand le
thread se termine, il devient donc impossible de savoir s'il est mort ou
pas, et donc il faut considérer qu'il est peut-être mort, et s'abstenir de
lui envoyer des signaux.

Le principe est simple : pthread_detach, ça veut dire exactement : ce thread
ne m'intéresse plus. Quand un thread ne nous intéresse pas, on ne lui envoie
pas de signaux. Et réciproquement, quand on envisage d'envoyer des signaux à
un thread, on ne le détache pas.

vu ce qui est inscrit sur
<http://udrepper.livejournal.com/tag/programming+posix>



Ça me semble assez raisonnable. De toute évidence il a loupé la possibilité
de garder trace de l'état du thread au niveau du pthread_join, qui me semble
plus simple que de faire ça au niveau du pthread_exit, mais ce qu'il dit est
globalement valable.

En théorie, c'est bien. En pratique, ce n'est pas forcément
faisable.



Bien sûr que si c'est faisable.

Pour ce qui est de n'envoyer des signaux qu'aux threads dont on sait qu'ils
sont vivants, c'est même très simple. Il suffit par exemple de suivre cette
ligne directrice :

- le thread_t d'un thread n'est stocké qu'à un unique endroit dans le
programme, toutes les fonctions qui en ont besoin se réfèrent à cet
endroit ;

- quand un thread_join ou un thread_detach est effectué, le thread_t
correspondant est invalidé à l'endroit unique où il est stocké.

Bien sûr, si tu as commencé à passer des thread_t en paramètres par-ci
par-là, tu n'as plus qu'à te taper la tête contre les murs d'avoir conçu tes
structures de données à la lumière d'une mauvaise lecture de la norme.

Quant à se passer complètement de signaux, ce qui est de loin préférable, je
pense que c'est presque toujours possible, à condition de se rappeler la loi
0 de la programmation avec des threads POSIX :

Il n'y a pas de problèmes avec les threads qui ne puisse se résoudre avec
plus de threads.

Le candidat pour remplacer un signal, c'est bien entendu une condition.