[POSIX] Souci avec pthread_exit() sur threads joignables

12 réponses
Avatar
JKB
Bonsoir à tous,

Je suis en train de chasser un bug sournois avec pthread_exit() dans
un programme POSIX. Il est développé sous Linux mais doit pouvoir
fonctionner sous des BSD.

Ce programme effectue un certain nombre de fork(). Dans chacun des
sous-processus créés par le fork() en question sont lancés deux threads
supplémentaires. Ces deux threads sont dans l'état JOIGNABLE.

Avant de quitter le sous-processus créé, j'attends gentiment que les
threads joignables finissent (il y a un mécanisme d'arrêt). Le
Le fil d'exécution principal (créé par le fork() plus haut) attend
la fin des threads en question grâce à deux appels pthread_join()
avec les références des deux threads créés plus haut.

Or de temps en temps, mon programme entre dans un deadlock lors de
l'appel de pthread_exit(). Les deux pthread_exit() sont en attente
sur le même mutex interne :

(gdb) bt
#0 __lll_lock_wait () at
../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1 0x00007fe0c7b93912 in __GI___pthread_mutex_lock (
mutex=0x7fe0c86f6970 <_rtld_global+2352>)
at ../nptl/pthread_mutex_lock.c:115
#2 0x00007fe0c72839df in __GI___dl_iterate_phdr
(callback=0x7fe0c6f60d60,
data=0x7fe0bfffe980) at dl-iteratephdr.c:41
#3 0x00007fe0c6f620b3 in _Unwind_Find_FDE ()
from /lib/x86_64-linux-gnu/libgcc_s.so.1
#4 0x00007fe0c6f5ea76 in ?? () from
/lib/x86_64-linux-gnu/libgcc_s.so.1
#5 0x00007fe0c6f5fcc0 in ?? () from
/lib/x86_64-linux-gnu/libgcc_s.so.1
#6 0x00007fe0c6f602e6 in _Unwind_ForcedUnwind ()
from /lib/x86_64-linux-gnu/libgcc_s.so.1
#7 0x00007fe0c7b99370 in __GI___pthread_unwind
(buf=<optimized out>) at unwind.c:126
#8 0x00007fe0c7b92345 in __do_cancel () at pthreadP.h:283
#9 __pthread_exit (value=value@entry=0x0) at pthread_exit.c:28
...

Je n'arrive pas à savoir si le problème provient de l'ordre des
pthread_exit() bien que je n'ai rien vu sur un éventuel problème
d'ordre. Les pthread_join() sont toujours appelés dans le même sens,
mais je ne maîtrise pas l'ordre des pthread_exit().

Je viens de passer la journée avec valgrind et je puis assurer qu'il
n'y a pas d'utilisation foireuse de pointeurs.

Est-ce que ce genre de problème rappelle quelque chose à quelqu'un ?

J'ai aussi vu dans la doc de pthread_exit() de Linux la remarque
suivante :

BUGS
Currently, there are limitations in the kernel implementation
logic for wait(2)ing on a stopped thread group with a dead thread
group leader. This can manifest in problems such as a locked
terminal if a stop signal is sent to a foreground process
whose thread group leader has already called pthread_exit().

mais je ne vois pas bien si cela correspond à mon problème et si
oui, comment le contourner.

Une idée ?

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
=> http://loubardes.de-charybde-en-scylla.fr

10 réponses

1 2
Avatar
JKB
Bonjour à tous,
Je vois que ça n'a inspiré personne. J'en suis toujours au même
point et je désespère un peu. Je lance donc un concours d'été, la
chasse au bug.
Depuis mon message initial, j'ai continué à chercher sans succès.
J'ai utilisé les options -fsanitize de gcc, rien n'y fait. Je n'ai
pas trouvé de corruption mémoire, encore moins de corruption du tas
ou de race condition sur ce fichu fil d'exécution.
Le code source est disponible ici :
http://www.rpl2.fr/telechargements.php -> snapshot
Il se compile avec un petit coup de ./autogen.sh et de ./configure
et fonctionne normalement sur Linux (et les xBSD).
Le programme de test est très simple :
#!/usr/local/bin/rpl -spd
TEST
<<
0 -> I << do 'TEST2' detach wfproc 'I' incr I disp until false end >>


TEST2
<<


Il crée un sous processus (DETACH) qui ne fait rien (TEST2 vide),
attend sa fin (WFPROC) et continue indéfiniment.
Au bout d'un certain nombre de cycle qui varie
entre pas beaucoup et plusieurs dizaine de millions, un
pthread_exit() s'arrête sur un mutex interne verrouillé :-(
Ça ne plante pas n'importe où, toujours sur un pthread_exit()
(celui qui se trouve à la ligne 201 du fichier src/interruptions.c,
le pthread_join() correspondant étant aux alentours de la ligne 3550
du même fichier).
Si quelqu'un avait un début d'explication...
Bien 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
=> http://loubardes.de-charybde-en-scylla.fr
Avatar
Lucas Levrel
Le 22 juillet 2016, JKB a écrit :
Bonjour à tous,
Je vois que ça n'a inspiré personne. J'en suis toujours au même
point et je désespère un peu. Je lance donc un concours d'été, la
chasse au bug.

Faute de mieux, quelques idées en vrac :
- testé avec des noyaux différents ? sur des machines différentes ?
- sur un ou plusieurs cœurs ?
- dans ton PO tu disais avoir systématiquement deux threads fils (est-ce
toujours le cas avec l'ECM d'aujourd'hui ?), ne peux-tu pas les
imbriquer (le fils 1 lance le fils 2 et l'attend avant de se terminer) ?
- n'y a-t-il pas moyen avec des mutex d'imposer l'ordre dans lequel les
fils se terminent ?
--
LL
Ἕν οἶδα ὅτι οὐδὲν οἶδα (Σωκράτης)
Avatar
Alain Ketterlin
JKB writes:
[...]
Je vois que ça n'a inspiré personne. J'en suis toujours au m ême
point et je désespère un peu. Je lance donc un concours d'à ©té, la
chasse au bug.

Pas d'idée précise, et ton code est trop gros pour que j'y vois c lair.
Cela dit, la dernière fois que j'ai galéré, c'était à   cause de
pthread_cancel avec des threads de type deferred : la liste des
"cancellation points" contenait des trucs que je ne soupçonnais pas
(strftime, je crois, à cause de la locale), et je me retrouvais avec un
mutex verrouillé et perdu. Ca n'a pas l'air d'etre ton cas exactement,
mais sait-on jamais avec pthread_cancel().
Mes 2 cents, au cas où...
-- Alain.
Avatar
JKB
Le Fri, 22 Jul 2016 23:08:58 +0200,
Lucas Levrel écrivait :
Le 22 juillet 2016, JKB a écrit :
Bonjour à tous,
Je vois que ça n'a inspiré personne. J'en suis toujours au même
point et je désespère un peu. Je lance donc un concours d'été, la
chasse au bug.

Faute de mieux, quelques idées en vrac :
- testé avec des noyaux différents ? sur des machines différentes ?

Sur plusieurs machines avec plusieurs noyaux Linux (et j'ai un
phénomène semblable avec un NetBSD, je ne peux pas tester sous Free,
j'ai besoin des mémoires partagées POSIX).
- sur un ou plusieurs cœurs ?

Plusieurs coeurs.
- dans ton PO tu disais avoir systématiquement deux threads fils (est-ce
toujours le cas avec l'ECM d'aujourd'hui ?), ne peux-tu pas les
imbriquer (le fils 1 lance le fils 2 et l'attend avant de se terminer) ?

Je ne peux pas réduire à un seul thread. J'ai modifié le programme
pour que les threads s'attendent mutuellement. En gros, lorsque le
pthread_exit() et appelé, il se trouve toujours en face du
pthread_join() correspondant.
- n'y a-t-il pas moyen avec des mutex d'imposer l'ordre dans lequel les
fils se terminent ?

C'est fait (et sans mutex). Le processus principal ordonne la fin des
threads et attend gentiment qu'ils finissent. Il maîtrise l'ordre
d'arrêt.
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
=> http://loubardes.de-charybde-en-scylla.fr
Avatar
JKB
Le Sat, 23 Jul 2016 12:47:13 +0200,
Alain Ketterlin écrivait :
JKB writes:
[...]
Je vois que ça n'a inspiré personne. J'en suis toujours au même
point et je désespère un peu. Je lance donc un concours d'été, la
chasse au bug.

Pas d'idée précise, et ton code est trop gros pour que j'y vois clair.
Cela dit, la dernière fois que j'ai galéré, c'était à cause de
pthread_cancel avec des threads de type deferred : la liste des
"cancellation points" contenait des trucs que je ne soupçonnais pas
(strftime, je crois, à cause de la locale), et je me retrouvais avec un
mutex verrouillé et perdu. Ca n'a pas l'air d'etre ton cas exactement,
mais sait-on jamais avec pthread_cancel().
Mes 2 cents, au cas où...

Il n'y a pas de pthread_cancel() dans le code en question. Mais ça
pourrait bien ressembler à un truc comme cela.
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
=> http://loubardes.de-charybde-en-scylla.fr
Avatar
JKB
Le Sat, 23 Jul 2016 12:47:13 +0200,
Alain Ketterlin écrivait :
JKB writes:
[...]
Je vois que ça n'a inspiré personne. J'en suis toujours au même
point et je désespère un peu. Je lance donc un concours d'été, la
chasse au bug.

Pas d'idée précise, et ton code est trop gros pour que j'y vois clair.
Cela dit, la dernière fois que j'ai galéré, c'était à cause de
pthread_cancel avec des threads de type deferred : la liste des
"cancellation points" contenait des trucs que je ne soupçonnais pas
(strftime, je crois, à cause de la locale), et je me retrouvais avec un
mutex verrouillé et perdu. Ca n'a pas l'air d'etre ton cas exactement,
mais sait-on jamais avec pthread_cancel().

Chose amusante : pour les besoins de mon test, je crée en boucle des
processus. Dans chacun d'eux, je lance deux threads totalement
indépendants (comprendre que je peux arrêter de manière indépendante
l'une de l'autre).
Lorsqu'un pthread_exit() entre dans un deadlock, il s'agit
_toujours_ du premier pthread(). Je n'arrive pas à expliquer cela.
Je viens de vérifier que je n'utilisais pas par mégarde de fonction
bizarre dans les threads fautifs, mais je n'ai rien trouvé.
Je conçois mal dans ces conditions une histoire de corruption de la
mémoire car en cas de corruption, ce serait toujours le même
pthread_exit() qui partirait en vrille... D'autant que si le premier
pthread_t est dans un mmap(), le second est beaucoup plus régulier
puisqu'il est dans une structure allouée par un vulgaire malloc().
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
=> http://loubardes.de-charybde-en-scylla.fr
Avatar
Lucas Levrel
Le 25 juillet 2016, JKB a écrit :
Le Fri, 22 Jul 2016 23:08:58 +0200,
Lucas Levrel écrivait :
Faute de mieux, quelques idées en vrac :
- sur un ou plusieurs cœurs ?

Plusieurs coeurs.

Et sur un seul cœur, le bug apparaît aussi ?
- n'y a-t-il pas moyen avec des mutex d'imposer l'ordre dans lequel les
fils se terminent ?

C'est fait (et sans mutex). Le processus principal ordonne la fin des
threads et attend gentiment qu'ils finissent. Il maîtrise l'ordre
d'arrêt.

OK, donc dans les quatre possibilités d'ordre d'exécution ci-dessous, le
problème se présente, et toujours sur le premier thread lancé :
- crée A, crée B, exit A, exit B ;
- crée A, crée B, exit B, exit A ;
- crée B, crée A, exit A, exit B ;
- crée B, crée A, exit B, exit A ?
(Mais peut-être ne peux-tu pas lancer B avant A.)
--
LL
Ἕν οἶδα ὅτι οὐδὲν οἶδα (Σωκράτης)
Avatar
JKB
Le Tue, 26 Jul 2016 10:47:56 +0200,
Lucas Levrel écrivait :
Le 25 juillet 2016, JKB a écrit :
Le Fri, 22 Jul 2016 23:08:58 +0200,
Lucas Levrel écrivait :
Faute de mieux, quelques idées en vrac :
- sur un ou plusieurs cœurs ?

Plusieurs coeurs.

Et sur un seul cœur, le bug apparaît aussi ?

Je ne sais pas (et je ne sais pas comment tester).
- n'y a-t-il pas moyen avec des mutex d'imposer l'ordre dans lequel les
fils se terminent ?

C'est fait (et sans mutex). Le processus principal ordonne la fin des
threads et attend gentiment qu'ils finissent. Il maîtrise l'ordre
d'arrêt.

OK, donc dans les quatre possibilités d'ordre d'exécution ci-dessous, le
problème se présente, et toujours sur le premier thread lancé :
- crée A, crée B, exit A, exit B ;
- crée A, crée B, exit B, exit A ;
- crée B, crée A, exit A, exit B ;
- crée B, crée A, exit B, exit A ?
(Mais peut-être ne peux-tu pas lancer B avant A.)

Je peux. Dans tous les cas, c'est toujours sur le premier
pthread_exit().
Comme je n'ai plus d'autres moyens de tester (je ne suis pas
convaincu par l'instrumentation des routines de type valgrind ou
sanitizer), je suis en train de bissecter le code à grands coups de
#if 0. Lorsque je supprime l'appel à une fonction evaluation() dans
le thread principal, je n'échoue plus jamais sur un pthread_exit().
Le principal problème est que je ne vois pas comment cette fonction
(par ailleurs débugguée depuis longtemps) pourrait influer sur le
pthread_exit() en question. Je suis sûr d'une chose, c'est que cette
fonction evaluation(), dans mon cas, ne crée aucun thread et qu'elle
ne corrompt pas la mémoire...
Je continue ma bissection.
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
=> http://loubardes.de-charybde-en-scylla.fr
Avatar
Samuel DEVULDER
Le 26/07/2016 à 17:53, JKB a écrit :
Je suis sûr d'une chose, c'est que cette
fonction evaluation(), dans mon cas, ne crée aucun thread et qu'elle
ne corrompt pas la mémoire...

Les threads ont typiquement des piles plus petites et/ou plus
contraintes que le programme principal. Est-ce que tu n'aurais pas un
débordement par ce coté ?
Avatar
JKB
Le Tue, 26 Jul 2016 21:16:41 +0200,
Samuel DEVULDER écrivait :
Le 26/07/2016 à 17:53, JKB a écrit :
Je suis sûr d'une chose, c'est que cette
fonction evaluation(), dans mon cas, ne crée aucun thread et qu'elle
ne corrompt pas la mémoire...

Les threads ont typiquement des piles plus petites et/ou plus
contraintes que le programme principal. Est-ce que tu n'aurais pas un
débordement par ce coté ?

J'ai effectivement déjà pensé à ça, mais les threads en question ne
sont constitués que d'une seule fonction (avec quelques appels comme
poll() et équivalents). Je ne pense pas à un dépassement de pile,
d'autant plus qu'un tel dépassement aurait déjà été vu par les
outils de debug que j'utilise et serait reproductible. À mon avis,
c'est plus vicieux que ça. De temps en temps, le processus ayant
créé les deux threads se prend un SIGALRM qui est traité dans un
gestionnaire de signal ad hoc et je me demande si le problème ne
vient pas de là. Naturellement, les threads ont leurs signaux
bloqués.
Je continue à bissecter pour isoler le bug... Et si je le trouve, je
vais encore me traiter de khon.
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
=> http://loubardes.de-charybde-en-scylla.fr
1 2