close() est de ne pas fermer correctement le connecteur
J'ai un serveur multithread (pool de threads) qui est la manipulation d'un grand nombre de demandes (jusqu'à 500/sec pour un nœud), à l'aide de 20 fils. Il y a un thread d'écoute qui accepte les connexions entrantes et les files d'attente pour le gestionnaire de threads du processus. Une fois que la réponse est prêt, les fils puis écrire sur le client et fermer le socket. Tout semblait bien se passer jusqu'à récemment, un client de test du programme commencé à traîner au hasard après la lecture de la réponse. Après beaucoup de creuser, il semble que le close() du serveur n'est pas fait de débrancher la prise. J'ai ajouté un peu de débogage imprime le code avec le descripteur de fichier numéro et je reçois ce type de sortie.
Processing request for 21
Writing to 21
Closing 21
La valeur de retour de close() est 0, ou il y aurait un autre debug déclaration imprimée. Après cette sortie avec un client qui se bloque, lsof est montrant une connexion établie.
SERVEUR 8160 racine 21u IPv4 32754237 TCP localhost:9980->localhost:47530 (ÉTABLI)
CLIENT 17747 racine 12u IPv4 32754228 TCP localhost:47530->localhost:9980 (ÉTABLI)
C'est comme si le serveur n'envoie jamais de la séquence d'arrêt pour le client, et cet état se bloque jusqu'à ce que le client est tué, laissant le côté serveur en état d'attente
SERVEUR 8160 racine 21u IPv4 32754237 TCP localhost:9980->localhost:47530 (CLOSE_WAIT)
Aussi, si le client dispose d'un délai spécifié, il expire au lieu de la pendaison. Je peux aussi exécuter manuellement
call close(21)
dans le serveur gdb, et le client puis de le déconnecter. Cela se produit peut-être une fois dans plus de 50 000 demandes, mais peut ne pas se produire pour de longues périodes.
La version Linux: 2.6.21.7-2.fc8xen
Centos version: 5.4 (Final)
socket actions sont comme suit
SERVEUR:
int client_socket;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
while(true) {
client_socket = accept(incoming_socket, (struct sockaddr *)&client_addr, &client_len);
if (client_socket == -1)
continue;
/* insert into queue here for threads to process */
}
Alors le fil ramasse le socket et construit la réponse.
/* get client_socket from queue */
/* processing request here */
/* now set to blocking for write; was previously set to non-blocking for reading */
int flags = fcntl(client_socket, F_GETFL);
if (flags < 0)
abort();
if (fcntl(client_socket, F_SETFL, flags|O_NONBLOCK) < 0)
abort();
server_write(client_socket, response_buf, response_length);
server_close(client_socket);
server_write et server_close.
void server_write( int fd, char const *buf, ssize_t len ) {
printf("Writing to %d\n", fd);
while(len > 0) {
ssize_t n = write(fd, buf, len);
if(n <= 0)
return;//I don't really care what error happened, we'll just drop the connection
len -= n;
buf += n;
}
}
void server_close( int fd ) {
for(uint32_t i=0; i<10; i++) {
int n = close(fd);
if(!n) {//closed successfully
return;
}
usleep(100);
}
printf("Close failed for %d\n", fd);
}
CLIENT:
Côté Client est à l'aide de libcurl v 7.27.0
CURL *curl = curl_easy_init();
CURLcode res;
curl_easy_setopt( curl, CURLOPT_URL, url);
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_callback );
curl_easy_setopt( curl, CURLOPT_WRITEDATA, write_tag );
res = curl_easy_perform(curl);
Rien de compliqué, juste une base curl connexion. Client se bloque dans le transfert.c (dans libcurl) parce que le socket n'est pas perçue comme étant fermé. Il est en attente pour plus de données à partir du serveur.
Choses que j'ai essayé jusqu'à présent:
D'arrêt avant de fermer
shutdown(fd, SHUT_WR);
char buf[64];
while(read(fd, buf, 64) > 0);
/* then close */
Réglage SO_LINGER à proximité de force en 1 seconde
struct linger l;
l.l_onoff = 1;
l.l_linger = 1;
if (setsockopt(client_socket, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1)
abort();
Ces ont fait aucune différence. Toutes les idées seraient grandement appréciés.
MODIFIER, et Cela finissait par être un thread problème de sécurité à l'intérieur d'une file d'attente de la bibliothèque origine du socket pour être manipulé par plusieurs threads.
- Êtes-vous à 100% positif, aucun autre thread ne pouvait l'être à l'aide de la prise lorsque vous appelez
close
sur elle? Comment faites-vous votre non-blocage de lit? - J'ai peur, j'ai juste connecté ici et se souvint de ce problème. J'ai découvert plus tard qu'il y a un thread problème de sécurité dans une file d'attente utilisé pour transmettre les connexions autour de. Il n'y a pas de bug ici. Désolé pour la désinformation.
Vous devez vous connecter pour publier un commentaire.
Voici un code que j'ai utilisé sur de nombreux systèmes Unix (e.g SunOS 4, SGI IRIX, hp-ux 10.20, CentOS 5, Cygwin) pour fermer un socket:
Mais le ci-dessus ne garantit pas qu'un tampon d'écriture est envoyé.
Gracieux près: Il m'a fallu environ 10 ans pour comprendre comment fermer un socket. Mais pour un autre 10 ans, j'ai paresseusement appelé
usleep(20000)
pour un léger retard pour "assurer" que la mémoire tampon d'écriture a été rincé avant de le fermer. De toute évidence, cela n'est pas très intelligent, parce que:usleep()
(mais j'ai l'habitude appeléusleep()
deux fois pour gérer ce cas--un hack).Mais faire une bonne chasse est étonnamment difficile. À l'aide de
SO_LINGER
est apparemment pas la voie à suivre; voir, par exemple:Et
SIOCOUTQ
semble être spécifique à Linux.Note
shutdown(fd, SHUT_WR)
n'est pas arrêter d'écrire, contrairement à son nom, et peut-être contrairement àman 2 shutdown
.Ce code
flushSocketBeforeClose()
attend jusqu'à ce qu'une lecture de zéro octets, ou jusqu'à l'expiration de la minuterie. La fonctionhaveInput()
est un simple wrapper pour sélectionner(2), et est configuré pour bloquer jusqu'à 1/100e de seconde.Exemple d'utilisation:
Ci-dessus, mon
getWallTimeEpoch()
est similaire àtime(),
etPerror()
est un wrapper pourperror().
Edit: Quelques commentaires:
Ma première entrée est un peu gênant. Les OP et Nemo contesté le besoin de nettoyer l'intérieur
so_error
avant de fermer, mais je ne trouver aucune référence à cela. Le système en question était-UX 10.20. Après l'échec d'uneconnect()
, juste appelerclose()
n'a pas communiqué le descripteur de fichier, parce que le système a souhaité offrir une remarquable erreur pour moi. Mais j'ai, comme la plupart des gens, jamais pris la peine de vérifier la valeur de retour declose.
j'ai Donc finalement manqué de descripteurs de fichiers(ulimit -n),
qui a finalement obtenu mon attention.(très petite), Un commentateur a objecté à la codées en dur des arguments numériques à
shutdown()
, plutôt que, par exemple, SHUT_WR pour 1. La réponse la plus simple est que Windows utilise différents #définit la/les énumérations, par exempleSD_SEND
. Et de nombreux autres auteurs (par exemple, Beej) utiliser des constantes, comme le font de nombreux systèmes hérités.Aussi, j'ai toujours, toujours, ensemble FD_CLOEXEC sur toutes mes prises, puisque dans mes applications, je ne veux plus jamais les transmettre à un enfant et, plus important encore, je ne veux pas d'un enfant accroché à l'impact de moi.
Exemple de code pour définir CLOEXEC:
shutdown
doit être utilisé avec les macrosSHUT_RD
etcFINWAIT
caractéristique de TCP.close()
ing un socket, mais il était encore bloque suraccept()
. Une fois que j'ai utiliséshutdown()
, il n'est plus bloquant. Vous avez enregistré plusieurs heures pour moi. Si cela fonctionne vraiment indépendant de la plateforme, dans l'ensemble, je serai heureux d'attribuer un +50 bounty pour vous!Grande réponse de Joseph Quinsey. J'ai des commentaires sur le
haveInput
fonction. Vous vous demandez comment il est probable que select retourne un fd vous n'avez pas inclus dans votre ensemble. Ce serait un des grands OS bug à mon humble avis. C'est le genre de chose que je voudrais vérifier si j'ai écrit des tests unitaires pour leselect
la fonction, non pas dans une simple application.Mon autre commentaire se rapporte à la manipulation de EINTR. En théorie, vous pourriez obtenir coincé dans une boucle infinie si
select
cesse de revenir EINTR, car cette erreur permet à la boucle de recommencer. Compte tenu du très court délai (0.01), il semble hautement improbable. Cependant, je pense que la façon appropriée de traiter ce serait pour les erreurs de retour à l'appelant (flushSocketBeforeClose
). L'appelant peut continuer à appelerhaveInput
a longtemps que son délai d'attente n'a pas expiré, et de déclarer l'échec pour les autres erreurs.PLUS #1
flushSocketBeforeClose
ne sera pas en sortir rapidement en cas deread
de retourner une erreur. Il gardera en boucle jusqu'à l'expiration de ce délai. Vous ne pouvez pas compter sur lesselect
à l'intérieur dehaveInput
d'anticiper toutes les erreurs.read
a des erreurs de ses propres (ex:EIO
).Cela me semble être un bug dans votre distribution Linux.
La Bibliothèque C de GNU de documentation dit:
Rien de compensation tous les indicateurs d'erreur ou d'attente pour les données seront supprimées ou quelque chose de ce genre.
Votre code est très bien; votre O/S a un bug.
Nothing about clearing any error flags or waiting for the data to be flushed or any such thing.
Sans doute, "l'attente pour que les données soient vidées" relève "lorsque vous avez fini d'utiliser un socket".close()
ne fonctionne pas, rien ne fonctionnerait.close()
. C'est en fonction de chaque norme (p. ex. POSIX) et la mise en œuvre (par exemple, la glibc) documentation quelqu'un a cité ici. Si vous avez rien d'autre à faire pour fermer un socket, c'est un bug dans votre bibliothèque C ou S/S... Et comme vous pouvez le voir sur certains autres réponses ici, de tels bugs a existent dans différentes implémentations Unix au fil des ans.