Faraday: Distinguer TimeoutErrors pour les délais d'ouverture et de lecture

Créé le 9 août 2017  ·  32Commentaires  ·  Source: lostisland/faraday

Dans faraday/adapter/rack.rb, TimeoutError est déclenché pour les délais d'attente d'ouverture et de lecture :

timeout  = env[:request][:timeout] || env[:request][:open_timeout]
response = if timeout
  Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
else ... end

Selon https://stackoverflow.com/questions/10322283/what-is-timeout-and-open-timeout-in-faraday , open_timeout est pour la connexion TCP et timeout est pour la réponse lue.

Ce serait bien d'avoir des types d'exception séparés pour ces délais d'attente. Ensuite, nous pourrions déterminer s'il faut ou non réessayer la demande. Est-ce que l'ajout de quelque chose comme Faraday::Error::OpenTimeoutError et Faraday::Error::ResponseTimeoutError et leur utilisation ici ont-ils un sens ?

feature help wanted

Commentaire le plus utile

Salut @coberlin, je pense que cela pourrait être un ajout intéressant, j'ai juste peur de la rétrocompatibilité.
Cependant, une solution possible pour cela pourrait être d'avoir OpenTimeoutError et ResponseTimeoutError pour hériter de TimeoutError , de sorte que les rescue existants continuent de fonctionner comme prévu.
Cela vaut vraiment la peine d'être testé

Tous les 32 commentaires

Salut @coberlin, je pense que cela pourrait être un ajout intéressant, j'ai juste peur de la rétrocompatibilité.
Cependant, une solution possible pour cela pourrait être d'avoir OpenTimeoutError et ResponseTimeoutError pour hériter de TimeoutError , de sorte que les rescue existants continuent de fonctionner comme prévu.
Cela vaut vraiment la peine d'être testé

Le rack_adapter n'est peut-être pas le bon endroit pour cette fonctionnalité. Je pense que les applications Rack ne font pas nécessairement la distinction entre les délais d'ouverture et de lecture. Peut-être que cette fonctionnalité fonctionnerait dans l'adaptateur HTTPClient ou d'autres adaptateurs ? Depuis adaptateur/httpclient.rb :

    @app.call env
  rescue ::HTTPClient::TimeoutError, Errno::ETIMEDOUT
    raise Faraday::Error::TimeoutError, $!
  rescue ::HTTPClient::BadResponseError => err
    if err.message.include?('status 407')
      raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
    else
      raise Faraday::Error::ClientError, $!
    end
  rescue Errno::ECONNREFUSED, IOError, SocketError
    raise Faraday::Error::ConnectionFailed, $!
  rescue => err
    if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
      raise Faraday::SSLError, err
    else
      raise
    end

::HTTPClient::TimeoutError a 3 sous-classes ConnectTimeoutError, ReceiveTimeoutError, SendTimeoutError, voir par exemple http://www.rubydoc.info/gems/httpclient/2.1.5.2/HTTPClient/TimeoutError

Faraday a déjà Faraday::Error::ConnectionFailed. Est-ce approprié pour ConnectTimeoutError ? Faraday::Error::TimeoutError peut être sous-classé en Faraday::Error::ReceiveTimeoutError et Faraday::Error::SendTimeoutError.

Faraday a déjà Faraday::Error::ConnectionFailed. Est-ce approprié pour ConnectTimeoutError ?

Cela a du sens, mais ce ne serait pas rétrocompatible. Nous devons garder à l'esprit que les gens attrapent déjà Faraday::Error::TimeoutError dans leur application, donc passer à ConnectionFailed freinera ces cas.
Ce que nous voulons faire, à la place, c'est définir 2 sous-classes pour Faraday::Error::TimeoutError dont les noms doivent être aussi génériques que possible :

  • Faraday::Error::OpenTimeoutError
  • Faraday::Error::ReadTimeoutError

L'étape suivante consiste à entrer dans chaque adaptateur et à mapper les exceptions d'adaptateur en conséquence. Par exemple pour le HTTPClient :

  • HTTPClient::ConnectTimeoutError ==> Faraday::Error::OpenTimeoutError
  • HTTPClient::ReceiveTimeoutError ==> Faraday::Error::ReadTimeoutError
  • HTTPClient::TimeoutError ==> Faraday::Error::TimeoutError (cela attrapera également SendTimeoutError, dont je ne suis pas sûr d'avoir un mappage correspondant dans Faraday ou un paramètre spécifique)

Enfin, des tests devraient être ajoutés lorsque cela est possible :)

Salut les gars.

Nous avons eu cette discussion il y a "un peu" de temps (https://github.com/lostisland/faraday/pull/324).

Je réessaye (https://github.com/mistersourcerer/faraday/tree/718_mrsrcr_timeout-wrapping-2nd-chance), j'essaierai d'ouvrir un nouveau PR dès que j'aurai avancé.

Salut @mistersourcerer , merci pour le coup de j'ignorais totalement que la discussion avait eu lieu.
Je suis un peu confus car je vois le PR fermé, mais le changement de code, peut-être de savoir que vous avez fusionné votre changement d'une manière ou d'une autre à la fin 😄
Votre aide serait appréciée dans ce cas car je pense que vous êtes déjà à l'aise avec les tests Timeout de votre travail précédent (même si nous parlons d'il y a 3 ans !).
J'espère que mon explication sur les OpenTimeoutError et ReadTimeoutError est claire, mais si ce n'est pas le cas, merci de me le faire savoir.
Prenez votre temps et ouvrez une pull request une fois que vous avez terminé 👍

Salut @iMacTia.

Si je me souviens bien, nous n'avons pas réussi à régler la situation à l'époque. Mais je ne sais pas exactement pourquoi.
Le problème principal était d'écrire un test qui échouait systématiquement parmi tous les adaptateurs. Donc, je ne pense pas que mon code ait été fusionné à l'époque.
En tout cas, j'ai une idée pour ça quelques années après haha, voyons comment ça se passe.

À l'heure actuelle, les tests pour _EMSynchrony_ échouent sur Travis, mais pas localement. Essayer de le comprendre. Je pense même à ouvrir une RP "précoce" alors peut-être que nous pourrons en discuter.

Et votre explication est limpide, semble la voie parfaite pour aller avec.

Merci pour le travail formidable sur ce, mec.

Merci @mistersourcerer !

RE vos modifications : je ne suis pas vraiment sûr de ce qui s'est passé, mais je vois que @mislav a finalement fusionné vos modifications ici : https://github.com/lostisland/faraday/commit/f73d13ee09814fa68b37efa7bddafa47331948c2

Alors réjouissez-vous, Errno::ETIMEDOUT est déjà encapsulé sous Faraday::Error::TimeoutError sur la plupart (sinon tous) des adaptateurs 😄

Merci d'avoir travaillé sur ce @mistersourcerer !

En regardant votre commit ici , je me demande si pour une compatibilité descendante, nous avons besoin de 2 nouvelles sous-classes : OpenConnectionError < ConnectionError pour Net::HTTP et OpenTimeoutError < TimeoutError pour HttpClient ?

Il semble y avoir une certaine confusion autour de cette question.
La raison en est qu'une décision a été prise sur le #438 pour gérer les erreurs de "délai d'expiration ouvert" en tant que ConnectionFailed . C'est sans doute la meilleure décision, mais la réalité est que quelqu'un a décidé de s'engager dans cette voie.
Maintenant, cela n'affecte pas seulement l'adaptateur Rack mais aussi tous les autres adaptateurs, et leur comportement n'est probablement même pas cohérent.
Je prévois de les standardiser sur le même comportement avec la v1.0 et je garderai ce problème comme référence.

Suite de mon commentaire précédent.

Fondamentalement, nous levons actuellement une erreur Faraday::ConnectionFailed en cas de délai d'attente ouvert, tandis que nous levons un Faraday::TimeoutError pour un délai de lecture. Bien que différents adaptateurs se comportent actuellement de différentes manières, cela semble être le comportement le plus courant.
Cela a été décidé il y a environ 3 ans, mais ici, nous discutons d'avoir un Faraday::TimeoutError pour le premier cas également (avec des sous-classes appropriées pour faire la distinction entre open et close).

D'un côté, je comprends que ce serait plus proche de la réalité, mais si j'analyse la question du point de vue de la mise en œuvre, j'ai du mal à justifier ce changement.
Si j'appelle un service et que je reçois un ConnectionFailed , je sais que mon appel n'a pas pu être traité. Je n'ai probablement pas atteint le serveur, ou je n'ai pas pu résoudre le nom d'hôte, ou quelque chose d'autre s'est produit.
Si je récupère un TimeoutError , ma demande a peut-être été traitée, ou partiellement traitée, et j'ai peut-être manqué la réponse. C'est un cas complètement différent et nécessite de vérifier auprès du serveur que j'appelais ce qui s'est passé.

Faire du délai d'expiration ouvert une sous-catégorie de TimeoutError signifie prendre une situation simple (demande non traitée) sous un domaine plus complexe, et nécessite sûrement des vérifications supplémentaires pour décider quoi faire : était-ce un délai d'expiration ouvert ou une lecture temps libre?

Nous devons le faire:

  1. Décidez comment augmenter les délais d'attente ouverts
  2. Standardisez tous les adaptateurs sur le même comportement

@coberlin @erik-escobedo @mislav @mistersourcerer aimerait avoir votre avis après avoir examiné ce qui précède 😄

Utiliser ConnectionFailed pour les erreurs de délai d'attente ouvert a du sens pour moi et fournirait ce que j'espérais obtenir en distinguant les erreurs de délai d'attente ouvert des autres erreurs de délai d'attente. Pour la cohérence de l'adaptateur, cela signifierait, par exemple, que Net::HTTP est correct tel quel, mais que HTTPClient changerait, avec ConnectTimeoutErrors mappé sur ConnectionFailed au lieu de TimeoutError.

Ce n'est pas grave, une fois que nous décidons de standardiser tous les adaptateurs sur le même comportement (dans la v1.0 évidemment, car ce sera rétrocompatible)

Lancer un cas d'utilisation dans le ring :

Au travail, nous avons souffert de certains délais d'attente en raison de l'échec de Nginx + Kubernetes à se diriger vers les pods suspendus (ou quelque chose du genre). Quoi qu'il en soit, NetHTTP avait l'habitude de lancer des erreurs OpenTimeout et ReadTimeout, et c'était vraiment pratique pour nous de déboguer qui était quoi.

Maintenant que nous sommes passés à Typhoeus, nous avons malheureusement tous les délais d'attente réunis, et il nous est difficile de dire si notre travail sur les problèmes nginx + kuber a été amélioré, ou si nous venons tout juste de faire plus de demandes à un groupe de plus en plus en difficulté. système. Quoi qu'il en soit, le nombre de délais d'attente est à peu près le même, et sans les séparer, nous sommes un peu coincés à deviner.

Je ne pense pas qu'ajouter simplement Faraday::OpenTimeoutError soit suffisant, nous devrions avoir Faraday::OpenTimeoutError et Faraday::ReadTimeoutError s'étendant de Faraday::TimeoutError IMO.

@philsturgeon et qu'en est-il de l'autre solution proposée, cela aiderait-il également ?

Délai d'attente d'ouverture -> Faraday::ConnectionFailed
Délai de lecture -> Faraday::TimeoutError

Cela devrait être le comportement de tous les adaptateurs, mais malheureusement, certains ne se comportent pas comme prévu (c'est-à-dire Typhoeus)

J'ai l'impression que ce sont des choses différentes.

ConnectionFailed ressemble à "Je n'ai aucune idée de comment parler à ce serveur", comme un DNS/IP invalide, etc.

OpenTimeout est "Je sais où se trouve ce serveur, j'attends juste qu'il fasse quelque chose"

OpenTimeout est "Je sais où se trouve ce serveur, j'attends juste qu'il fasse quelque chose"

Je ne suis pas d'accord avec ça, je dirais plutôt :

Délai d'attente ouvert : j'essaie de contacter le serveur, mais je n'y parviens pas (Remarque : la connexion n'est pas encore établie ou « ouverte »).
Read Timeout : j'ai établi une connexion avec le serveur mais j'attends qu'il fasse quelque chose (lecture de la sortie).

Un pare-feu/proxy/load_balancer défectueux ne sont que des exemples simples de la façon dont vous pouvez obtenir un délai d'attente ouvert, mais dans tous ces cas, la connexion au serveur n'a pas encore démarré. C'est le plus important pour moi. "ConnectionFailed" pour moi signifie simplement : je n'ai pas pu me connecter au serveur. Et il convient parfaitement à ces cas.

Si vous pensez toujours qu'un Faraday::OpenTimeoutError spécifique devrait exister, alors je suggérerais d'hériter de ConnectionFailed plutôt que de TimeoutError mais je suis d'accord que ce serait un peu déroutant et je ne sais pas comment cela aiderait dans la pratique.
Veuillez consulter mon précédent https://github.com/lostisland/faraday/issues/718#issuecomment -343957963 sur la façon dont cela pourrait réellement aider à gérer l'erreur.

Est-ce que ça fait du sens? J'aimerais trouver une solution qui convienne à tout le monde

J'accepte vos définitions plus précises du délai d'attente ouvert, mais j'arrive à une conclusion différente.

Vous considérez que le délai d'attente ouvert est considéré comme un échec de connexion, car le temps que vous êtes prêt à attendre pour cette connexion est considéré comme faisant partie de la connexion. "Impossible d'établir une connexion en 5s" a certainement du sens si vous l'expliquez comme ça, mais ce n'est pas ce que pensent beaucoup de gens.

Pour beaucoup, le délai d'attente ouvert signifie simplement que cela ne s'est pas encore produit . Cela en fait une déclaration moins définitive que la plupart des échecs de connexion, à savoir « Le serveur est en panne » ou « Ce DNS est une ordure ».

Je suppose que cela n'a pas beaucoup d'importance, car les échecs de connexion et les délais d'attente ouverts doivent tous deux être réessayés, ici, car un délai d'attente de lecture peut être considéré comme un motif de recul ?

Je suis d'accord, nous pouvons discuter autant que nous le voulons sur la lecture que l'on peut y appliquer, mais l'aspect pratique de mon propos est ce que vous avez également dit : si vous obtenez un délai d'attente ouvert, cela signifie que vous pouvez réessayer la demande, si vous obtenez a Read Timeout, cela signifie que vous devez être TRÈS prudent car votre demande a pu être traitée (entièrement ou partiellement). Par coïncidence, la signification pratique d'un délai d'attente ouvert correspond à celle d'une connexion échouée, par conséquent, je le ferais hériter de là.

Aujourd'hui, les gens détectent des exceptions ConnectionFailed et TimeoutError et la logique sous-jacente reflète très probablement ce que nous avons dit auparavant. Si nous introduisons la nouvelle exception en tant que sous-classe de ConnectionFailed il y a de fortes chances que la plupart des applications (sinon toutes) n'aient besoin d'aucune modification.

Je comprends (et suis d'accord) d'un point de vue sémantique, cependant, qu'un OpenTimeout n'est qu'un autre type de Timeout.

Mais bon, et si on l'appelait ConnectionTimedOut place ?

Il y aurait une certaine confusion autour du fait que open_timeout: X soit le nom de la propriété qui indique combien de temps attendre avant de lancer un ConnectionTimedOut .

Bon point 😞

Appelez ça ConnectionOpenTimeout ? Il indique clairement qu'il s'agit d'un problème de connexion et indique clairement qu'il s'agit d'un délai d'attente d'ouverture. Je pense que ce nom continue de comprendre le sens de "Impossible d'établir une connexion dans X secondes", même si certaines personnes peuvent encore se demander pourquoi le délai d'attente n'est pas un délai d'attente. ??

Ça m'a l'air bien !

@iMacTia hé, si vous pouviez me donner des indications par où commencer, je pourrais

Merci @philsturgeon , ce serait super ! Permettez-moi de récapituler les principaux points autour de cela :

  1. Toutes les modifications devront être effectuées par rapport à la branche v1.0 (car elles seront rétrocompatibles).
  2. Le comportement de gestion des délais d'attente est incohérent entre les adaptateurs, nous devons donc le standardiser.
  3. Le comportement convenu est le suivant :
  4. En cas de timeout OPEN, nous générerons une erreur ConnectionOpenTimeout qui héritera de ConnectionFailed .
  5. En cas de délai d'attente de READ, nous augmenterons un TimeoutError .

Ai-je manqué quelque chose?

Est-ce que ConnectionOpenTimeout sera ajouté aux exceptions Faraday::Request::Retry traitées par défaut ?

@mjhoy c'est un bon point mais pour le moment, le middleware Retry ne réessaye pas en cas de problème de connexion. Il ne relance la demande que si la connexion a réussi mais qu'il y a eu un délai d'attente. En fait, je ne suis pas sûr qu'il soit logique de réessayer une demande si le service que vous appelez n'est pas du tout accessible, vous préférerez peut-être récupérer l'exception et faire autre chose dans ce cas.

Cependant, le Faraday::Request::Retry est configurable donc rien ne vous empêche d'ajouter ConnectionOpenTimeout ou même ConnectionFailed à la liste des exceptions que vous voulez qu'il gère.

J'aimerais voir plus d'"opinions de la communauté" avant de les ajouter à la liste des exceptions par défaut

Nous rencontrons parfois des erreurs OpenTimeout avec un point de terminaison d'API qui doivent être réessayés ; il semble d'après votre logique ci - Errno::ETIMEDOUT, Timeout::Error, Error::TimeoutError , et Net::OpenTimeout est une sous-classe de Timeout::Error ; il n'était pas particulièrement clair que Faraday les traitait différemment. Alors peut-être que la documentation devrait être mise à jour? En tout cas, oui, nous avons configuré le middleware ; Je me demande juste si la valeur par défaut a du sens.

@mjhoy Vous avez raison de dire que Timeout::Error inclut également Net::OpenTimeout , donc avec l'implémentation actuelle, il semble que le délai d'attente d'ouverture devrait également être réessayé. De plus, Timeout::Error est récupéré et relancé par l'adaptateur dans des circonstances normales, de sorte que sa présence dans la liste des exceptions peut être inutile ou simplement pour plus de sécurité.

Une fois que nous aurons terminé la refactorisation des exceptions, le Net::OpenTimeout sera généré en tant que nouvelle exception et, comme je l'ai dit dans mon commentaire, changera le comportement par défaut actuel.

Je pense toujours que cela ne devrait pas faire partie des valeurs par défaut, mais c'est certainement quelque chose à considérer lors de l'exécution du travail.

Merci d'avoir soulevé ça

Hé, désolé, cela a traîné dans le carnet de commandes de mes équipes pendant un an et maintenant nos priorités ont beaucoup changé. Je ne travaillerai pas sur ce problème, mais bonne chance !

@iMacTia Est-ce que quelqu'un travaille sur ce changement ? Cela ne me dérange pas de reprendre cela pour la v2.0.

Salut @ragav0102 , merci pour le soutien !
Personne ne travaille encore là-dessus, car nous nous efforçons toujours de sortir la v1.0.

Nous apprécierions certainement votre aide, mais nous n'avons pas encore de plan pour la v2.0, donc je ne peux pas dire quand elle sera publiée, donc vos modifications devront peut-être attendre des mois avant de pouvoir être utilisées.

Si vous en avez besoin dans l'un de vos projets, ce n'est probablement pas faisable.
Si vous venez de réussir et que vous souhaitez contribuer, je vous suggère de choisir quelque chose de prévu pour la v1.0 car il sortira beaucoup plus tôt 😄

J'ai compris!

Cette page vous a été utile?
0 / 5 - 0 notes