Bug avec mutex robuste

J’essaie d’utiliser des mutex robustes sur Linux pour protéger les ressources entre les processus et il semble que dans certaines situations, ils ne se comportent pas de manière “robuste”. Par “robuste”, je veux dire que pthread_mutex_lock devrait renvoyer EOWNERDEAD si le processus propriétaire du verrou est terminé.

Voici le scénario où cela ne fonctionne pas:

2 processus p1 et p2. p1 crée un mutex robuste et l’attend (après la saisie de l’utilisateur). p2 a 2 threads: le thread 1 mappe dans le mutex et l’acquiert. Le thread 2 (après que le thread 1 a acquis le mutex) se transforme également dans le même mutex et attend (depuis que le thread 1 le possède maintenant). Notez également que p1 commence à attendre sur le mutex après que p2-thread1 l’ait déjà acquis.

Maintenant, si on termine p2, p1 ne se débloque jamais (ce qui signifie que pthread_mutex_lock ne retourne jamais) contrairement à la supposée “robustesse” où p1 devrait être débloqué avec l’erreur EOWNERDEAD.

Voici le code:

p1.cpp:

#include  #include  #include  #include  #include  #include  #include  #include  struct MyMtx { pthread_mutex_t m; }; int main(int argc, char **argv) { int r; pthread_mutexattr_t ma; pthread_mutexattr_init(&ma); pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED); pthread_mutexattr_setrobust_np(&ma, PTHREAD_MUTEX_ROBUST_NP); int fd = shm_open("/test_mtx_p", O_RDWR|O_CREAT, 0666); ftruncate(fd, sizeof(MyMtx)); MyMtx *m = (MyMtx *)mmap(NULL, sizeof(MyMtx), PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0); //close (fd); pthread_mutex_init(&m->m, &ma); puts("Press Enter to lock mutex"); fgetc(stdin); puts("locking..."); r = pthread_mutex_lock(&m->m); printf("pthread_mutex_lock returned %d\n", r); puts("Press Enter to unlock"); fgetc(stdin); r = pthread_mutex_unlock(&m->m); printf("pthread_mutex_unlock returned %d\n", r); puts("Before pthread_mutex_destroy"); r = pthread_mutex_destroy(&m->m); printf("After pthread_mutex_destroy, r=%d\n", r); munmap(m, sizeof(MyMtx)); shm_unlink("/test_mtx_p"); return 0; } 

p2.cpp:

  #include  #include  #include  #include  #include  #include  #include  #include  struct MyMtx { pthread_mutex_t m; }; static void *threadFunc(void *arg) { int fd = shm_open("/test_mtx_p", O_RDWR|O_CREAT, 0666); ftruncate(fd, sizeof(MyMtx)); MyMtx *m = (MyMtx *)mmap(NULL, sizeof(MyMtx), PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0); sleep(2); //to let the first thread lock the mutex puts("Locking from another thread"); int r = 0; r = pthread_mutex_lock(&m->m); printf("locked from another thread r=%d\n", r); } int main(int argc, char **argv) { int r; int fd = shm_open("/test_mtx_p", O_RDWR|O_CREAT, 0666); ftruncate(fd, sizeof(MyMtx)); MyMtx *m = (MyMtx *)mmap(NULL, sizeof(MyMtx), PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0); //close (fd); pthread_t tid; pthread_create(&tid, NULL, threadFunc, NULL); puts("locking"); r = pthread_mutex_lock(&m->m); printf("pthread_mutex_lock returned %d\n", r); puts("Press Enter to terminate"); fgetc(stdin); kill(getpid(), 9); return 0; } 

Tout d’abord, exécutez p1, puis exécutez p2 et attendez qu’il affiche “Verrouillage d’un autre thread”. Appuyez sur Entrée sur le shell de p1 pour verrouiller le mutex, puis appuyez sur Entrée sur le shell de p2 pour terminer p2, ou vous pouvez simplement le tuer d’une autre manière. Vous verrez que p1 imprime “locking …” et que pthread_mutex_lock ne retourne jamais.

Le problème ne se produit pas tout le temps, il semble que cela dépend du moment. Si vous laissez un peu de temps après que p1 commence à se bloquer et avant de terminer p2, cela fonctionne parfois et pthread_mutex_lock de p2 renvoie 130 (EOWNERDEAD). Mais si vous terminez p2 juste après ou peu de temps après que p1 commence à attendre sur le mutex, p1 ne sera jamais débloqué.

Quelqu’un d’autre a-t-il déjà rencontré le même problème?

Comportement juste vérifié avec la version de glibc: 2.11.1 sur Linux Kernel 2.6.32 et plus récent.

Ma première découverte: si vous appuyez sur Entrée dans p1 avant “Verrouiller depuis un autre thread” dans p2 (dans les 2s), le mutex robuste fonctionne correctement resp. comme on pourrait s’y attendre. Conclusion: L’ordre des threads en attente est important.

Le premier thread en attente est réveillé. Malheureusement, c’est le fil dans p2 qui, à ce moment-là, est tué.

Voir https://lkml.org/lkml/2013/9/27/338 pour une description du problème.

Je ne sais pas s’il existe des correctifs / correctifs du kernel. Vous ne savez même pas si cela est considéré comme un bug.

Néanmoins, il semble y avoir une solution pour tout le désordre. Utilisez des mutex robustes avec PTHREAD_PRIO_INHERIT:

 pthread_mutexattr_setprotocol(&ma, PTHREAD_PRIO_INHERIT); 

À l’intérieur du kernel (futex.c) au lieu de handle_futex_death (), un autre mécanisme dans exit_pi_state_list () gère le réveil des autres serveurs mutex. Cela semble résoudre le problème.

Essayez de simplifier votre problème. Il semble que votre problème est la séquence d’exécution.
Considérez toujours le pire scénario: même si vous exécutez A, alors B, B peut encore finir alors que A commence à courir. Ajoutez le contrôle mutex pour cela si nécessaire.
Voici un exemple simple pour A (producteur) et B (consommateur):

  Main: Call A A: Lock Call B Produce Unlock B: Lock Consume Unlock