9
votes

Débloquer RECVFROM lorsque la prise est fermée

Disons que je démarre un fil à recevoir sur un port. L'appel de socket bloquera sur RECVFROM. Ensuite, dans un autre fil, je ferme la prise.

sous Windows, cela débloquera RECVFROM et mon exécution de thread finira.

sur Linux, cela ne débloque pas RECVFROM et, par conséquent, mon thread est assis ne rien faire pour toujours, et l'exécution du fil ne se termine pas.

Quelqu'un peut-il m'aider avec ce qui se passe sur Linux? Lorsque la prise est fermée, je veux recvrom pour débloquer

Je continue à lire sur l'utilisation de Select (), mais je ne sais pas comment l'utiliser pour mon cas particulier.


0 commentaires

4 Réponses :


4
votes

Pas une réponse, mais la page Linux Fermer Man contient la citation intéressante:

Il est probablement imprudent de fermer les descripteurs de fichiers pendant qu'ils peuvent être dans Utilisez les appels système dans d'autres threads dans le même processus. Depuis un fichier Descripteur peut être réutilisé, il y a des conditions de race obscur qui peut provoquer des effets secondaires inattendus.


2 commentaires

C'est pourquoi j'ai dit "d'une manière ou d'une autre dans un autre fil, je ferme la prise ... Ce cas doit être manipulé et je ne veux pas que la possibilité de mon programme suspendue à jamais à cause d'un fil bloqué sur une prise en attente de la Recvrom quand sa fermeture


@ user657178 Ce cas n'a pas besoin d'être manipulé car le code qui est correct sinon ne peut pas éventuellement déclencher ce cas.



6
votes

Voici un croquis d'un moyen simple d'utiliser SELECT () pour faire face à ce problème:

// Note: untested code, may contain typos or bugs
static volatile bool _threadGoAway = false;

void MyThread(void *)
{
   int fd = (your socket fd);
   while(1)
   {
      struct timeval timeout = {1, 0};  // make select() return once per second

      fd_set readSet;
      FD_ZERO(&readSet);
      FD_SET(fd, &readSet);

      if (select(fd+1, &readSet, NULL, NULL, &timeout) >= 0)
      {
         if (_threadGoAway)
         {
            printf("MyThread:  main thread wants me to scram, bye bye!\n");
            return;
         }
         else if (FD_ISSET(fd, &readSet))
         {
            char buf[1024];
            int numBytes = recvfrom(fd, buf, sizeof(buf), 0);
            [...handle the received bytes here...]
         }
      }
      else perror("select");
   }
}

// To be called by the main thread at shutdown time
void MakeTheReadThreadGoAway()
{
   _threadGoAway = true;
   (void) pthread_join(_thread, NULL);   // may block for up to one second
}


8 commentaires

Et je vous rappelle à nouveau que la réponse "acceptée" pour cette question est tout simplement fausse. Chaque UNIX - de l'original BSD et SYS V ENDWARD - a garanti que lire () jamais bloquer après avoir sélectionné () indique que le socket est prêt. Et ainsi la spécification POSIX. Si Linux se comporte différemment, c'est un bug de Linux. Vous ne devez pas encourager les gens à polluer leur code avec des ordures pour répondre aux systèmes brisés.


Bonjour Nemo, si vous dites que le bogue de Linux est corrigé maintenant et que ma mise en garde est trompeuse, c'est une chose. Si OTOH, vous dites que le bogue est toujours présent, mais personne ne doit en parler, alors vous ne mettez que les gens à l'échec. Garder les gens ignorants du comportement de Linux (MIS) n'empêchera pas le problème de les mordre, il ne les empêchera que de pouvoir planifier à l'avance pour la manière dont ils veulent faire face à la question.


Un point juste. Je pense qu'il y a une chance non nulle que ce bogue a été corrigé, car de violer le POSIX si flagreux car il n'y a pas de bonnes raisons est stupide et que les développeurs Linux ne sont pas stupides. Je vais essayer de demander à la liste de diffusion de Linux-Kernel. Et je m'excuse si mon ton était trop hostile; J'ai eu une mauvaise journée...


@Nemo Où puis-je trouver cette garantie? Et quelle clause de Posix pensez-vous que cela viole? Cela semble fou pour moi. Cela signifierait, par exemple, qu'un protocole de réseau vous permettrait de "annuler" les données non traités ne peut être pris en charge par POSIX, ce qui ressemble à une réclamation extraordinaire. Ma compréhension est que SELECT est censé être neutre de protocole et que vous ne pouvez pas obtenir une garantie qu'un lue ne bloquerait pas sans tenir compte des règles du protocole. Par exemple, pourquoi la mise en oeuvre de l'UDP ne peut-elle pas déposer un datagramme après avoir déclenché un lire Hit depuis Sélectionnez ? Quelle règle POSIX est violée?


@Nemo Où ce scénario enfreint-il POSIX? 1. Les checksums UDP sont désactivés. 2. Un datagramme avec une somme de contrôle invalide est reçu sur une prise connectée. 3. A Sélectionnez appel débloque parce qu'un lu n'aurait pas été bloqué. 4. L'application de la liste de contrôle est activée. 5. A lire appelez maintenant des blocs car aucun datagramme avec une somme de contrôle valide n'a été reçu.


@Davidschwartz: Évidemment, si vous faites quelque chose à la prise entre le SELECT et le lisez (par exemple, activer l'application de la vérification de la vérification ou diable juste un autre appel à lire ) Vous pourriez changer de propriété "prêt à lire". Ce dont nous parlons est SELECT DISIONNAIRE La prise est prête à lire, puis à lire immédiatement Lire blocage. Aucun Unix n'a jamais permis cela, mais Linux fait ... et il viole clairement POSIX (voir pubs.opengroup.org/onlinepubs/9699919799/fonctions/select.ht ml et rechercher" considéré comme prêt ").


@Nemo Activation ou désactivation de l'UDP Checksum Appliquer est pas une opération sur la prise, c'est une opération sur la connexion. Et il n'ya aucun moyen que vous puissiez savoir si quelque chose d'autre a fait quelque chose à la connexion car la connexion est partagée. Donc, quelque chose que vous n'avez pas de contrôle (une opération sur la connexion partagée) peut modifier la propriété de lecture. Et vous êtes mal interprété POSIX. Il ne dis pas qu'une lecture future réelle ne bloquera pas, il dise une lecture hypothétique simultanée / i> ne bloque pas.


@NEMO Ce n'est pas différent d'une fonction "Obtenir de l'espace libre" disant que l'espace libre signifie qu'une écriture ne retournerait pas une erreur "sans espace". Cela ne signifie pas qu'il y a une garantie d'une erreur future réelle ne retournera pas une erreur "sans espace", car la mise en œuvre n'a pas de contrôle complet sur l'espace disque et ne gèle pas le monde pour vous. Même avec une connexion.



16
votes

appel fermeture (chaussette, shut_rdrwr) sur la prise, puis attendez que le fil soit quitté. (I.e. pthread_join ).

Vous penseriez que fermer () débloquerait le recvfrom () , mais il ne fonctionne pas sous Linux.


3 commentaires

Shut_RD suffit.


Ce n'est pas le cas et ne peut pas être, garanti de débloquer recvfrom sur n'importe quel système . Il existe une race inhérente qui ne peut être résolue que par la synchronisation dans l'espace utilisateur.


arrêt (socket, shut_rdrwr) a fonctionné parfaitement à Linux .... Merci



4
votes

Vous demandez l'impossible. Il n'y a tout simplement pas de moyen possible pour le fil qui appelle Fermer pour savoir que l'autre thread est bloqué dans Recvfrom . Essayez d'écrire du code qui garantit que cela se produit, vous constaterez que c'est impossible.

Peu importe ce que vous faites, il sera toujours possible pour l'appel à Fermer pour courir avec l'appel à RECVFROM . L'appel à Fermer Modifie ce que le descripteur de socket fait référence, il peut donc modifier la signification sémantique de l'appel à Recvrom . .

Il n'y a aucun moyen pour le fil qui entre Recvfrom à un signal d'une manière ou d'une autre sur le thread qui appelle Fermer qu'il est bloqué (par opposition à être sur le point de bloquer ou juste entrer dans l'appel du système). Donc, il n'ya littéralement aucun moyen possible d'assurer le comportement de Fermer et Recvrom sont prévisibles.

Considérez les éléments suivants:

  1. Un thread est sur le point d'appeler Recvfrom , mais il est préempté par d'autres choses que le système doit faire.
  2. plus tard, le fil appelle Fermer .
  3. Un thread a commencé par les appels de la bibliothèque d'E / S du système Socket et obtient le même décsriptor que celui que vous Fermer D.
  4. Enfin, les appels de threads recvfrom , et maintenant, il reçoit à partir de la prise ouverte de la bibliothèque.

    oups.

    Ne jamais faire quelque chose même à distance comme ça. Une ressource ne doit pas être libérée alors qu'un autre fil est, ou pourrait être, l'utiliser. Période.


0 commentaires