50 votes

Comment récupérer un sémaphore lorsque le processus qui l'a décrémenté à zéro se plante ?

J'ai plusieurs applications compilées avec g++, fonctionnant sous Ubuntu. J'utilise des sémaphores nommés pour coordonner les différents processus.

Tout fonctionne bien sauf dans la situation suivante : Si l'un des processus appelle sem_wait() ou sem_timedwait() pour décrémenter le sémaphore, puis se plante ou est tué -9 avant d'avoir eu la chance d'appeler sem_post() alors, à partir de ce moment, le sémaphore en question est "inutilisable".

Par "inutilisable", je veux dire que le compte du sémaphore est maintenant à zéro, et que le processus qui aurait dû le remettre à 1 est mort ou a été tué.

Je ne peux pas trouver un sem_*() API qui pourrait me dire que le processus qui l'a décrémenté en dernier s'est écrasé.

Est-ce qu'il me manque une API quelque part ?

Voici comment j'ouvre le sémaphore nommé :

sem_t *sem = sem_open( "/testing",
    O_CREAT     |   // create the semaphore if it does not already exist
    O_CLOEXEC   ,   // close on execute
    S_IRWXU     |   // permissions:  user
    S_IRWXG     |   // permissions:  group
    S_IRWXO     ,   // permissions:  other
    1           );  // initial value of the semaphore

Voici comment je le décrémente :

struct timespec timeout = { 0, 0 };
clock_gettime( CLOCK_REALTIME, &timeout );
timeout.tv_sec += 5;

if ( sem_timedwait( sem, &timeout ) )
{
    throw "timeout while waiting for semaphore";
}

52voto

Stéphane Points 3900

Il s'avère qu'il n'y a aucun moyen de récupérer le sémaphore de manière fiable. Bien sûr, n'importe qui peut post_sem() au sémaphore nommé pour que le compte repasse au-dessus de zéro, mais comment savoir quand une telle récupération est nécessaire ? L'API fournie est trop limitée et n'indique en aucune façon quand cela s'est produit.

Attention aux outils ipc également disponibles -- les outils communs ipcmk , ipcrm y ipcs sont seulement pour les sémaphores SysV périmés. Ils ne fonctionnent pas avec les nouveaux sémaphores POSIX.

Mais il semble qu'il y ait d'autres choses qui peuvent être utilisées pour verrouiller des choses, que le système d'exploitation libère automatiquement lorsqu'une application meurt d'une manière qui ne peut pas être attrapée dans un gestionnaire de signaux. Deux exemples : un socket d'écoute lié à un port particulier, ou un verrou sur un fichier spécifique.

J'ai décidé que le verrouillage d'un fichier est la solution dont j'avais besoin. Ainsi, au lieu d'un sem_wait() y sem_post() appel, j'utilise :

lockf( fd, F_LOCK, 0 )

et

lockf( fd, F_ULOCK, 0 )

Lorsque l'application se termine, de quelque manière que ce soit, le fichier est automatiquement fermé, ce qui libère également le verrou du fichier. Les autres applications clientes qui attendent le "sémaphore" sont alors libres de procéder comme prévu.

Merci pour votre aide, les gars.

1 votes

+1, j'ai fini par faire la même chose, les sémaphores sont inutiles dans de tels scénarios.

1 votes

Quelqu'un m'a envoyé un e-mail pour demander plus de détails. J'ai écrit un petit article de blog il y a presque 3 ans lorsque j'ai rencontré ce problème. Plus de détails sur la façon dont je l'ai résolu avec le verrouillage des fichiers sont disponibles ici : charette.no-ip.com:81/programmation/2010-01-13_PosixSemaphores/

0 votes

Peut-on obtenir la même chose en ouvrant et en fermant simplement un fichier ? J'ai trouvé ceci sur la page de manuel de open() : "Lors de l'ouverture d'un fichier, un verrou avec la sémantique flock(2) peut être obtenu en définissant O_SHLOCK pour un verrou partagé, ou O_EXLOCK pour un verrou exclusif."

6voto

Utilisez un fichier verrou au lieu d'un sémaphore, un peu comme la solution de @Stéphane mais sans les appels flock(). Vous pouvez simplement ouvrir le fichier en utilisant un verrou exclusif :

//call to open() will block until it can obtain an exclusive lock on the file.
errno = 0;
int fd = open("/tmp/.lockfile", 
    O_CREAT | //create the file if it's not present.
    O_WRONLY | //only need write access for the internal locking semantics.
    O_EXLOCK, //use an exclusive lock when opening the file.
    S_IRUSR | S_IWUSR); //permissions on the file, 600 here.

if (fd == -1) {
    perror("open() failed");
    exit(EXIT_FAILURE);
}

printf("Entered critical section.\n);
//Do "critical" stuff here.

//exit the critical section
errno = 0;
if (close(fd) == -1) {
    perror("close() failed");
    exit(EXIT_FAILURE);
}

printf("Exited critical section.\n");

2 votes

Bon code, avec 1 modification : Vous devriez créer le fichier de verrouillage avant que la contention ne commence, et, bien sûr, le garder lié. Sinon, dans mes tests sous Mac OS X 10.10 DP5, open() peut réussir pour deux processus pairs qui se disputent la création initiale du fichier, à quelques millisecondes près. Le problème se produit avec le code de Stéphane ou de Raffi. J'ai ensuite fait un test de stress. Résultat : Le code de Raffi fonctionne parfaitement, celui de Stéphane pas tout à fait. Je n'ai pas étudié pourquoi. Si cela vous intéresse, voyez github.com/jerrykrinock/ClassesObjC/blob/master/SSYSemaphore.h et .m.

0 votes

@JerryKrinock Mais, ne open() créer le fichier de verrouillage s'il n'est pas présent (avec l'indicateur O_CREAT) ?

1 votes

Je vais tirer à vue, car je n'ai pas les 30 minutes qu'il faudrait pour rafraîchir ma compréhension de la situation. Je pense que la réponse est que, oui, open() avec O_CREAT créera le fichier si nécessaire, mais si deux processus exécutent open() à quelques millisecondes d'intervalle, les résultats sont imprévisibles. D'où ma suggestion de créer le fichier de verrouillage avant que cela n'ait de l'importance ; enfin, j'ajouterai, à moins qu'il soit acceptable que la première contention soit un jetable.

5voto

Steve Lazaridis Points 1636

C'est un problème typique de la gestion des sémaphores. Certains programmes utilisent un seul processus pour gérer l'initialisation/la suppression du sémaphore. En général, ce processus ne fait que cela et rien d'autre. Vos autres applications peuvent attendre que le sémaphore soit disponible. J'ai vu cela fait avec l'API de type SYSV, mais pas avec POSIX. Similaire à ce que ' Canard Il faut donc utiliser l'option SEM_UNDO dans votre appel à semop().

Mais, Avec les informations que vous avez fournies, je vous suggère de ne pas utiliser les sémaphores. Surtout si votre processus est en danger d'être tué ou de s'écraser. Essayez d'utiliser quelque chose que le système d'exploitation nettoiera automatiquement pour vous.

2voto

Duck Points 17054

Vous devez vérifier, mais je crois que sem_post peut être appelé à partir d'un gestionnaire de signaux. Si vous êtes en mesure d'identifier certaines des situations qui ralentissent le processus, cela pourrait vous aider.

Contrairement à un mutex, n'importe quel processus ou thread (avec des permissions) peut poster sur le sémaphore. Vous pouvez écrire un utilitaire simple pour le réinitialiser. Vous savez probablement quand votre système est bloqué. Vous pouvez l'arrêter et exécuter le programme utilitaire.

De plus, le sémaphore est généralement listé sous /dev/shm et vous pouvez le supprimer.

Les sémaphores SysV sont plus adaptés à ce scénario. Vous pouvez spécifier SEM_UNDO, dans lequel le système annulera les modifications apportées au sémaphore par un processus s'il meurt. Ils ont également la possibilité de vous indiquer le dernier identifiant de processus à avoir modifié le sémaphore.

1 votes

Certains signaux comme kill -9 contournent les signaleurs, ce qui est la situation que j'ai rencontrée. J'ai un gestionnaire de signaux pour ceux que je peux attraper, et dans le destructeur d'un objet basé sur la portée, j'appelle sem_post() lorsque la pile se déroule. Mais j'espérais résoudre le problème de ces quelques signaux persistants que je ne peux pas attraper.

1 votes

Je pense qu'une question juste est de demander qui sont les utilisateurs et pourquoi ils tuent l'application de cette façon ? Vous pouvez essayer la route SysV ou même les verrous de fichiers, qui devraient s'inverser lorsque le processus meurt.

0 votes

En fait, c'est ce que j'ai décidé de faire hier soir. Puisque les fichiers qui ont été open() et lockf() sont automatiquement libérés lorsque les applications sont tuées -9, cette méthode de "communication" fonctionne en fait de manière plus fiable que les sémaphores compte tenu de ce que j'ai besoin de coordonner.

1voto

Carl Smotricz Points 36400

Vous devriez être en mesure de le trouver à partir du shell en utilisant lsof . Alors vous pouvez peut-être le supprimer ?

Mise à jour

Ah oui... man -k semaphore à la rescousse.

Il semble que vous pouvez utiliser ipcrm pour se débarrasser d'un sémaphore. Il semble que vous ne soyez pas le premier à avoir ce problème.

1 votes

Oui, je connais l'ipcrm, mais ça ne m'aide pas. Si je savais que le sémaphore avait été perdu, je pourrais tout aussi bien utiliser sem_post() pour le "récupérer". Le problème semble être qu'aucun événement n'est déclenché pour indiquer que l'application qui l'a décrémenté en dernier a été tuée.

1 votes

De plus, je viens de remarquer sur la page de manuel que ipcrm ne fonctionne que sur les anciens sémaphores SysV, et non sur les sémaphores POSIX. Idem pour ipcs.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X