Requests: Gesamtzeitüberschreitung

Erstellt am 16. Apr. 2016  ·  38Kommentare  ·  Quelle: psf/requests

Wir machen bereits großen Gebrauch vom Timeout-Parameter, der die Einstellung von TCP-Transaktions-Timeouts ermöglicht. Das ist sehr hilfreich! Wir müssen jedoch auch ein allgemeines Timeout über die Verbindung unterstützen. Beim Lesen der Dokumentation zu Zeitüberschreitungen sehe ich, dass dies derzeit nicht unterstützt wird, und beim Durchsuchen der Probleme zumindest ein bisschen früher habe ich keine weitere Anfrage für diese Funktion gesehen - entschuldigen Sie mich, wenn es eine gibt.

Mir ist klar, dass wir Timer in unserer Bibliothek einstellen können, um dies zu erreichen, aber ich mache mir Sorgen über den zusätzlichen Overhead (einen pro Thread, und wir können viele haben) sowie über etwaige nachteilige Auswirkungen auf das Verbindungspooling, wenn wir am Ende a abbrechen müssen Anfrage. Gibt es eine gute Möglichkeit, eine Anfrage überhaupt abzubrechen? In den Unterlagen habe ich nichts offensichtliches gesehen.

Also: Langfristig wäre es großartig, wenn wir der Anforderungsbibliothek ein allgemeines Timeout hinzufügen könnten. Gibt es kurzfristig einen empfohlenen Weg, dies auf meiner Seite zu implementieren?

Propose Close

Hilfreichster Kommentar

@jribbens Damit gibt es ein paar Probleme.

Teil 1 ist, dass die Komplexität eines solchen Patches sehr hoch ist. Damit es sich korrekt verhält, müssen Sie die Timeouts auf Socket-Ebene wiederholt ändern. Das bedeutet, dass der Patch durchgehend durch httplib weitergegeben werden muss, was wir bereits mehr gepatcht haben, als uns lieb ist. Im Wesentlichen müssten wir in httplib hineingreifen und etwa 50 % seiner komplexeren Methoden neu implementieren, um diese funktionale Änderung zu erreichen.

Teil 2 ist, dass die Wartung eines solchen Patches relativ aufwändig ist. Wir müssten wahrscheinlich damit beginnen, einen parallelen Fork von httplib (genauer gesagt http.client zu diesem Zeitpunkt) zu warten, um dies erfolgreich zu tun. Alternativ müssten wir die Wartungslast eines anderen HTTP-Stacks übernehmen, der für diese Art von Änderung besser geeignet ist. Dieser Teil wird, wie ich vermute, häufig von denen übersehen, die ein solches Feature haben möchten: Die Kosten für die Implementierung sind hoch, aber das ist _nichts_ im Vergleich zu den laufenden Wartungskosten für die Unterstützung eines solchen Features auf allen Plattformen.

Teil 3 ist, dass der Vorteil eines solchen Patches unklar ist. Ich habe die Erfahrung gemacht, dass die meisten Leute, die einen totalen Timeout-Patch wollen, nicht ganz klar darüber nachdenken, was sie wollen. In den meisten Fällen haben Gesamt-Timeout-Parameter den Effekt, dass einwandfreie Anfragen ohne Grund beendet werden.

Angenommen, Sie haben einen Code entworfen, der Dateien herunterlädt, und Sie möchten Blockaden behandeln. Während es zunächst verlockend ist, eine pauschale Gesamtzeitüberschreitung festlegen zu wollen („keine Anfrage darf länger als 30 Sekunden dauern!“), verfehlt eine solche Zeitüberschreitung den Sinn. Wenn sich beispielsweise eine Datei von 30 MB auf 30 GB Größe ändert, kann eine solche Datei in diesem Zeitintervall _niemals_ heruntergeladen werden, obwohl der Download völlig fehlerfrei sein kann.

Anders gesagt, totale Timeouts sind ein attraktives Ärgernis: Sie scheinen ein Problem zu lösen, aber sie tun es nicht effektiv. Ein sinnvollerer Ansatz besteht meiner Meinung nach darin, das Timeout pro Socket-Aktion in Kombination mit stream=True und iter_content zu nutzen und sich Zeitüberschreitungen für Datenblöcke zuzuweisen. So wie iter_content funktioniert, wird der Kontrollfluss in einem einigermaßen regelmäßigen Intervall an Ihren Code zurückgegeben. Das bedeutet, dass Sie selbst Timeouts auf Socket-Ebene (z. B. 5 s) und dann iter_content über relativ kleine Blöcke (z. B. 1 KB Daten) festlegen können und sich relativ sicher sein können, dass es keinen Denial-of-Service gibt, es sei denn, Sie werden aktiv angegriffen ist hier möglich. Wenn Sie sich wirklich Sorgen über Denial-of-Service machen, setzen Sie Ihr Socket-Level-Timeout viel niedriger und Ihre Chunk-Größe kleiner (0,5 s und 512 Bytes), um sicherzustellen, dass Sie regelmäßig den Kontrollfluss an sich zurückgeben.

Das Ergebnis all dessen ist, dass ich glaube, dass totale Timeouts ein Fehler in einer Bibliothek wie dieser sind. Die beste Art von Zeitüberschreitung ist eine, die darauf abgestimmt ist, großen Antworten genug Zeit zu geben, um sie in Ruhe herunterzuladen, und eine solche Zeitüberschreitung wird am besten durch Zeitüberschreitungen auf Socket-Ebene und iter_content bedient.

Alle 38 Kommentare

Hallo @emgerner-msft,

Als Referenz sind im Folgenden alle Variationen zu diesem Thema aufgeführt, wenn nicht genau diese Funktionsanforderung:

Wir haben dies auch auf https://github.com/sigmavirus24/requests-toolbelt/issues/51 besprochen

Sie werden feststellen, dass der letzte Link dieses Paket behandelt, das dies für Sie erledigen sollte, ohne es zu Anfragen hinzuzufügen. Die Realität ist, dass dafür keine Anfragen erforderlich sind, wenn ein anderes Paket dies bereits sehr gut macht.

Das Paket, auf das Sie verweisen, tut dies, indem es einen separaten Prozess forkt, um die Webanforderung auszuführen. Das ist ein sehr schwerer Weg, um das einfache Ziel einer Zeitüberschreitung zu erreichen, und meiner Ansicht nach ist es in keiner Weise ein Ersatz für Anfragen selbst, die eine native Zeitüberschreitungsfunktion haben.

@jribbens Wenn Sie einen Weg finden könnten, der weder Threads noch Prozesse verwendet, wäre das erstaunlich. Bis dahin, wenn Sie eine Wanduhr-Auszeit wünschen, ist dieses Paket die beste Wahl, da dies im Moment der zuverlässigste Weg ist, dies zu erreichen.

Ich glaube nicht, dass @jribbens keine Threads oder Prozesse sagt. Nur dass ein Prozess _pro_ Webanfrage übertrieben ist. Viele Sprachen haben die Möglichkeit, dass mehrere Timer einen einzelnen zusätzlichen Thread oder Prozess gemeinsam nutzen. Ich weiß nur nicht, wie man das am besten in Python macht.

Es scheint, als hätte #1928 die meisten Diskussionen über Alternativen, aber die meisten kommen mit vielen Vorbehalten (dies funktioniert nicht für Ihren Anwendungsfall usw.). Ich habe kein Problem damit, benutzerdefinierten Code in meiner Bibliothek zu haben und meine eigene benutzerdefinierte Lösung zu schreiben, wenn dies wirklich nicht in Anfragen gehört, aber ich denke, ich brauche ein wenig mehr Informationen darüber, wie das aussehen würde. Der einzige Grund, warum wir Anfragen verwenden, besteht darin, von der Low-Level-TCP-Verbindungspooling-Logik wegzukommen, aber es scheint, als würde ich diesen Thread lesen, dass ich diese Logik kennen muss, um diesen benutzerdefinierten Code zu schreiben, und damit habe ich einige Probleme .

@emgerner-msft ist richtig. Ich bin etwas verwirrt über den Kommentar von @ sigmavirus24 , eine "Gesamtzeitüberschreitung" ohne Verwendung von Threads oder Prozessen zu haben, scheint ziemlich banal und überhaupt nicht "erstaunlich". Berechnen Sie einfach die Frist zu Beginn des gesamten Prozesses (z. B. deadline = time.time() + total_timeout ) und setzen Sie dann bei jeder einzelnen Operation das Timeout auf deadline - time.time() .

ein "totales Timeout" zu haben, ohne Threads oder Prozesse zu verwenden, scheint ziemlich langweilig und überhaupt nicht "erstaunlich".

Und Ihre Lösung ist ziemlich primitiv. Der Grund, warum _die meisten_ Leute ein totales Timeout (oder Wanduhr-Timeout) wollen, besteht darin, zu verhindern, dass ein Read "hängt", mit anderen Worten in einem Fall wie dem folgenden:

r = requests.get(url, stream=True)
for chunk in r.iter_content(chunksize):
    process_data(chunk)

Wo jeder Lesevorgang in der Mitte von iter_content lange dauert, aber weniger als das Lese-Timeout ist (ich gehe davon aus, dass wir das beim Streamen anwenden, aber es kann immer noch der Fall sein, dass wir das nicht tun), das sie angegeben haben . Sicherlich scheint es so, als ob dies einfach von Ihrer Lösung @jribbens gehandhabt werden sollte, bis Sie sich daran erinnern, wie Uhren driften und die Sommerzeit funktioniert und dass time.time() erbärmlich unzureichend ist.

Schließlich ist es wichtig zu bedenken, dass die API von Requests eingefroren ist. Es gibt keine gute oder konsistente API zum Angeben eines Gesamtzeitlimits. Und wenn wir eine Zeitüberschreitung wie von Ihnen vorgeschlagen implementieren, hätten wir unzählige Fehler, bei denen sie eine einminütige Gesamtzeitüberschreitung angegeben haben, aber es dauerte länger, weil wir bei der letzten Überprüfung weniger als eine Minute lagen, aber ihre konfigurierte Lesezeitüberschreitung lang genug war, dass ihre Zeitüberschreitung Fehler wurde etwa anderthalb Minuten ausgelöst. Das ist ein _sehr_ rauer Wand-Timeout, der für Leute, die danach suchen, etwas besser wäre, aber nicht anders als die Person, die dies selbst implementiert.

Entschuldigung, falls ich unklar war @sigmavirus24 , Sie scheinen meine prinzipielle Pseudocode-Darstellung kritisiert zu haben, als ob Sie dachten, es wäre ein wörtlicher Patch. Ich sollte jedoch darauf hinweisen, dass time.time() nicht so funktioniert, wie Sie anscheinend denken - die Sommerzeit ist nicht relevant, und die Uhr ist auch nicht in den Zeitskalen verzerrt, über die wir hier sprechen. Außerdem haben Sie den Vorschlag falsch verstanden, wenn Sie glauben, dass der von Ihnen beschriebene Fehler auftreten würde. Schließlich bin ich mir nicht sicher, was Sie damit meinen, dass die Requests-API "eingefroren" ist, da die API erst in Version 2.9.0 geändert wurde, so klar, was auch immer Sie meinen, es ist nicht das, was ich normalerweise unter dem Wort verstehen würde.

Nur um meine Diskussion zu trennen: Ich behaupte eigentlich nicht, dass dies einfach ist. Wenn es ganz einfach wäre, würde ich es einfach schreiben und aufhören, dich zu nerven. :)

Meine Probleme sind:
1) Alles in den Threads, die Sie aufgelistet haben, waren Affenpatches. Das ist in Ordnung, aber ich verwende dies in einer Bibliothek in Produktionsqualität und kann den Vorbehalt nicht ertragen, dass interne Änderungen alles kaputt machen.
2) Der Timeout-Decorator in dem von Ihnen angegebenen Link ist großartig, aber mir ist nicht klar, wie sich das auf die Verbindung auswirkt. Selbst wenn wir akzeptieren, dass Timeouts nur mit einer Reihe von Threads möglich sind, wie erzwingt diese Bibliothek, dass der Socket heruntergefahren wird, die Verbindung getrennt wird usw. Wir stellen viele Verbindungen her, und dies scheint möglicherweise recht zu sein Leck anfällig. Bei Anfragen gibt es keine 'Abbruch'-Methode, die ich finden kann (korrigieren Sie mich, wenn ich falsch liege). Wie läuft also das Herunterfahren der Verbindung ab?

Alles, was ich suche, ist eine klare „gesegnete“ Version, wie ich dieses Problem selbst lösen kann, oder, wenn es keine perfekte Lösung gibt, ein paar Lösungen mit den besprochenen Vorbehalten. Ist das sinnvoll?

@emgerner-msft Angenommen, Sie verwenden CPython, wird die Verbindung beendet, wenn die Anfrage nicht mehr fortgesetzt wird. An diesem Punkt gehen alle Verweise auf die zugrunde liegende Verbindung verloren und der Socket wird geschlossen und entsorgt.

@Lukasa Okay, danke! Wie stellt die Bibliothek fest, dass die Bestellung nicht mehr fortgesetzt wird? Wenn ich zum Beispiel die Timeout-Decorator-Route verwende und mitten im Download abbreche, wann würde der Download tatsächlich aufhören? Muss ich bei den Streaming-Optionen etwas Besonderes tun?

Wenn Sie den Timeout-Decorator verwenden, wird der Download beendet, wenn das Timeout ausgelöst wird. Dies liegt daran, dass Signale Systemaufrufe unterbrechen, was bedeutet, dass keine weiteren Aufrufe in den Socket erfolgen. Sobald die Anfrage nicht mehr im Gültigkeitsbereich ist (z. B. der Stack außerhalb Ihrer requests.* -Funktion abgewickelt wurde), ist das drin: CPython bereinigt das Verbindungsobjekt und bricht die Verbindung ab. Dort sind keine speziellen Streaming-Optionen erforderlich.

Perfekt. Ich bin gut, den Thread dann zu schließen, es sei denn, andere haben mehr zu sagen.

Tut mir leid, noch eine Sorge. Ich habe mir den Timeout-Decorator-Code genauer angesehen, da Sie sagten, dass er Signale verwendet, was relevant ist, im Gegensatz zu etwas wie Python-Timern (vermutlich). Es sieht so aus, als würde es Signal mit SIGALRM aufrufen , das in Python Signal dokumentiert ist, um unter Windows nicht zu funktionieren. Ich brauche dies, um sowohl in Unix- als auch in Windows-Umgebungen sowie in Python 2.7 und 3.3+ zu funktionieren (ähnlich wie Anfragen selbst). Ich werde noch ein bisschen herumstöbern und sehen, ob das angesichts dessen tatsächlich funktioniert.

@emgerner-msft Das ist frustrierend. =(

@Lukasa Yup , habe das Snippet für die grundlegende Verwendung ausprobiert und es funktioniert nicht unter Windows. Ich habe noch mehr Code/Beispiele gelesen und herumgespielt, und es sieht so aus, als ob das Paket funktionieren könnte, wenn wir keine Signale verwenden, aber alles muss auswählbar sein, was bei meiner Anwendung nicht der Fall ist. Soweit ich das beurteilen kann, wird Timeout Decorator mein Problem nicht lösen. Irgendwelche anderen Ideen?

@emgerner-msft Sind Sie sicher, dass keines der Windows-spezifischen Signale geeignet ist?

@Lukasa Um ehrlich zu sein, ich weiß es einfach nicht. Ich habe zuvor keine Signale verwendet, und ähnlich wie ich es nicht bemerkt habe, bis Sie mir sagten, dass sie die Anfrage unterbrechen würden, bin ich mir nicht sicher, was angemessen ist. Ich versuche auch nicht, dies nur unter Windows zum Laufen zu bringen. Ich brauche volle plattformübergreifende Unterstützung (Windows und Unix) und sowohl Python 2- als auch Python 3-Unterstützung. So viele Signale sehen plattformspezifisch aus, es wirft mich um. Timer war eine der Lösungen, die ich mir angesehen habe, die weniger niedrig aussah und sich daher um meine Einschränkungen kümmern könnte, aber ich bin mir nicht sicher, wie ich die Verbindung schließen könnte. Ich kann mehr lesen, aber deshalb hatte ich gehofft, zusätzliche Anleitung von euch zu bekommen. :)

Das ist also ein wirklich schwieriger Ort.

Die Realität ist, dass es mehr oder weniger keine plattformübergreifende Möglichkeit gibt, einen Thread zu beenden, außer ihn zu unterbrechen, was im Grunde genommen das ist, was ein Signal ist. Das bedeutet meiner Meinung nach, dass Signale der einzige Weg sind, den Sie wirklich haben, um dies plattformübergreifend zum Laufen zu bringen. Ich neige dazu, zu versuchen, einen Windowsy-Pythony-Experten anzupingen: @brettcannon , hast du hier einen guten Vorschlag?

Gibt es aus Interesse einen Grund, "total timeout" in Requests nicht zu implementieren, außer dass das Implementieren und Testen Arbeit erfordert? Ich meine, wenn ein Patch zur Implementierung heute auf magische Weise erscheinen würde, würde er theoretisch abgelehnt oder akzeptiert werden? Ich schätze und stimme dem Standpunkt "unnötige Komplexität beseitigen" zu, aber "Sie können dies tun, indem Sie einen separaten Prozess forken" macht diese Funktion meiner Meinung nach nicht unnötig.

@jribbens Damit gibt es ein paar Probleme.

Teil 1 ist, dass die Komplexität eines solchen Patches sehr hoch ist. Damit es sich korrekt verhält, müssen Sie die Timeouts auf Socket-Ebene wiederholt ändern. Das bedeutet, dass der Patch durchgehend durch httplib weitergegeben werden muss, was wir bereits mehr gepatcht haben, als uns lieb ist. Im Wesentlichen müssten wir in httplib hineingreifen und etwa 50 % seiner komplexeren Methoden neu implementieren, um diese funktionale Änderung zu erreichen.

Teil 2 ist, dass die Wartung eines solchen Patches relativ aufwändig ist. Wir müssten wahrscheinlich damit beginnen, einen parallelen Fork von httplib (genauer gesagt http.client zu diesem Zeitpunkt) zu warten, um dies erfolgreich zu tun. Alternativ müssten wir die Wartungslast eines anderen HTTP-Stacks übernehmen, der für diese Art von Änderung besser geeignet ist. Dieser Teil wird, wie ich vermute, häufig von denen übersehen, die ein solches Feature haben möchten: Die Kosten für die Implementierung sind hoch, aber das ist _nichts_ im Vergleich zu den laufenden Wartungskosten für die Unterstützung eines solchen Features auf allen Plattformen.

Teil 3 ist, dass der Vorteil eines solchen Patches unklar ist. Ich habe die Erfahrung gemacht, dass die meisten Leute, die einen totalen Timeout-Patch wollen, nicht ganz klar darüber nachdenken, was sie wollen. In den meisten Fällen haben Gesamt-Timeout-Parameter den Effekt, dass einwandfreie Anfragen ohne Grund beendet werden.

Angenommen, Sie haben einen Code entworfen, der Dateien herunterlädt, und Sie möchten Blockaden behandeln. Während es zunächst verlockend ist, eine pauschale Gesamtzeitüberschreitung festlegen zu wollen („keine Anfrage darf länger als 30 Sekunden dauern!“), verfehlt eine solche Zeitüberschreitung den Sinn. Wenn sich beispielsweise eine Datei von 30 MB auf 30 GB Größe ändert, kann eine solche Datei in diesem Zeitintervall _niemals_ heruntergeladen werden, obwohl der Download völlig fehlerfrei sein kann.

Anders gesagt, totale Timeouts sind ein attraktives Ärgernis: Sie scheinen ein Problem zu lösen, aber sie tun es nicht effektiv. Ein sinnvollerer Ansatz besteht meiner Meinung nach darin, das Timeout pro Socket-Aktion in Kombination mit stream=True und iter_content zu nutzen und sich Zeitüberschreitungen für Datenblöcke zuzuweisen. So wie iter_content funktioniert, wird der Kontrollfluss in einem einigermaßen regelmäßigen Intervall an Ihren Code zurückgegeben. Das bedeutet, dass Sie selbst Timeouts auf Socket-Ebene (z. B. 5 s) und dann iter_content über relativ kleine Blöcke (z. B. 1 KB Daten) festlegen können und sich relativ sicher sein können, dass es keinen Denial-of-Service gibt, es sei denn, Sie werden aktiv angegriffen ist hier möglich. Wenn Sie sich wirklich Sorgen über Denial-of-Service machen, setzen Sie Ihr Socket-Level-Timeout viel niedriger und Ihre Chunk-Größe kleiner (0,5 s und 512 Bytes), um sicherzustellen, dass Sie regelmäßig den Kontrollfluss an sich zurückgeben.

Das Ergebnis all dessen ist, dass ich glaube, dass totale Timeouts ein Fehler in einer Bibliothek wie dieser sind. Die beste Art von Zeitüberschreitung ist eine, die darauf abgestimmt ist, großen Antworten genug Zeit zu geben, um sie in Ruhe herunterzuladen, und eine solche Zeitüberschreitung wird am besten durch Zeitüberschreitungen auf Socket-Ebene und iter_content bedient.

Vielleicht hat @zooba eine Idee, da er eigentlich weiß, wie Windows funktioniert. :)

(Unabhängig davon ist es eine meiner Lieblingsbeschäftigungen, eine Daisy-Chain von Experten in einem GitHub-Problem einzurichten.)

Haha, @zooba und @brettcannon kenne ich schon. Ich kann mit ihnen hier oder intern diskutieren, wie eine Lösung dafür wahrscheinlich auch ihnen helfen würde.

@emgerner-msft Ich dachte schon, wollte aber nicht annehmen: MSFT ist eine große Organisation!

@Lukasa Ich lese gerade die Textwand durch, die Sie gerade oben geschrieben haben - interessant! Was ist bei der Diskussion von stream=True und iter_content zum zeitlichen Herunterladen von Dateien die äquivalente Methode zum Umgang mit größeren Uploads?

_PS_: Der obige Absatz, der mit "Andere Weise setzen, ..." beginnt, ist die Art von Anleitung, nach der ich in den Dokumenten gesucht habe. Angesichts der Anzahl der Anfragen, die Sie für die maximale Zeitüberschreitung erhalten (und Ihrer triftigen Gründe, dies nicht zu tun), ist es vielleicht das Beste, einige dieser Informationen in die Zeitüberschreitungsdokumente aufzunehmen ?

lol @lukasa Ich nehme Ihren Standpunkt zur Wartung an, der mir bereits in den Sinn kam, aber bei "Feature vs Misfeature" bin ich Ihnen leider völlig entgegengesetzt. Ich denke, jeder, der _keinen_ Timeout will, denkt nicht klar darüber nach, was er will, und ich habe Schwierigkeiten, mir eine Situation vorzustellen, in der das, was Sie als Fehler beschreiben, „30 MB Download ändert sich in 30 GB und schlägt daher fehl“, nicht so ist in der Tat ein nützliches Feature!

Sie können, wie Sie sagen, etwas Ähnliches tun (aber ich vermute, ohne die meisten Vorteile einer vollständigen Auszeit), indem Sie stream=True verwenden, aber ich dachte, der Sinn von Anfragen war, dass es Dinge für Sie erledigt ...

Ich dachte, der Sinn von Anfragen war, dass es die Dinge für Sie erledigt

Es behandelt HTTP für Sie. Die Tatsachen, dass wir Verbindungs- und Lesezeitüberschreitungen bereits handhaben und dass wir einige Ausnahmen von unserem mehrjährigen Funktionsstopp hatten, sind nebensächlich für die Diskussion über Nützlichkeit, Erwünschtheit, Konsistenz (über mehrere Plattformen hinweg) und Wartbarkeit. Wir freuen uns über Ihr Feedback und Ihre Meinung. Wenn Sie neue Informationen präsentieren möchten, würden wir uns darüber freuen.

Anhand der Anzahl der abgelehnten Feature-Requests in diesem Projekt und der Tatsache, dass es ein separates Projekt gibt, das allgemeine Nutzungsmuster für Benutzer implementiert (das Requests Toolbelt), kann es auch sagen, dass Requests nicht alles erledigen. Wenn ein totales Timeout irgendwo hingehört, wäre es da, aber auch hier müsste es unter Windows, BSD, Linux und OSX mit ausgezeichneter Testabdeckung funktionieren und ohne dass es ein Albtraum wäre, es zu warten.

Was ist bei der Diskussion von stream=True und iter_content zum zeitlichen Herunterladen von Dateien die äquivalente Methode zum Umgang mit größeren Uploads?

Definieren Sie einen Generator für Ihren Upload und übergeben Sie diesen an data . Oder, wenn Chunked Encoding kein Gewinn für Sie ist, definieren Sie ein dateiähnliches Objekt mit einer magischen read Methode und übergeben Sie _that_ an data .

Lassen Sie mich etwas näher darauf eingehen. Wenn Sie einen Generator an data übergeben, werden Anforderungen darüber iteriert und die einzelnen Blöcke der Reihe nach gesendet. Das bedeutet, dass wir zum Senden von Daten unbedingt die Flusskontrolle für jeden Chunk an Ihren Code übergeben müssen. Auf diese Weise können Sie in dieser Zeit tun, was Sie wollen, einschließlich Ausnahmen auslösen, um die Anfrage insgesamt abzubrechen.

Wenn Sie aus irgendeinem Grund keine Chunked Transfer Encoding für Ihre Uploads verwenden können (unwahrscheinlich, aber möglich, wenn der betreffende Server wirklich schlecht ist), können Sie dasselbe tun, indem Sie ein dateiähnliches Objekt erstellen, das eine Länge hat, und dann Ihre Magie im Aufruf read , der wiederholt für 8192-Byte-Chunks aufgerufen wird. Auch dies stellt sicher, dass der Steuerungsfluss Ihren Code intermittierend durchläuft, sodass Sie Ihre eigene Logik verwenden können.

PS: Der obige Absatz, der mit "Andere Weise setzen, ..." beginnt, ist die Art von Anleitung, nach der ich in den Dokumenten gesucht habe. Angesichts der Anzahl der Anfragen, die Sie für die maximale Zeitüberschreitung erhalten (und Ihrer berechtigten Gründe, dies nicht zu tun), ist es vielleicht das Beste, einige dieser Informationen in die Zeitüberschreitungsdokumente aufzunehmen?

Schätze ich_. Im Allgemeinen bin ich jedoch immer nervös, wenn ich etwas defensiven Text in die Dokumentation einbaue. Ich denke, es könnte in eine FAQ gehen, aber Text, der erklärt, warum wir etwas _nicht_ haben, ist in der Dokumentation selten nützlich. Ich vermute, dass der Platz in den Dokumenten besser mit einem Rezept für etwas gedient wäre.

Ich denke, jeder, der kein totales Timeout will, denkt nicht klar darüber nach, was er will, und ich habe Schwierigkeiten, mir eine Situation vorzustellen, in der das, was Sie als Fehler beschreiben, "30 MB Download ändert sich in 30 GB und schlägt daher fehl", dies nicht der Fall ist in der Tat ein nützliches Feature!

He, ich bin nicht:

  • Paketmanager (z. B. pip, der Anfragen verwendet), bei dem Pakete in der Datengröße stark variieren können
  • Web-Scraper, der auf mehreren Websites ausgeführt werden kann, deren Größe stark variiert
  • ein Protokollaggregator, der Protokolldateien von Hosts herunterlädt, die sehr unterschiedliche Ebenen von uns (und daher Protokolldateigrößen) haben
  • Video-Downloader (Videos können in der Größe stark variieren)

Tatsächlich denke ich, dass der Fall, dass der Entwickler innerhalb einer Größenordnung weiß, mit welchen Dateigrößen er es zu tun haben wird, der ungewöhnliche Fall ist. In den meisten Fällen haben Entwickler keine Ahnung. Und im Allgemeinen würde ich sagen, dass es unklug ist, Annahmen über diese Größen zu treffen. Wenn Sie Einschränkungen bei der Downloadgröße haben, sollte Ihr Code diese Annahmen absichtlich codieren (z. B. in Form von Überprüfungen der Inhaltslänge), anstatt sie implizit zu codieren und sie mit der Bandbreite des Benutzernetzwerks zu mischen, damit andere Leute sie lesen Code kann sie deutlich sehen.

aber ich dachte, der Sinn von Anfragen wäre, dass es die Dinge für dich erledigt...

Requests behandelt ganz bewusst nicht alles für die Benutzer. Zu versuchen, alles zu tun, ist eine unmögliche Aufgabe, und es ist unmöglich, eine gute Bibliothek aufzubauen, die das tut. Wir sagen den Benutzern regelmäßig, dass sie zu urllib3 gehen sollen, um etwas zu erreichen.

Wir fügen Code nur dann in Anfragen ein, wenn wir es besser oder sauberer machen können, als es die meisten Benutzer können. Wenn nicht, gibt es keinen Wert. Ich bin wirklich noch nicht davon überzeugt, dass Total Timeout eines dieser Dinge ist, insbesondere angesichts dessen, was ich als relativ marginalen Nutzen ansehe, wenn es über unsere Benutzerbasis aggregiert wird.

Das heißt, ich bin offen dafür, überzeugt zu sein, dass ich falsch liege: Ich habe nur noch kein überzeugendes Argument dafür gesehen (und um Sie am Pass abzulenken, ist "Ich brauche es!" kein überzeugendes Argument: muss einige Gründe angeben!).

@sigmavirus24

Wenn ein totales Timeout irgendwo hingehört, wäre es da, aber auch hier müsste es unter Windows, BSD, Linux und OSX mit ausgezeichneter Testabdeckung funktionieren und ohne dass es ein Albtraum wäre, es zu warten.

Einverstanden!

@lukasa Ich denke, ich denke nicht nur, dass ich es will, sondern fast alle Benutzer würden es wollen, wenn sie darüber nachdenken (oder sie merken nicht, dass es noch nicht da ist). Die Hälfte Ihrer oben genannten Nutzungsszenarien, in denen Sie sagen, dass es vermieden werden sollte, würde ich sagen, es ist wichtig (Web Scraper und Log Aggregator) - die anderen beiden sind weniger notwendig, da wahrscheinlich ein Benutzer auf das Ergebnis wartet, der den Download manuell abbrechen kann wenn Sie wollen. Alles, was ohne UI im Hintergrund läuft und kein allgemeines Timeout verwendet, ist meiner Meinung nach fehlerhaft!

Ich nehme an, ich denke, dass nicht nur ich es will, sondern fast alle Benutzer es wollen würden, wenn sie darüber nachdenken (oder sie merken nicht, dass es noch nicht da ist).

@jribbens Wir haben mehrere Jahre (über ein Jahrzehnt, wenn Sie die Erfahrungen von uns dreien kombinieren) mit den Bedürfnissen unserer Benutzer gesprochen und sie verstanden. Was für fast alle (mindestens 98%) Benutzer notwendig war, waren Verbindungs- und Lese-Timeouts. Wir verstehen, dass eine sehr lautstarke Minderheit unserer Benutzer eine allgemeine Auszeit wünscht. In Anbetracht dessen, was wir hochrechnen können, um die Größe der Gruppe potenzieller Benutzer für diese Funktion im Vergleich zur potenziellen Größe der Benutzer, die diese Funktion nicht benötigen, und der Komplexität der Wartung und Entwicklung der Funktion, ist dies nicht wirklich etwas, was wir tun werden machen.

Wenn Sie etwas _Neues_ zu teilen haben, würden wir das gerne hören, aber alles, was Sie bisher gesagt haben, ist, dass Ihrer Meinung nach alles, was Anfragen ohne ein allgemeines Timeout verwendet, fehlerhaft ist, und ich kann mir vorstellen, dass es viele Benutzer gibt, die das tun würde Ihre Behauptung beleidigen, dass ihre Designentscheidungen fehlerhaft sind. Bitte sehen Sie also davon ab, die Intelligenz unserer Benutzer zu beleidigen.

@sigmavirus24 In diesem Thread waren Sie unnötig herablassend, aufrührerisch und unhöflich, und ich bitte Sie höflich, bitte hören Sie auf.

@Lukasa Ich habe mir Ihre Vorschläge zum Hochladen und Herunterladen von Streaming im Detail angesehen und die Dokumente zu diesen Themen gelesen. Wenn Sie meine Annahmen/Fragen bestätigen könnten, wäre das großartig.

  1. Wenn ich für Streaming-Downloads so etwas wie ein Lese-Timeout '(z. B. 5 s) und dann iter_content über ziemlich kleine Teile (z. B. 1 KB Daten)' verwende, bedeutet dies, dass die Anforderungsbibliothek das 5-s-Timeout für jeden Lesevorgang von 1 KB und Timeout anwendet, wenn dies der Fall ist dauert länger als 5s. Richtig?
  2. Wenn ich für Streaming-Uploads einen Generator oder ein dateiähnliches Objekt verwende, das Datenblöcke zurückgibt, und ich das Lese-Timeout auf 5 s setze, wendet die Anforderungsbibliothek das 5-s-Timeout für jeden Chunk an, den ich zurückgebe, und das Timeout, wenn es länger dauert. Richtig?
  3. Wenn ich keinen Generator zum Hochladen verwende und Bytes einfach direkt übergebe, wie entscheidet dann die Anforderungsbibliothek, das von mir festgelegte Lese-Timeout anzuwenden? Wenn ich beispielsweise einen Block mit einer Größe von 4 MB und einem Lese-Timeout von 5 Sekunden übergebe, wann genau wird dieses Lese-Timeout angewendet?
  4. Wenn ich iter_content nicht verwende und Anfragen einfach den gesamten Inhalt mit einem Lese-Timeout von 5 Sekunden direkt in die Anfrage herunterladen lasse, wann genau wird dieses Lese-Timeout angewendet?

Ich habe ein allgemeines Verständnis von Sockets/TCP-Protokoll/etc, aber nicht genau, wie urllib mit diesen Konzepten auf einer niedrigeren Ebene funktioniert oder ob Anfragen etwas Besonderes tun, außer die Werte weiterzugeben. Ich möchte genau verstehen, wie die Timeouts angewendet werden, da das einfache Zurückholen des Kontrollflusses und das Anwenden meines eigenen Timeout-Schemas angesichts der Crossplat-Probleme beim Beenden des Threads nicht funktioniert. Wenn es zusätzliches Lesematerial gibt, um meine Fragen zu beantworten, können Sie mich gerne weiterempfehlen! Auf jeden Fall sollten dies hoffentlich meine letzten Fragen sein. :)

Danke für deine bisherige Hilfe.

@emgerner-msft Okay:

  1. Nein. Leider ist es komplexer. Wie besprochen gilt jedes Timeout _pro Socket-Aufruf_, aber wir können nicht garantieren, wie viele Socket-Aufrufe sich in einem bestimmten Chunk befinden. Der ziemlich komplexe Grund dafür ist, dass die Standardbibliothek den Backing-Socket in ein Pufferobjekt (normalerweise so etwas wie io.BufferedReader ) verpackt. Dadurch werden so viele recv_into -Aufrufe getätigt, wie erforderlich sind, bis genügend Daten bereitgestellt wurden. Das kann so wenig wie null sein (wenn bereits genügend Daten im Puffer vorhanden sind) oder so viel wie genau die Anzahl der Bytes, die Sie empfangen haben, wenn der Remote-Peer Sie Byte für Byte tropft. Dagegen können wir wirklich sehr wenig tun: Aufgrund der Natur eines read() -Aufrufs gegen ein solches gepuffertes Objekt bekommen wir zwischen jedem recv_into -Aufruf nicht einmal den Kontrollfluss zurück.

Das bedeutet, dass der _einzige_ Weg, um sicherzustellen, dass Sie nicht länger als n Sekunden warten müssen, darin besteht, iter_content mit einer Chunk-Größe von 1 . Das ist eine absurd ineffiziente Art, eine Datei herunterzuladen (verbringt viel zu viel Zeit mit Python-Code), aber es ist die einzige Möglichkeit, die gewünschte Garantie zu erhalten.

  1. Ich glaube auch, dass die Antwort darauf nein ist. Wir haben derzeit keine Vorstellung von einem _Send_-Timeout. Um einen zu erhalten, verwenden Sie socket.setdefaulttimeout .
  2. Lese-Timeouts werden nur auf Lesevorgänge angewendet, daher spielt es keine Rolle, wie Sie den Text passieren.
  3. Diese Lese-Zeitüberschreitung unterliegt den gleichen Bedenken wie der Fall iter_content : Wenn Sie Anfragen haben, alles herunterzuladen, senden wir am Ende so viele recv_into -Aufrufe wie nötig, um den Text herunterzuladen, und die Zeitüberschreitung gilt zu jedem der Reihe nach.

Sie stoßen hier auf das Kernproblem: Anfragen kommen einfach nicht nahe genug an den Socket, um genau das zu erreichen, wonach Sie suchen. Wir _könnten_ ein Sende-Timeout hinzufügen: Das ist eine Feature-Request-Arbeit, und es leidet nicht unter den gleichen Problemen wie das Lese-Timeout, aber bei allem anderen stecken wir fest, weil httplib (zu Recht) auf Austausch besteht zu einer gepufferten Socket-Darstellung, und dann verwendet der Rest von httplib diese gepufferte Darstellung.

@Lukas

Ah, was für ein Durcheinander, haha. Ich dachte, das könnte der Fall sein, aber ich hoffte wirklich, dass ich falsch lag.

Zuerst brauchen wir dringend ein Sende-Timeout. Ich kann meinen Benutzern einfach nicht sagen, dass ihre Uploads einfach unendlich hängen bleiben können und wir keinen Plan haben, das Problem zu beheben. :/

Anscheinend befinde ich mich derzeit in einer unmöglichen Situation. Es gibt keine Bibliotheksunterstützung für das Gesamtzeitlimit (was ich verstehe). Es gibt keine Garantien dafür, wie genau das vorhandene Timeout mit verschiedenen Chunk-Größen funktioniert – wenn es eines gäbe, könnte ich die Zeit einfach zusammenfassen: Connect Timeout + Read Timeout * Chunk-Größe. In der Lage zu sein, den Fluss mit Stream-Modus und Generatoren zu unterbrechen, ist nett, aber da ich keine Lösung habe, um die Threads tatsächlich plattformübergreifend abzubrechen, hilft dies auch nicht. Sehen Sie andere Möglichkeiten, um voranzukommen? Was tun andere Benutzer, um diese Probleme zu lösen?

Zuerst brauchen wir dringend ein Sende-Timeout. Ich kann meinen Benutzern einfach nicht sagen, dass ihre Uploads einfach unendlich hängen bleiben können und wir keinen Plan haben, das Problem zu beheben. :/

Die in Anfragen verwendete Timeout-Logik ist also grundsätzlich die von urllib3, daher sollte es ausreichen, die Änderung dort vorzunehmen: Sie können gerne eine Feature-Anfrage öffnen und wir können Ihnen bei der Änderung helfen. Und kurzfristig können Sie mit setdefaulttimeout nachforschen.

Sehen Sie andere Möglichkeiten, um voranzukommen? Was tun andere Benutzer, um diese Probleme zu lösen?

Die Optionen, die Sie hier haben, hängen von Ihren spezifischen Einschränkungen ab.

Wenn Sie ein deterministisches Timeout haben _müssen_ (das heißt, wenn Sie garantieren können müssen, dass eine Anfrage nicht länger als _n_ Sekunden dauert), dann können Sie das mit der Python-Standardbibliothek, wie sie heute existiert, nicht einfach tun. In Python 2.7 müssten Sie socket._fileobject patchen, damit Sie für jeden recv -Aufruf ein sequenzielles Timeout ausführen können, aber in Python 3 ist es noch schwieriger, weil Sie in eine Klasse patchen müssen, deren Implementierung ist in C ( io.BufferedReader ), was ein Albtraum sein wird.

Andernfalls erhalten Sie es nur, indem Sie die Pufferung in der Standardbibliothek ausschalten. Das wird httplib und alle unsere Patches darüber kaputt machen, die davon ausgehen, dass wir einen read(x) -Aufruf machen können, der sich nicht wie der read -Systemaufruf auf einem Socket verhält, sondern wie der read Systemaufruf für eine Datei (dh gibt eine deterministische Länge zurück).

Anders ausgedrückt: Wenn Sie ein deterministisches Timeout _brauchen_, werden Sie feststellen, dass eine große Anzahl von Bibliotheken es einfach nicht für Sie bereitstellen können. Wenn sie httplib oder socket.makefile verwenden, haben Sie im Grunde Pech: Es gibt einfach keinen sauberen Weg, um zu garantieren, dass die Kontrolle in einer definierten Zeit an Sie zurückkehrt, außer durch wiederholte Ausgabe von Länge -1 liest. Sie _können_ das tun, aber es wird Ihrer Leistung schaden.

Hier müssen Sie also einen Kompromiss eingehen: Wenn Sie ein deterministisches Timeout wünschen, wird Ihnen die Art und Weise, wie die Pufferung in der Python-Standardbibliothek (und damit in Anforderungen) implementiert ist, dies einfach nicht zur Verfügung stellen. Sie können das wiederherstellen, indem Sie die Pufferung deaktivieren und den Code neu schreiben, aber das schadet Ihrer Leistung möglicherweise ziemlich stark, es sei denn, Sie implementieren die Pufferung auf eine Weise, die Zeitüberschreitungen anerkennt.

Sie könnten darauf abzielen, den erforderlichen Code in der Python-Standardbibliothek in der Klasse BufferedReader zu implementieren: Sie können auf jeden Fall die Python-Leute fragen, ob sie interessiert sind. Aber ich würde nicht die Luft anhalten.

Die in Anfragen verwendete Timeout-Logik ist also grundsätzlich die von urllib3, daher sollte es ausreichen, die Änderung dort vorzunehmen: Sie können gerne eine Feature-Anfrage öffnen und wir können Ihnen bei der Änderung helfen. Und kurzfristig können Sie dies mit setdefaulttimeout untersuchen.

Funktionsanfrage in urllib3 oder hier? Wird eine (oder beide) so schnell wie möglich öffnen.

Funktionsanfrage in urllib3: Wir müssen nichts Neues in Anfragen offenlegen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen