NEWS
[Hilfe] parllele asynchrone lese/schreibe HTTP Requests (Adapterentwicklung)
-
Ich brauche mal Eure Hilfe. JavaScript ist ziemlich neu für mich! Ich versuche gerade für die Alarmanlage Lupusec einen Adapter zu entwickeln. Für das Lesen und Setzen von Status (Plural?) bietet der Hersteller mehrere APIs an, die ich über HTTP Requests nutzen kann. D.h. es gibt ca. 4 Lese APIs und 2 APIs zum schreiben.
Lesen:
Alle 15 Sekunden werden alle 4 Lese APIs (asynchroner HTTP Request) aufgerufen. Bei einer Statusänderung durch die APIs wird der Status in den entsprechenden ioBroker Objekten mit ack = true angepasst.
Schreiben:
Wird der Status in einem ioBroker Objekt geändert, werden in adapter.on("stateChange", function (id, state) die entsprechenden APIs (asynchronen HTTP Requests) zum Ändern des Status in der Alarmanlage aufgerufen.
1. Wenn ein Zugriff einer API beim lesen länger als 15 Sekunden benötigt (asyncr. http request), wird der Lesezugriff nochmals im setInterval gestartet, bevor der vorherige Zugriff abgearbeitet ist. Damit kann ich noch leben.
2. Die 4 Lesezugriffe (4 asynchrone http requests) laufen parallel. Die Alarmanlage, scheint damit kein Problem zu haben. Auch damit kann ich leben. Ist vielleicht auch besser als das sequentielle abarbeiten der http requests (mir würde da als sequentielle Lösung nur der Weg von Request in Requests über callbacks einfallen oder die Nutzung von Probmise)
3. Jetzt das eigentliche Problem bzw. Hauptproblem. Es ist nicht unwahrscheinlich, dass der Lesezugriff für ein Status noch läuft und in dem Moment der Status in einem ioBroker Objekt geändert wird. Die Update API wird in adapter.on("stateChange", function (id, state) wird gestartet. Jetzt kann es sein, das der laufende Read Prozess den gesetzten ioBroker Status des Benutzers überschreibt. Damit kommt es zu einem falschen Zustand.
Bsp.
ioBroker-Stauts-abc = 0
-
Read API (Process 1) startet
-
Benutzer ändert ioBroker-Stauts-abc = 1 (ack = false)
-
Write API (Process 2) startet in adapter.on("stateChange", function (id, state)
-
Read API (Process 1) ändert Status ioBroker-Stauts-abc auf 0 und ack = true, (der Wert war vorher 1 durch den Benutzer mit ack = false gesetzt und überschrieben mit Wert 0 aus der Alarmanlage)
-
Read API (Process 1) beendet sich
-
Wirte API (Process 2) ändert Status in der Alarmanlage auf 1
-
Wirte API (Process 2) beendet sich
-
Read API (Process 3) startet
-
Read API (Process 3) ändert Status ioBroker-Stauts-abc auf 1 und ack = true, da ioBroker-Stauts-abc ja 0 ist (ack bleibt true).
-
Read API (Process 3) beendet sich
Wenn es gut läuft sieht es so aus:
ioBroker-Stauts-abc = 0
-
Read API (Process 1) startet
-
Read API (Process 1) beendet sich, da nichts zu ändern ist (Wert in Alarmanlage auch 0)
-
Benutzer ändert ioBroker-Stauts-abc = 1 (ack = false)
-
Write API (Process 2) startet in adapter.on("stateChange", function (id, state)
-
Wirte API (Process 2) ändert Status in der Alarmanlage auf 1
-
Wirte API (Process 2) beendet sich
-
Read API (Process 3) startet
-
Read API (Process 3) ändert Status ioBroker-Stauts-abc auf 1 und ack = true, da vorher 1 und ack = false (Wert 1 aus Alarmanlage ist gelesen).
-
Read API (Process 3) beendet sich
Hat jemand eine Idee wie ich das verhindern kann?
-
-
> Es ist nicht unwahrscheinlich, dass der Lesezugriff für ein Status noch läuft und in dem Moment der Status in einem ioBroker Objekt geändert wird. Die Update API wird in adapter.on("stateChange", function (id, state) wird gestartet. Jetzt kann es sein, das der laufende Read Prozess den gesetzten ioBroker Status des Benutzers überschreibt. Damit kommt es zu einem falschen Zustand.
Ohne jetzt im Detail auf den tatsächlichen Ablauf einzugehen, sehe ich grundsätzlich zwei Möglichkeiten, die beide auf einer Boolean-Status-Variable basieren:
1. Beschäftigt-Variable "busy" => 1. Prozess hat Vorrang:
Beim Start eines länger dauernden Prozesses (der nicht mehrfach laufen soll) wird diese auf true gesetzt, zum Abschluss auf false.
Wenn der Prozess (Instanz 2) gestartet werden sollte, aber busy = true ist, den Prozess nicht starten.
2. Abbrechen-Variable "cancel", am besten in Kombination mit der Beschäftigt-Variablen => letzter Prozess hat Vorrang:
Beim Start einer weiteren Instanz des Prozesses wird (sofern busy = true ist), wird cancel auf true gesetzt. Der laufende Prozess prüft in regelmäßigen Abständen ob er abgebrochen werden soll, macht dann nicht weiter und setzt cancel auf false.
Ganz und Überschneidungsfrei wäre es, wenn du dir alle laufenden Prozesse in einer Liste merkst und somit jedem laufendem Prozess getrennt den Abbrechen-Befehl senden kannst.
Hier ein Beispiel aus der .NET-Welt, wie das mit einem sogenannten CancellationToken realisiert werden kann: http://kean.github.io/post/cancellation-token
-
@tstueben:3. Jetzt das eigentliche Problem bzw. Hauptproblem. Es ist nicht unwahrscheinlich, dass der Lesezugriff für ein Status noch läuft und in dem Moment der Status in einem ioBroker Objekt geändert wird. Die Update API wird in adapter.on("stateChange", function (id, state) wird gestartet. Jetzt kann es sein, das der laufende Read Prozess den gesetzten ioBroker Status des Benutzers überschreibt. Damit kommt es zu einem falschen Zustand. `
Könnte man nicht auch mit Zeitstempeln arbeiten und diese Vergleichen?
Wenn der Wert im DP aktueller ist, als zum Start des Requests…
-
Zu 1.) kein Intervall nehmen sondern mit setTimeout arbeiten der immer am Ende des letzten der reads (wenn Ergebnis da ist) auf 15s später gesetzt wird
Zu 3.) ich würde mir im Adapter merken was der letzte wert war und bei einen read nur dann schreiben wenn der Wert anders ist. Das reduziert Schöntal dein Problem.
Die andere frage ist wann der Wert als „bestätigt gesetzt“ angesehen werden kann. Direkt wenn Update api „ok“ sagt oder erst wenn Du danach nochmal gelesen hast und der Wert dann geändert zurückkommt.
Mit der Timeout Idee von oben (Timeout „handle“ in Variable merken und auf null setzen wenn Timeout getriggert wurde) siehst du ob gerade ein read läuft (Timeout variable ist null) oder nicht (!=null) und kannst darauf reagieren und ggf ein Boolean flag setzen was ein read Ergebnis ignoriert.
Kam das einigermaßen verständlich rüber?
-
Zu 1.) kein Intervall nehmen sondern mit setTimeout arbeiten der immer am Ende des letzten der reads (wenn Ergebnis da ist) auf 15s später gesetzt wird `
Gute Idee. Da die Read Funktion durch den HTTP Request asynchron ist, muss ich den setTimout im HTTP Request setzten und dort die Read Funktion wieder aufrufen. Ich hoffe ich habe da keinen Denkfehler.
Zu 3.) ich würde mir im Adapter merken was der letzte wert war und bei einen read nur dann schreiben wenn der Wert anders ist. Das reduziert Schöntal dein Problem. `
Ja, genau so mache ich es. Ich ändere den Wert nur wenn er anders als der vorherige ist ode wenn ack == false.
Die andere frage ist wann der Wert als „bestätigt gesetzt“ angesehen werden kann. Direkt wenn Update api „ok“ sagt oder erst wenn Du danach nochmal gelesen hast und der Wert dann geändert zurückkommt.
Mit der Timeout Idee von oben (Timeout „handle“ in Variable merken und auf null setzen wenn Timeout getriggert wurde) siehst du ob gerade ein read läuft (Timeout variable ist null) oder nicht (!=null) und kannst darauf reagieren und ggf ein Boolean flag setzen was ein read Ergebnis ignoriert.
Kam das einigermaßen verständlich rüber? `
das kam verständlich rüber. Da muss ich mal schauen, wie ich das am besten umsetze. Ich muss ja den Update solange "parken", bis der aktuelle Read durch ist!
Momentan habe ich die Lösung von @ruhr70 mit dem Timestamp übernommen.
-
Naja Update parken musst du nicht. Mach ne variable „ignoriere_read“ die du setzt sobald du den update schickst der beim read Ergebnis dafür sorgt das es einfach ignoriert wird.
Ich kann mit das nach dem Urlaub wenn ich wieder Laptop gab gern nochmal ansehen. Wenn die Lösungsidee von Ruhr aber tut ist alles bestens
-
Naja Update parken musst du nicht. Mach ne variable „ignoriere_read“ die du setzt sobald du den update schickst der beim read Ergebnis dafür sorgt das es einfach ignoriert wird.
Ich kann mit das nach dem Urlaub wenn ich wieder Laptop gab gern nochmal ansehen. Wenn die Lösungsidee von Ruhr aber tut ist alles bestens `
das wäre super!!
jetzt habe ich alles auf setTimeout umgestellt und finde nach einer Weile einen "heap out of memory" Fehler im Log und der Adapter restarted. Ich gehe davon aus, dass der Fehler in der Funktion read() liegt. Was mache ich verkehrt? Wie kann ich mit setTimout "mich selber" immer wieder aufrufen?
function read() { var options = { url: self.getURL(urlDeviceListGet), rejectUnauthorized: false, headers: { "Authorization": self.auth } }; // GetRequest(options, callback); GetRequest(options, function(error, data) { parse(error, data); // hier wird data verarbeitet setTimeout(read, self.pollsec * 1000); }); } function GetRequest(options, callback) { request.get(options, function(error, response, body) { if (error || response.statusCode !== 200) { return callback(error); } else if (body) { try { // Sourcecode if (callback) { callback(null, body); } } catch (e) { } } }); }
-
@tstueben:Naja Update parken musst du nicht. Mach ne variable „ignoriere_read“ die du setzt sobald du den update schickst der beim read Ergebnis dafür sorgt das es einfach ignoriert wird.
Ich kann mit das nach dem Urlaub wenn ich wieder Laptop gab gern nochmal ansehen. Wenn die Lösungsidee von Ruhr aber tut ist alles bestens `
das wäre super!!
jetzt habe ich alles auf setTimeout umgestellt und finde nach einer Weile einen "heap out of memory" Fehler im Log und der Adapter restarted. Ich gehe davon aus, dass der Fehler in der Funktion read() liegt. Was mache ich verkehrt? Wie kann ich mit setTimout "mich selber" immer wieder aufrufen?
function read() { var options = { url: self.getURL(urlDeviceListGet), rejectUnauthorized: false, headers: { "Authorization": self.auth } }; // GetRequest(options, callback); GetRequest(options, function(error, data) { parse(error, data); // hier wird data verarbeitet setTimeout(read, self.pollsec * 1000); }); } function GetRequest(options, callback) { request.get(options, function(error, response, body) { if (error || response.statusCode !== 200) { return callback(error); } else if (body) { try { // Sourcecode if (callback) { callback(null, body); } } catch (e) { } } }); } ```` `
Fehler gefunden! Ich hatte an einer Stelle setInterval statt setTimeout benutzt.
-
`
Eine Frage hätte ich aber noch zum dem setTimeout. Wenn sich die Funktion mit dem setTimeout immer selber in den aufgerufenen asynchronen Funktionen aufrufe, landen die Aufrufe nicht alle auf dem Stack. Und irgendwann geht dann nichts mehr?Ist es sinnvoll vor dem nächsten handle = setTimeout, den vorherigen mit clearTimeout(handle) zu löschen?
-
Bei setTimeout wird es einmalig ausgeführt. Dann ist es ja weg und die zurückgegebene id ungültig.
Diese zurückgegebene id nutzt du nur um den Timeout vorzeitig abzubrechen (oder als Trick um zu sehen ob er schon gefeuert wurde oder nicht)