web-dev-qa-db-fra.com

104, Erreur de socket «Réinitialisation de la connexion par l'homologue», ou Quand la fermeture d'un socket entraîne-t-elle un RST plutôt que FIN?

Nous développons un Python et un site Web client en parallèle. Lorsque nous faisons une demande HTTP du client au service, un appel déclenche systématiquement un socket.error dans socket.py , en lecture:

(104, "Connexion réinitialisée par l'homologue")

Lorsque j'écoute avec Wireshark, les "bonnes" et "mauvaises" réponses sont très similaires:

  • En raison de la taille de l'en-tête OAuth, la demande est divisée en deux paquets. Le service répond aux deux avec ACK
  • Le service envoie la réponse, un paquet par en-tête (HTTP/1.0 200 OK, puis l'en-tête Date, etc.). Le client répond à chacun avec ACK.
  • (Bonne demande) le serveur envoie un FIN, ACK. Le client répond avec un FIN, ACK. Le serveur répond ACK.
  • (Mauvaise demande) le serveur envoie un RST, ACK, le client n'envoie pas de réponse TCP, le socket.error est levé côté client.

Le service Web et le client s'exécutent sur une boîte Gentoo Linux x86-64 exécutant glibc-2.6.1. Nous utilisons Python 2.5.2 dans le même virtual_env.

Le client est une application Django 1.0.2 qui appelle httplib2 0.4.0 pour faire des demandes. Nous signons des demandes avec l'algorithme de signature OAuth, avec le jeton OAuth toujours défini sur une chaîne vide.

Le service exécute Werkzeug 0.3.1, qui utilise wsgiref.simple_server de Python. J'ai exécuté l'application WSGI via wsgiref.validator sans aucun problème.

Il semble que cela devrait être facile à déboguer, mais lorsque je trace une bonne demande côté service, cela ressemble à la mauvaise demande, dans la fonction socket._socketobject.close (), transformant les méthodes déléguées en méthodes factices. Lorsque la méthode send ou sendto (ne me souviens pas laquelle) est désactivée, FIN ou RST est envoyé et le client commence le traitement.

"La réinitialisation de la connexion par l'homologue" semble blâmer le service, mais je ne fais pas confiance à httplib2 non plus. Le client peut-il être en faute?

** Débogage supplémentaire - Ressemble à un serveur sous Linux **

J'ai un MacBook, j'ai donc essayé d'exécuter le service sur l'un et le site Web client sur l'autre. Le client Linux appelle le serveur OS X sans le bogue (FIN ACK). Le client OS X appelle le service Linux avec le bogue (RST ACK et a (54, 'Connection reset by peer')). Il semble donc que ce soit le service fonctionnant sous Linux. Est-ce x86_64? Une mauvaise glibc? wsgiref? Toujours à la recherche...

** Tests supplémentaires - wsgiref semble floconneux **

Nous sommes passés en production avec Apache et mod_wsgi, et les réinitialisations de connexion ont disparu. Voir ma réponse ci-dessous, mais mon conseil est d'enregistrer la réinitialisation de la connexion et de réessayer. Cela permettra à votre serveur de fonctionner correctement en mode développement et solidement en production.

30
jwhitlock

J'ai eu ce problème. Voir Le problème Python "Connection Reset By Peer" .

Vous avez (très probablement) rencontré de petits problèmes de synchronisation basés sur le Python Global Interpreter Lock).

Vous pouvez (parfois) corriger cela avec une time.sleep(0.01) placée stratégiquement.

"Où?" tu demandes. Me bat. L'idée est de fournir une meilleure concurrence de threads dans et autour des demandes des clients. Essayez de le mettre juste avant de faire la demande afin que le GIL soit réinitialisé et que l'interpréteur Python puisse effacer tout threads en attente.

21
S.Lott

N'utilisez pas wsgiref pour la production. Utilisez Apache et mod_wsgi, ou autre chose.

Nous continuons à voir ces connexions se réinitialiser, parfois fréquemment, avec wsgiref (le backend utilisé par le serveur de test werkzeug, et éventuellement d'autres comme le Django serveur de test). Notre solution était de consigner l'erreur, réessayez l'appel dans une boucle, et abandonnez après dix échecs. httplib2 essaie deux fois, mais nous en avions besoin de quelques autres.

Nous n'avons jamais vu une connexion réinitialisée lors de l'exécution via Apache et mod_wsgi. Je ne sais pas ce qu'ils font différemment (peut-être qu'ils les masquent simplement), mais ils n'apparaissent pas.

Lorsque nous avons demandé de l'aide à la communauté de développeurs locale, quelqu'un a confirmé qu'il voyait beaucoup de réinitialisations de connexion avec wsgiref qui disparaissent sur le serveur de production. Il y a un bug là-bas, mais il va être difficile de le trouver.

11
jwhitlock

Je me rends compte que vous utilisez python, mais j'ai trouvé cet article Java utile).

http://Java.Sun.com/javase/6/docs/technotes/guides/net/articles/connection_release.html

5
Sean McCauliff

Normalement, vous obtiendrez un RST si vous effectuez une fermeture qui ne persiste pas (c'est-à-dire dans laquelle les données peuvent être rejetées par la pile si elles n'ont pas été envoyées et ACK) et une FIN normale si vous autorisez la fermeture de s'attarder (c'est-à-dire que la fermeture attend que les données en transit soient ACK).

Peut-être que tout ce que vous devez faire est de mettre votre socket en attente afin de supprimer la condition de concurrence entre une fermeture non persistante effectuée sur le socket et les ACK qui arrivent?

2
Len Holgate

Cependant, j'ai eu le même problème avec le téléchargement d'un très gros fichier à l'aide d'un client python-requests sur un backend nginx + uwsgi.

Ce qui a fini par être la cause était que le backend avait un plafond sur la taille maximale du fichier pour les téléchargements inférieur à ce que le client essayait d'envoyer.

L'erreur ne s'est jamais présentée dans nos journaux uwsgi car cette limite était en fait celle imposée par nginx.

Augmenter la limite dans nginx a supprimé l'erreur.

1
David Simic