Faraday: Unterscheiden Sie TimeoutErrors für Öffnungs- und Lese-Timeouts

Erstellt am 9. Aug. 2017  ·  32Kommentare  ·  Quelle: lostisland/faraday

In faraday/adapter/rack.rb wird TimeoutError sowohl für Open- als auch Read-Timeouts ausgelöst:

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

Laut https://stackoverflow.com/questions/10322283/what-is-timeout-and-open-timeout-in-faraday ist open_timeout für die TCP-Verbindung und timeout für das Lesen der Antwort.

Es wäre schön, separate Ausnahmetypen für diese Zeitüberschreitungen zu haben. Dann könnten wir entscheiden, ob die Anfrage wiederholt werden soll oder nicht. Ist es sinnvoll, etwas wie Faraday::Error::OpenTimeoutError und Faraday::Error::ResponseTimeoutError hinzuzufügen und diese hier zu verwenden?

feature help wanted

Hilfreichster Kommentar

Hallo @coberlin Ich glaube, das könnte eine nette Ergänzung sein, ich habe nur Angst vor der Abwärtskompatibilität.
Eine mögliche Lösung hierfür könnte jedoch darin bestehen, OpenTimeoutError und ResponseTimeoutError von TimeoutError zu erben, damit vorhandene rescue s weiterhin wie erwartet funktionieren.
Testen lohnt sich auf jeden Fall 😃

Alle 32 Kommentare

Hallo @coberlin Ich glaube, das könnte eine nette Ergänzung sein, ich habe nur Angst vor der Abwärtskompatibilität.
Eine mögliche Lösung hierfür könnte jedoch darin bestehen, OpenTimeoutError und ResponseTimeoutError von TimeoutError zu erben, damit vorhandene rescue s weiterhin wie erwartet funktionieren.
Testen lohnt sich auf jeden Fall 😃

Der rack_adapter ist möglicherweise der falsche Ort für diese Funktion. Ich denke, Rack-Anwendungen unterscheiden nicht unbedingt zwischen Zeitüberschreitungen beim Öffnen und Lesen. Vielleicht würde diese Funktion im HTTPClient-Adapter oder anderen Adaptern funktionieren? Von adapter/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 hat 3 Unterklassen ConnectTimeoutError, ReceiveTimeoutError, SendTimeoutError, siehe zB http://www.rubydoc.info/gems/httpclient/2.1.5.2/HTTPClient/TimeoutError

Faraday hat bereits Faraday::Error::ConnectionFailed. Ist das für ConnectTimeoutError angemessen? Faraday::Error::TimeoutError könnte in Faraday::Error::ReceiveTimeoutError und Faraday::Error::SendTimeoutError unterteilt werden.

Faraday hat bereits Faraday::Error::ConnectionFailed. Ist das für ConnectTimeoutError angemessen?

Das macht Sinn, wäre aber nicht abwärtskompatibel. Wir müssen bedenken, dass die Leute bereits Faraday::Error::TimeoutError in ihrer Anwendung fangen, also wird der Wechsel zu ConnectionFailed diese Fälle bremsen.
Stattdessen wollen wir 2 Unterklassen für Faraday::Error::TimeoutError deren Namen so allgemein wie möglich sein sollten:

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

Der nächste Schritt besteht darin, in jeden Adapter zu gehen und die Adapterausnahmen entsprechend zuzuordnen. ZB für den HTTPClient:

  • HTTPClient::ConnectTimeoutError ==> Faraday::Error::OpenTimeoutError
  • HTTPClient::ReceiveTimeoutError ==> Faraday::Error::ReadTimeoutError
  • HTTPClient::TimeoutError ==> Faraday::Error::TimeoutError (dies fängt auch SendTimeoutError ab, bei dem ich mir nicht sicher bin, ob es ein entsprechendes Mapping in Faraday oder eine bestimmte Einstellung gibt)

Schließlich sollten nach Möglichkeit Tests hinzugefügt werden :)

Hallo Leute.

Wir hatten diese Diskussion vor "weniger" Zeit (https://github.com/lostisland/faraday/pull/324).

Ich versuche es noch einmal (https://github.com/mistersourcerer/faraday/tree/718_mrsrcr_timeout-wrapping-2nd-chance), werde versuchen, eine neue PR zu öffnen, sobald ich Fortschritte habe.

Hallo @mistersourcerer , danke für den Anstupser, ich
Ich bin etwas verwirrt, als ich sehe, dass die PR geschlossen ist, aber die Änderung im Code weiß vielleicht, dass Sie Ihre Änderung am Ende irgendwie zusammengeführt haben 😄
Ihre Hilfe wäre in diesem Fall dankbar, da ich denke, dass Sie mit Timeout-Tests von Ihrer vorherigen Arbeit bereits vertraut sind (obwohl wir über 3 Jahre sprechen!).
Ich hoffe, meine Erklärung zu OpenTimeoutError und ReadTimeoutError ist klar, aber wenn das nicht der Fall ist, lassen Sie es mich bitte wissen.
Nimm dir Zeit und öffne einen Pull Request, wenn du fertig bist 👍

Hallo @iMacTia.

Wenn ich mich richtig erinnere, haben wir es damals nicht geschafft, die Situation zu lösen. Aber ich weiß nicht genau warum.
Das Hauptproblem bestand darin, einen Test zu schreiben, der bei allen Adaptern konsistent fehlschlug. Ich glaube also nicht, dass mein Code zu der Zeit zusammengeführt wurde.
Wie auch immer, ich habe einige Jahre später eine Idee dafür, haha, mal sehen, wie es läuft.

Im Moment schlagen die Tests für _EMSynchrony_ auf Travis fehl, aber nicht lokal. Versuche es herauszufinden. Ich denke sogar an eine "frühe" PR, damit wir das vielleicht besprechen können.

Und Ihre Erklärung ist kristallklar, scheint der perfekte Weg zu sein.

Danke für die tolle Arbeit, Mann.

Danke @mistersourcerer!

RE Ihre Änderungen: Ich bin mir nicht wirklich sicher, was passiert ist, aber ich sehe, dass @mislav Ihre Änderungen endlich hier zusammengeführt hat: https://github.com/lostisland/faraday/commit/f73d13ee09814fa68b37efa7bddafa47331948c2

Freut euch also, Errno::ETIMEDOUT ist auf den meisten (wenn nicht allen) Adaptern bereits unter Faraday::Error::TimeoutError verpackt 😄

Danke für die Arbeit an diesem @mistersourcerer!

Wenn ich mir Ihren Commit hier ansehe, frage ich mich, ob wir aus Gründen der Abwärtskompatibilität 2 neue Unterklassen brauchen: OpenConnectionError < ConnectionError für Net::HTTP und OpenTimeoutError < TimeoutError für HttpClient?

Bei diesem Thema scheint es einige Verwirrung zu geben.
Der Grund dafür ist, dass bei #438 die Entscheidung getroffen wurde, "open timeout"-Fehler als ConnectionFailed . Das ist wohl die beste Entscheidung, aber in Wirklichkeit hat sich jemand für diesen Weg entschieden.
Dies betrifft nun nicht nur den Rack-Adapter, sondern auch alle anderen Adapter, und ihr Verhalten ist wahrscheinlich nicht einmal konsistent.
Ich plane, sie mit v1.0 auf das gleiche Verhalten zu standardisieren, und ich werde dieses Problem als Referenz behalten.

Follow-up in meinem vorherigen Kommentar.

Im Grunde lösen wir derzeit einen Faraday::ConnectionFailed Fehler im Falle eines Open-Timeouts aus, während wir einen Faraday::TimeoutError für einen Read-Timeout auslösen. Obwohl sich verschiedene Adapter derzeit unterschiedlich verhalten, scheint dies das häufigste Verhalten zu sein.
Dies wurde vor ungefähr 3 Jahren beschlossen, aber hier diskutieren wir darüber, auch für den ersten Fall ein Faraday::TimeoutError zu haben (mit geeigneten Unterklassen, um zwischen Open und Close zu unterscheiden).

Einerseits verstehe ich, dass das näher an der Realität wäre, aber wenn ich das Thema aus der Sicht der Umsetzung analysiere, fällt es mir schwer, diese Änderung zu rechtfertigen.
Wenn ich einen Service anrufe und ein ConnectionFailed zurückbekomme, weiß ich, dass mein Anruf unmöglich bearbeitet worden sein kann. Ich habe den Server wahrscheinlich nicht erreicht oder konnte den Hostnamen nicht auflösen oder etwas anderes ist passiert.
Wenn ich TimeoutError zurückerhalte, wurde meine Anfrage möglicherweise verarbeitet oder teilweise verarbeitet, und ich habe möglicherweise die Antwort verpasst. Das ist ein ganz anderer Fall und erfordert eine nochmalige Überprüfung mit dem Server, den ich angerufen habe, was passiert ist.

Das Öffnen des Timeouts zu einer Unterkategorie von TimeoutError bedeutet, eine einfache Situation (Anfrage nicht verarbeitet) unter einer komplexeren Domäne zu nehmen, und erfordert sicherlich zusätzliche Prüfungen, um zu entscheiden, was zu tun ist: War es ein Open-Timeout oder ein Lesevorgang? Auszeit?

Wir müssen:

  1. Entscheiden Sie, wie Sie Zeitüberschreitungen für offene Öffnungen aufblasen
  2. Standardisieren Sie alle Adapter auf das gleiche Verhalten

@coberlin @erik-escobedo @mislav @mistersourcerer würde gerne deine Gedanken hören, nachdem du das oben Gesagte bedacht hast 😄

Für mich ist es sinnvoll, ConnectionFailed für offene Timeout-Fehler zu verwenden und würde das liefern, was ich mir erhofft hatte, indem ich die Open-Timeout-Fehler von den anderen Timeout-Fehlern unterscheide. Für die Adapterkonsistenz würde dies zum Beispiel bedeuten, dass Net::HTTP in Ordnung ist, aber HTTPClient würde sich ändern, wobei ConnectTimeoutErrors ConnectionFailed statt TimeoutError zugeordnet wird.

Das ist in Ordnung, sobald wir uns entschieden haben, werden wir alle Adapter auf das gleiche Verhalten standardisieren (in v1.0 offensichtlich, da dies abwärtskompatibel ist).

Einen Use-Case in den Ring werfen:

Bei der Arbeit haben wir unter einigen Open Timeouts gelitten, weil Nginx + Kubernetes nicht an hängende Pods weitergeleitet werden konnten (oder so). Wie auch immer, NetHTTP hat OpenTimeout- und ReadTimeout-Fehler ausgegeben, und das war wirklich praktisch für uns beim Debuggen, was was war.

Jetzt, wo wir auf Typhoeus umgestiegen sind, haben wir leider alle Timeouts zusammengepfercht, und es ist schwer zu sagen, ob unsere Arbeit an den nginx + kuber-Problemen verbessert wurde oder ob wir gerade erfolgreich mehr Anfragen an ein zunehmend kämpfendes richten System. Wie auch immer, die Anzahl der Timeouts ist ungefähr gleich, und ohne sie getrennt zu bekommen, stecken wir irgendwie im Raten fest.

Ich denke nicht, dass es ausreicht, nur Faraday::OpenTimeoutError hinzuzufügen, wir sollten Faraday::OpenTimeoutError und Faraday::ReadTimeoutError sich von Faraday::TimeoutError IMO erweitern.

@philsturgeon und was ist mit der anderen vorgeschlagenen Lösung, würde das auch helfen?

Zeitüberschreitung beim Öffnen -> Faraday::ConnectionFailed
Zeitüberschreitung beim Lesen -> Faraday::TimeoutError

Das sollte das Verhalten auf allen Adaptern sein, aber leider verhalten sich einige nicht wie erwartet (zB Typhoeus)

Ich habe das Gefühl, dass das verschiedene Dinge sind.

ConnectionFailed scheint wie "Ich habe keine Ahnung, wie ich mit diesem Server kommunizieren soll", wie eine ungültige DNS/IP usw.

OpenTimeout ist "Ich weiß, wo dieser Server ist, ich warte nur darauf, dass er etwas tut"

OpenTimeout ist "Ich weiß, wo dieser Server ist, ich warte nur darauf, dass er etwas tut"

Dem stimme ich nicht zu, ich würde eher sagen:

Open Timeout: Ich versuche, den Server zu kontaktieren, kann ihn aber nicht erreichen (Hinweis: Verbindung noch nicht hergestellt oder "geöffnet").
Read Timeout: Ich habe eine Verbindung mit dem Server hergestellt, warte aber darauf, dass er etwas tut (die Ausgabe liest).

Ein fehlerhafter Firewall/Proxy/Load_Balancer sind nur einfache Beispiele dafür, wie Sie einen Open-Timeout bekommen können, aber in all diesen Fällen hat die Verbindung zum Server noch nicht begonnen. Das ist für mich das Wichtigste. "ConnectionFailed" bedeutet für mich einfach: Ich konnte keine Verbindung zum Server herstellen. Und es passt perfekt zu diesen Fällen.

Wenn Sie immer noch der Meinung sind, dass ein bestimmter Faraday::OpenTimeoutError vorhanden sein sollte, würde ich vorschlagen, von ConnectionFailed anstatt von TimeoutError zu erben, aber ich stimme zu, dass das etwas verwirrend wäre und nicht sicher ist, wie es würde in der Praxis helfen.
Bitte sehen Sie in meinem vorherigen https://github.com/lostisland/faraday/issues/718#issuecomment -343957963 nach, wie dies tatsächlich helfen könnte, den Fehler zu beheben.

Macht das Sinn? Ich möchte eine Lösung finden, die für alle passt

Ich akzeptiere Ihre genaueren Definitionen für Open Timeout, komme aber zu einem anderen Schluss.

Sie betrachten die Zeitüberschreitung beim Öffnen als Verbindungsfehler, da die Zeit, die Sie bereit sind, auf diese Verbindung zu warten, als Teil der Verbindung betrachtet wird. "Keine Verbindung in 5s fehlgeschlagen" macht sicherlich Sinn, wenn man es so erklärt, aber so denken viele Leute nicht.

Für viele bedeutet Open Timeout nur, dass es noch nicht passiert ist . Das macht es weniger zu einer definitiven Aussage als die meisten Verbindungsausfälle, die "Der Server ist ausgefallen" oder "Dieser DNS ist Müll" lautet.

Ich nehme an, es spielt keine große Rolle, da Verbindungsfehler und Zeitüberschreitungen beim Öffnen erneut versucht werden sollten.

Ich stimme zu, wir können so viel streiten, wie wir wollen, über die Lektüre, die man darauf anwenden kann, aber die Praktikabilität meines Standpunkts ist auch das, was Sie gesagt haben: Wenn Sie ein Open Timeout erhalten, bedeutet dies, dass Sie die Anfrage erneut versuchen können, wenn Sie es bekommen a Read Timeout bedeutet, dass Sie SEHR vorsichtig sein müssen, da Ihre Anfrage möglicherweise (ganz oder teilweise) bearbeitet wurde. Zufälligerweise stimmt die praktische Bedeutung eines offenen Timeouts mit der einer fehlgeschlagenen Verbindung überein, daher würde ich es von dort erben lassen.

Heutzutage fangen die Leute ConnectionFailed und TimeoutError Ausnahmen ein und die Logik dahinter spiegelt sehr wahrscheinlich das wieder, was wir vorher gesagt haben. Wenn wir die neue Ausnahme als Unterklasse von ConnectionFailed einführen, dann sind die Chancen hoch, dass die meisten (wenn nicht alle) Anwendungen keine Änderungen benötigen.

Ich verstehe (und stimme) aus semantischer Sicht jedoch zu, dass ein OpenTimeout nur eine andere Art von Timeout ist.

Aber hey, was ist, wenn wir es stattdessen ConnectionTimedOut ?

Es würde einige Verwirrung geben, wenn open_timeout: X der Name der Eigenschaft ist, die angibt, wie lange gewartet werden soll, bis ein ConnectionTimedOut geworfen wird.

Guter Punkt 😞

Nennen Sie es ConnectionOpenTimeout ? Es macht deutlich, dass es sich um ein Verbindungsproblem handelt, und macht deutlich, dass es sich um eine Zeitüberschreitung beim Öffnen handelt. Ich denke, dieser Name steht im Einklang mit der Bedeutung von "Verbindung konnte nicht in X Sekunden hergestellt werden", auch wenn sich manche Leute immer noch fragen, warum Timeout kein Timeout ist. 😅

Klingt gut für mich 👍!

@iMacTia hey, wenn du mir ein paar könntest , wo ich anfangen soll, könnte ich das

Danke @philsturgeon , das wäre toll! Lassen Sie mich die wichtigsten Punkte dazu zusammenfassen:

  1. Alle Änderungen müssen gegen den Zweig v1.0 durchgeführt werden (da sie nicht abwärtskompatibel sind).
  2. Das Verhalten der Zeitüberschreitungsverwaltung ist bei allen Adaptern inkonsistent, daher müssen wir es standardisieren.
  3. Das vereinbarte Verhalten ist folgendes:
  4. Im Falle eines OPEN-Timeouts geben wir einen ConnectionOpenTimeout Fehler aus, der von ConnectionFailed erbt.
  5. Im Falle eines READ-Timeouts geben wir TimeoutError .

Habe ich etwas vergessen?

Wird ConnectionOpenTimeout zu den von Faraday::Request::Retry behandelten Ausnahmen hinzugefügt?

@mjhoy das ist ein guter Punkt, aber im Moment versucht die Retry-Middleware bei Verbindungsproblemen nicht erneut. Es wiederholt die Anforderung nur, wenn die Verbindung erfolgreich war, aber eine Zeitüberschreitung aufgetreten ist. Tatsächlich bin ich mir nicht sicher, ob es sinnvoll ist, eine Anfrage zu wiederholen, wenn der von Ihnen aufgerufene Dienst überhaupt nicht erreichbar ist. Möglicherweise ziehen Sie es vor, die Ausnahme zurückzuholen und in diesem Fall etwas anderes zu tun.

Allerdings ist Faraday::Request::Retry konfigurierbar, sodass Sie nichts davon abhalten können, ConnectionOpenTimeout oder sogar ConnectionFailed zur Liste der Ausnahmen hinzuzufügen, die behandelt werden sollen.

Ich würde gerne mehr "Community-Meinungen" sehen, bevor ich diese zur Liste der Standardausnahmen hinzufüge

Gelegentlich treten OpenTimeout Fehler mit einem API-Endpunkt auf, die erneut versucht werden müssen. Aus Ihrer obigen Logik scheint es, dass offene Timeouts sicher wiederholt werden können (sicherer als ein Read-Timeout). Wir waren auch davon ausgegangen, dass mit der Retry-Middleware sowohl das Öffnen als auch das Lesen von Timeouts wiederholt werden würde; in der Dokumentation steht: "Standardmäßig versucht es 2 Mal und behandelt nur Timeout-Ausnahmen." Die standardmäßig behandelten Ausnahmen sind Errno::ETIMEDOUT, Timeout::Error, Error::TimeoutError und Net::OpenTimeout ist eine Unterklasse von Timeout::Error ; Es war nicht besonders klar, dass Faraday sie anders behandelte. Vielleicht sollte die Dokumentation also aktualisiert werden? Auf jeden Fall haben wir die Middleware konfiguriert; Ich frage mich nur, ob die Standardeinstellung Sinn macht.

@mjhoy Sie haben Recht, Timeout::Error auch Net::OpenTimeout einschließt. Darüber hinaus wird Timeout::Error vom Adapter unter normalen Umständen gerettet und erneut ausgelöst, sodass seine Anwesenheit in der Ausnahmeliste möglicherweise unnötig oder nur aus Sicherheitsgründen erforderlich ist.

Sobald wir mit dem Refactoring der Ausnahmen fertig sind, wird die Net::OpenTimeout als neue Ausnahme ausgelöst und, wie ich in meinem Kommentar sagte, das aktuelle Standardverhalten ändern.

Ich glaube immer noch, dass dies nicht Teil der Standardeinstellungen sein sollte, aber es ist definitiv etwas, das man bei der Arbeit berücksichtigen sollte.

Danke, dass du das großgezogen hast 😄

Hey, es tut mir leid, dass das ein Jahr lang in meinem Team-Backlog schmachtete und jetzt haben sich unsere Prioritäten stark verändert. Ich werde dieses Thema nicht bearbeiten, aber viel Glück!

@iMacTia Arbeitet jemand an dieser Änderung? Ich habe nichts dagegen, dies für v2.0 zu übernehmen.

Hallo @ragav0102 , danke für die Unterstützung!
Daran arbeitet noch niemand, da wir immer noch darauf drängen, v1.0 aus der Tür zu bekommen.

Wir würden uns auf jeden Fall über Hilfe freuen, aber wir haben noch keinen Plan für v2.0, daher kann ich nicht sagen, wann es veröffentlicht wird, daher müssen Ihre Änderungen möglicherweise Monate warten, bevor sie verwendet werden können.

Wenn Sie dies in einem Ihrer Projekte benötigen, ist das wahrscheinlich nicht machbar.
Wenn Sie gerade bestanden haben und einen Beitrag leisten möchten, empfehle ich Ihnen, etwas zu wählen, das für v1.0 geplant ist, da es viel früher veröffentlicht wird 😄

Ich habs!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen