HTTPUtils_NonblockingGet - grosse Post requests (> 30 k)

Begonnen von viegener, 29 September 2015, 15:39:51

Vorheriges Thema - Nächstes Thema

viegener

und ich schon wieder zu HTTPUtils  ;D


Ich habe jetzt einige graue Haare dabei bekommen (Nein eigentlich sind die alle schon grau  :D ) herauszufinden, warum mein Modul keine Bilder über Telegram versenden kann (HTTP POST).

Nachdem ich HTTPUtils ein wenig instrumentiert habe ist mir aufgefallen, dass im HTTPUtils_NonblockingGet die Daten per syswrite in den Socket geschrieben werden, aber nicht überprüft wird ob das auch komplett geschieht. Auf meinem Raspberry werden jeweils nur ca. 30k Daten geschrieben.

Man muss also wohl die Daten in Stücken schreiben und bei Rückmeldung: "Resource temporarily unavailable" einen Moment warten bis die Daten versendet werden.

Ich kann versuchen mal einen Patch vorzubereiten.
Vielleicht hat aber auch jemand eine bessere Lösung für den Nonblocking Transfer (http post) von grösseren Datenmengen (aus Dateien). Denn an sich wäre es vermutlich besser eine Lösung zu bauen, die grössere Datenmengen aus Dateien streamt und nicht erst komplett in den Speicher liest...

Johannes
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

viegener

#1
Wie angekündigt, habe ich mal eine veränderte Version von HTTPUtils erstellt, die bei einem Postrequest die Daten wenn nötig in mehreren "chunks" überträgt, wenn auf Sendeseite der Puffer voll ist.

Leider war die Änderung doch etwas aufwändiger und hat im wesentlichen eine komplette Neuimplemenitierung / Zerlegung von HTTPUtils_Connect2 erfordert. Denn diese FUnktion wird sowohl bei blocking als auch bei nonBlocking requests verwendet und auch bei Post und Get-Requests.

Ich habe grosse Übertragungen mit meinem Modul getestet und auch Übertragungen aus anderen Modulen die httputils verwenden laufen lassen. Trotzdem sind noch weitere Tests erforderlich, insbesondere wenn POST-Requests verwendet werden (inesbesondere die hash-variante). Also herzliche Einiladung an alle Tester!

Das Modul ist ganz bewusst noch mit vielen Logausgaben versehen, um etwaige Auffälligkeiten analysieren zu können.

Ich habe auch hier keinen Patch erstellt, da die Änderungen doch sehr weitreichend mit vielen neuen Funktionen sind. Der basisstand ist aber die aktuellste Version, die diese Woche per update verteilt wurde.

Gruss,
Johannes

Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

viegener

Jetzt habe ich doch mal einen diff file / patch für httputils erstellt auf der Basis der SVN-Revision r9318 (also aktueller Stand heute).

Vielleicht gibt es ja Leute, die die Version aus dem vorherigen Post oder den hier angehängten Patch testen könnten?

Bei mir läuft die Version bisher störungsfrei und hat auch schon megabyte grosse Dateien per POST-Request abgeschickt (ohne den Server dabei zu blockieren).

Rückmeldung gerne in diesem Thread...

@Rudi: Soll ich die Logausgaben wieder entfernen bzw. was sollte getan werden, um die Änderungen in httputils zu übernehmen?

Gruss,
Johannes
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

rudolfkoenig

"komplette Neuimplemenitierung" ist ueblicherweise ein rotes Tuch fuer mich, da ich damit den Code von jemanden anderen warten muss. Darunter kraenkeln diverse Module bis heute. Falls man wirklich grosse Aenderungen in meinem Code vornehmen muss, dann ist es mir lieber einfache/gute Testbedingungen zu beschreiben.

Weiterhin hast du ja selber geschrieben, dass du unsicher bist, und Tester brauchst. Damit habe ich einen vorzueglichen Vorwand gehabt, mich zurueckzuhalten.

viegener

Zitat von: rudolfkoenig am 03 Oktober 2015, 11:34:12
"komplette Neuimplemenitierung" ist ueblicherweise ein rotes Tuch fuer mich, da ich damit den Code von jemanden anderen warten muss. Darunter kraenkeln diverse Module bis heute. Falls man wirklich grosse Aenderungen in meinem Code vornehmen muss, dann ist es mir lieber einfache/gute Testbedingungen zu beschreiben.

Kein Problem! Das kann ich verstehen, ich sah allerdings nur 2 Möglichkeiten: Ein paralleles Modul  für die Nonblocking-Behandlung von http-Requests zu bauen und das fände ich wenig sinnvoll oder eben das vorhandene Modul so saft wie möglich erweitern. Mehrfachimplementierungen sind aus meiner Sicht ein nogo (nicht speziell oder nur bei fhem).

Ausserdem finde ich die Unterstützung von grösseren Http-Postrequests (grösser 10k) soweit nützlich, dass ich denke auch andere Module könnten so etwas mal benötigen (z.B. bei restapis).

Ich kann anbieten auch als Maintainer für httputils bereitzustehen, denn das mit der Teststellung verstehe ich nicht ganz.

Zitat von: rudolfkoenig am 03 Oktober 2015, 11:34:12
Weiterhin hast du ja selber geschrieben, dass du unsicher bist, und Tester brauchst. Damit habe ich einen vorzueglichen Vorwand gehabt, mich zurueckzuhalten.

Unsicher bin ich deshalb, weil ich (1) nur einen Teil der Module im Einsatz habe, die httputils verwenden (trotz meines Zoos) und damit nicht alles testen kann und httputils ziemlich zentral im System hängt.


Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

viegener

Hier noch eine kurze Beschreibung was ich geändert habe:

In HTTPUtils gibt es eine zentrale Routine _Connect2, die an verschiedenen Stellen für den eigentlichen http-Transfer aufgerufen wird. Im Falle von nonblocking transfers (also mit callback) wird _Connect2 innerhalb einer inline definierten sub aufgerufen. Diese Inline-sub wird als DirectWrite-Funktion in der Mainloop von fhem aufgerufen.

Bisher wurde darin header und body des requests über 2 syswrite requests an die Connection zum server übertragen. Da der Socket auch nonblocking definiert ist, lassen sich immer nur soviele Daten auf einmal in den Socket schreiben, bis der interne Puffer voll ist. Dies ist erkennbar an der Rückgabe von syswrite (Anzahl Bytes kleiner als Länge der zu übertragenden Daten).

Die inline Directwrite function ruft jetzt nacheinander die Phasen von Connect2, jede Phase wird separat aus der fhem-Schleife aufgerufen, so dass fhem zwischendurch weiter andere devices/module bearbeiten kan:

Connectphase 0 - Init - Vorbereitung

Connectphase 1 - Header - Übertragung des http headers (weiterhin in einem, da anzunehmen dass er immer kleiner als der Puffer ist)

Connectphase 2 - looping through data transfer -
    Diese Routine wird u.U. mehrfach aus der Mainloop von fhem aufgerufen bis alle Daten im Buffer sind

Connectphase 3 - finalize (also start timer again and directreadfn added)
    Hier wird auch der timer wiedergestartet für ein timeout der response und auch die entpsrechende Funktion für das Lesen/Bearbeiten der Antwort
   
Die einzelnen Phasen sind im wesentlichen derselbe Code, der bisher in der Funktion _Connect2 hintereinander ausgeführt wurde.
   
Die Routine _Connect2 gibt es weiterhin, sie ruft aber jetzt die obigen Phasen nacheinander auf, damit das Verhalten unverändert ist und die entsprechenden Teil nur einmalig existieren.

Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

rudolfkoenig

Zitatdas mit der Teststellung verstehe ich nicht ganz.

Ich brauche eine Moeglichkeit das Problem selbst zu sehen, und wenn ich eine Loesung programmiert habe, es selbst zu testen, ob es funktioniert.

viegener

Zitat von: rudolfkoenig am 04 Oktober 2015, 12:49:30
Ich brauche eine Moeglichkeit das Problem selbst zu sehen, und wenn ich eine Loesung programmiert habe, es selbst zu testen, ob es funktioniert.

OK, das klingt erstmal machbar:

Möglichkeit 1 - HTTPUtils_NonblockingGet mit einem HTTP-POST-Request aufrufen, bei der im hash unter "data" grössere Mengen Daten (z.B. mehr als 16kB auf dem Raspberry) enthalten sind. Effekt - Post request wird nur teilweise übertragen (lässt sich auch durch loggen des syswrite-Ergebnisses nachvollziehen) oder natürlich am Server.

Falls kein Server zur Hand kann man über http://requestb.in/ jederzeit einen Test-Server einrichten, der die Requests auch vollständig anzeigt. Damit kann man problemlos requests nachvollziehen, wenn sie zumindest vollständig sind. Das erfordert keine Registrierung und ist relativ dynamisch.

Möglichkeit 2 - 50_TelegramBot.pm Modul installieren (mit gültigem API token) und über sendPhoto ein Photo entsprechender Grösse versenden.
Siehe dazu hier: http://forum.fhem.de/index.php/topic,38328.0.html

Möglichkeit 3 - Ich müsste ein Testmodul schreiben, dass quasi nichts anderes macht als einen solchen Request zu versenden, vielleicht hast Du ja auch ein httpUtils-Testmodul in das ich so etwas einbauen könnte?


Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

viegener

Hallo Rudi,
ich habe mich mal an Variante 3 gewagt und ein Testprogramm geschrieben.

Das Modul TestHTTPUtil.pm siehe unten, lässt sich in fhem relativ einfach einbinden.
(Ich habe es über use TestHTTPUtil; in Myutils eingebunden).

Die eigentliche Testroutine ist sub TestHTTPUtil_LargePostRequest($$) diese bekommt einen URL als String und einen Dateinamen mit Pfad übergeben. Die entsprechende Datei wird direkt in den post-request (multipart formdata) übernommen. Der eigentliche Postrequest ist etwa 200 Bytes grösser als die Dateigrösse.

Bei mir laufen Requests bis ca 32k mit dem originalen HTTPUtils durch. Wenn ich (auf dem Pi2) höher gehe wird der Request nicht mehr vollständig übertragen, damit kommt nie eine Antwort vom Server (der ja immer noch auf das Ende der Übertragung wartet) und der Request läuft in einen Timeout.

Das eigentliche Problem lässt sich auch gut sehen, wenn man in HTTPUtils (Zeile 272)
  syswrite $hash->{conn}, $data if(defined($data));

ersetzt durch eine etwas geschwätzigere:

Debug "syswrite hat gesendet: ".syswrite( $hash->{conn}, $data)." bytes  von ".length($data)." Bytes" if(defined($data));

Bei mir kommt es bei folgendem Befehl:
{ Debug TestHTTPUtil_LargePostRequest( "http://requestb.in/17m24431", "/opt/fhem/www/images/default/weather/drizzle.png" ) }

zu folgender Ausgabe (drizzle.png ist fast 37k gross)

DEBUG>syswrite hat gesendet: 27360 bytes  von 36902 Bytes

und voila da fehlt doch was  ;)

Gruss,
Johannes
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

Loredo

Wäre das auch eine Erklärung, dass hier und dort nach längerer Betriebszeit "Too many open files" auftritt, wenn HttpUtils im Spiel sind? Beispiel ist das ENIGMA2 Modul. Dort tritt das mutmaßlich vor allem bei Einsatz von https auf, soweit ich weiß kam es aber auch schon bei unverschlüsseltem http vor.
Hat meine Arbeit dir geholfen? ⟹ https://paypal.me/pools/c/8gDLrIWrG9

Maintainer:
FHEM-Docker Image, https://github.com/fhem, Astro(Co-Maintainer), ENIGMA2, GEOFANCY, GUEST, HP1000, Installer, LaMetric2, MSG, msgConfig, npmjs, PET, PHTV, Pushover, RESIDENTS, ROOMMATE, search, THINKINGCLEANER

viegener

Auf den ersten Block kann ich das nicht unbedingt bestätigen. Die von mir beschriebene Beschränkung betrifft eigentlich nur Post-Requests, in ENIGMA wird zwar generell auch POST unterstützt, aber ich kann nicht erkennen, dass grosse Datenmengen übertragen werden.
Ein Versuch wäre es immer wert, denn die Verbindung läuft auf jeden Fall in einen Timeout, wenn das Problem auftritt, damit kann bei vielen Anfragen (retries ?) ein Problem mit open files entstehen.

Ich habe selber leider kein ENIGMA-passendes Gerät, deshalb kann ich den Test nicht machen. Wäre aber interessiert zu hören, ob das obige httputils
- Das Problem verbessert?
- Andere Probleme erzeugt?

gruss,
Johannes
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

rudolfkoenig

@Johannes: Ich habe es mit Moeglichkeit 1 versucht:
fhem> define n1  notify n1 { HttpUtils_NonblockingGet({ url=>"http://requestb.in/1hehe071", callback=>sub($$$) { Log 1, "$_[0]->{myParam} ERR:$_[1] DATA:$_[2]" }, myParam=>"MyParam", data=> "HelloWorld "x10000 }) }
fhem> trigger n1


Laut http://requestb.in/1hehe071?inspect sind alle Daten angekommen.

Ich habe das Schreiben in HttpUtils_Connect2 trotzdem auf nonblocking umgestellt (directWriteFn), funktioniert immer noch. Bitte testen.

viegener

@Rudi:

Interessant auf welcher Plattform hast Du das ausgeführt? Ich habe entsprechende Tests auf verschiedenen Linuxen ausgeführt (speziell Wheezy auf PI2) ?

Habe auch nochmals etwas dazu gesucht und es geht ja eigentlich um den socket buffer der normalerweise mit setsockopt eingestellt werden kann:
ZitatFor SO_SNDBUF the min size is 4k the default size is 16K and the maximum can be of 128K.
Das passt auch zu meinen Tests, erklärt aber nicht warum bei Dir 100k funktionieren.

Also macht es vielleicht Sinn, Deinen test mit x100000 zu wiederholen, also statt 100k sogar 1MB zu übertragen...

Johannes
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

viegener

Habe mir gerade auch noch Deine Änderungen angeschaut, kann sie aber wohl frühestens heute abend ausprobieren.
Ja das könnte grundsätzlich funktionieren und ist sicher kürzer als meine Änderung  :D
Was möglicherweise noch anfällt ist, dass man explizit den Fall EAGAIN als Rückgabecode nicht zum Abbruch sondern als Hinweis noch etwas zu warten verwenden muss.

Johannes
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

rudolfkoenig

Auf Ubuntu 14.04 ist der TCP/IP Puffer ca 700k gross, auf OSX ca 1MB, d.h. erst darueber gibt es Schwiergikeiten.