FHEM Forum

FHEM => Automatisierung => Thema gestartet von: Torxgewinde am 22 September 2023, 08:11:16

Titel: BlockingCall: Was passiert mit "rate-limited" Web-APIs wie Solcast, Landroid...?
Beitrag von: Torxgewinde am 22 September 2023, 08:11:16
Hallo,
Nutzt man BlockingCall() so wird der FHEM Prozess geforkt. Jetzt gibt es meinem Verständnis nach Devices die sowohl im Child- und im Parent-Prozess weiterhin Anfagen an Web-APIs stellen.

Das kann doch bei WEB-APIs die die Anzahl der Anfragen pro Tage begrenzen zu einer erhöhten Last führen, richtig? Wie geht ihr damit um?
Titel: Aw: BlockingCall: Was passiert mit "rate-limited" Web-APIs wie Solcast, Landroid...?
Beitrag von: rudolfkoenig am 22 September 2023, 10:17:53
Das Landroid Modul versucht die Anfragen im Modul zu limitieren.
Wohl nicht zu 100% erfolgreich, wie man es im entsprechenden Thema sieht.
Hat aber nichts mit BlockingCall zu tun.
Titel: Aw: BlockingCall: Was passiert mit "rate-limited" Web-APIs wie Solcast, Landroid...?
Beitrag von: DS_Starter am 22 September 2023, 10:41:21
Thema "Web-APIs wie Solcast"...
Ich weiß nicht ob es nur eine allgemeine Frage ohne direkten Modulbezug ist, aber im Modul SolarForecast wird dieser API Abruf nicht in einem BlockingCall ausgeführt. Die API Anfragen werden via HttpUtils umgesetzt und die entsprechenden Limitierungen im Modul in den Zeitraum zwischen Sonnenauf- und Untergang optimiert.
Titel: Aw: BlockingCall: Was passiert mit "rate-limited" Web-APIs wie Solcast, Landroid...?
Beitrag von: Torxgewinde am 22 September 2023, 18:13:26
Danke für die Antworten, diese bestätigen meine Vermutung. Ich habe es nun so gelöst:

Alle FHEMWEB Instanzen werden im "Child" als allererstes mit "attr TYPE=FHEMWEB plotfork 0");" umgeändert, so dass dort nicht abermals geforkt wird sofern man SVGs abruft. Ich denke dass in meinem Fall Kind- und Parentprozess zuviel auf Landroids Web-API parallel arbeiteten, ich war dort nämlich für ~24h ausgesperrt.

Kontext:
Ein SVG mit einer Jahresansicht berechnet hier recht lange (~12s). Man könnte jetzt die Datenbasis ausdünnen und optimieren, das wäre auch vermutlich der "normale" Weg. Da ich mich ohnehin mit BlockingCall (https://wiki.fhem.de/wiki/Blocking_Call) auseinandersetzte, habe ich mir etwas mit einem Weblink (https://wiki.fhem.de/wiki/Weblink) gebaut, dass mir zuerst den "alten" SVG-Plot anzeigt und im Hintergrund im Kindprozess einen neuen Plot berechnet. Wenn der Plot dann fertig berechnet ist, wird er mit FW_directNotify (https://wiki.fhem.de/wiki/DevelopmentFHEMWEB-API#FW_directNotify) ausgespielt und in allen offenen Browserfenster aktualisiert. Das ist ein wenig ausgeufert, ist aber auch sehr lehrreich und im Alltag sogar praktisch. So habe ich gelernt, dass die Antwort aus der BlockingFn am besten einfach Base64 encodiert ist. Und ich durfte lernen, da die Javascript-Funktion escape() deprecated ist folgendes Monstrum ein Equivalent darstellt: var decodedString = new TextDecoder("utf-8").decode(new Uint8Array([...atob(newHTML)].map(char => char.charCodeAt(0))));

defmod GaszaehlerJahresverbrauch.weblink weblink htmlCode {\
my $name = "GaszaehlerJahresverbrauch.weblink";;\
\
my $ret = "<a href=\"/fhem?detail=$name\">Edit</a> | ";;\
$ret .= "<a href=\"/fhem?detail=SVG_Gaszaehler.Log_1\">SVG</a><br />\n";;\
\
my $finishFnName = "finishFn_${name}";;\
$finishFnName =~ s/[\.\-]/_/g;;\
$ret .= "<!-- finishFnName: $finishFnName -->\n";;\
\
#create finish-function only once for this weblink, needed here to include $name\
if (1 or !defined(&{$finishFnName})) {\
my $finishFnString = AttrVal($name, "finishFn", undef);;\
$finishFnString =~ s/\$finishFnName/$finishFnName/g;;\
$finishFnString =~ s/\$name/$name/g;;\
$ret .= "<!-- finishFnString: $finishFnString -->\n";;\
eval($finishFnString) or $ret .= "<!-- Error in finishFn Attribute: $@ -->\n";;\
}\
\
my $svgName = AttrVal($name, "SVG_Device_Name", undef);;\
$ret .= "<!-- svgName: $svgName -->\n";;\
\
my $workerFn = eval(AttrVal($name, "BlockingFn", undef)) or $ret .= "Error in BlockingFn Attribute: $@\n";;\
\
my $age = ReadingsAge($name, "cache", -1);;\
$ret .= "<!-- age: $age -->\n";;\
\
if ( $age > 10 and !ReadingsVal($name, "busy", 0) ) {\
my $hash = $defs{$name};;\
readingsSingleUpdate($hash, "busy", 1, 1);;\
my $result = BlockingCall($workerFn, "$svgName", "$finishFnName", 90);;\
}\
$ret .= ReadingsVal($name, "cache", "<!-- no cached value yet -->");;\
$ret .= '<script>function replaceElementsByID(myID, newHTML) {\
var decodedString = new TextDecoder("utf-8").decode(new Uint8Array([...atob(newHTML)].map(char => char.charCodeAt(0))));;\
\
// search by ID, but do not use #ID as selector to get all occurences even if ID is used multiple times\
document.querySelectorAll("[id*=\""+myID+"\"]").forEach(myElement => {\
if (myElement.id != myID) return;;\
myElement.outerHTML = decodedString;;\
});;\
}</script>';;\
\
return $ret;;\
}
attr GaszaehlerJahresverbrauch.weblink userattr SVG_Device_Name BlockingFn finishFn
attr GaszaehlerJahresverbrauch.weblink BlockingFn sub {\
my ($svgName) = @_;;\
\
#set all FHEMWEB instances to plotfork 0,\
#intention is to prevent another forked process and running other tasks concurrently\
fhem("attr TYPE=FHEMWEB plotfork 0");;\
\
$FW_RET                 = undef;;\
$FW_webArgs{dev}        = $svgName;;\
$FW_webArgs{logdev}     = InternalVal($svgName, "LOGDEVICE", "");;\
$FW_webArgs{gplotfile}  = InternalVal($svgName, "GPLOTFILE", "");;\
$FW_webArgs{logfile}    = InternalVal($svgName, "LOGFILE", "CURRENT");;\
##$FW_pos{zoom}           = "24hours";; #day, week\
##$FW_pos{off}            = 0;;\
\
#get the SVG from current FHEMWEB instance\
my ($mime, $svgdata) = SVG_showLog("unused");;\
$FW_RET = undef;;\
\
my ($w, $h) = split(",", AttrVal($svgName, "plotsize", "800,160"));;\
my $extra = '<text x="10" y="10" style="fill: red;;">'."$hms".'</text>';;\
$svgdata =~ s/<\/svg>/<polyline opacity="0" points="0,0 $w,$h"\/>$extra<\/svg>/;;\
$svgdata =~ s/\n/ /g;;\
\
$svgdata = MIME::Base64::encode_base64($svgdata);;\
$svgdata =~ s/\n//g;;\
return $svgdata;;\
};;
attr GaszaehlerJahresverbrauch.weblink SVG_Device_Name SVG_Gaszaehler.Log_1
attr GaszaehlerJahresverbrauch.weblink finishFn no warnings 'redefine';;\
sub $finishFnName($) {\
my ($result) = @_;;\
my $plain = MIME::Base64::decode_base64($result);;\
\
my $hash = $defs{'$name'};;\
readingsSingleUpdate($hash, "busy", 0, 1);;\
readingsSingleUpdate($hash, "cache", "<html>".$plain."</html>", 1);;\
\
my $MY_SVG = AttrVal('$name', "SVG_Device_Name", "???");;\
map {\
FW_directNotify("#FHEMWEB:$_", "replaceElementsByID('SVGPLOT_${MY_SVG}', '$result')", "");;\
} devspec2array("TYPE=FHEMWEB");;\
};;\
use warnings 'redefine';;
attr GaszaehlerJahresverbrauch.weblink group Gas
attr GaszaehlerJahresverbrauch.weblink room Keller
attr GaszaehlerJahresverbrauch.weblink widgetOverride BlockingFn:textField-long,87 finishFn:textField-long,87

So sieht es dann aus (Der Link SVG führt zur Detailseite des SVG und Edit erlaubt die Bearbeitung des Weblinks):
Bildschirmfoto_2023-09-22_18-09-28.png
Titel: Aw: BlockingCall: Was passiert mit "rate-limited" Web-APIs wie Solcast, Landroid...?
Beitrag von: Torxgewinde am 23 September 2023, 08:21:01
These:
Ich denke das Verhalten ist bisher nicht aufgefallen, da die meisten ja BlockingCall() nutzen dürften um den Kindprozess wirklich blockierend auszuführen. Dabei kann dann auch kein anderes Device noch nebenläufig etwas machen und es kommt garnicht zu dem unerwünschten Verhalten. Hier hatte ich mit SVGs eben etwas erwischt, dass eigentlich nochmal forkt... (außer man schaltet es mit "plotfork 0" aus, wie oben dargestellt).
Titel: Aw: BlockingCall: Was passiert mit "rate-limited" Web-APIs wie Solcast, Landroid...?
Beitrag von: frober am 23 September 2023, 09:12:55
Im Fork läuft doch nicht das komplette Fhem parallel. Es wird nur die gewünschte Handlung abgearbeitet und der Fork wieder beendet.

Nach deiner Theorie würde z.B. Landroid bei jedem Fork versuchen eine Verbindung herzustellen. Da jedesmal die gleich ID verwendet wird, hätten alle das Problem gesperrt zu werden, da die ID nur einmal erlaubt ist.
Titel: Aw: BlockingCall: Was passiert mit "rate-limited" Web-APIs wie Solcast, Landroid...?
Beitrag von: Torxgewinde am 23 September 2023, 20:32:53
Naja schon, doch - ich kenn' die spezifischen Abweichungen bei Perl nicht genau, aber vom einem Linux her ist ein Fork eine zweite Instanz des Prozesses, so eine Art Schnappschuß die ab dem Moment seinen eigenen Speicher hat und ab dem Fork-Aufruf voneinander abweichen kann. Platt ausgedrückt ist es eine schnelle, aber vollwertige Kopie des Elternprozesses (Windows macht das nicht genau so, sondern nochmal anders - deswegen dürfte Perl da ein wenig tricksen um es einheitlich darzustellen).

Wikipedia sagt auch (https://de.wikipedia.org/wiki/Fork_(Unix) (https://de.wikipedia.org/wiki/Fork_(Unix))):
...
In unixoiden Betriebssystemen ist fork der Name eines Systemaufrufs, anhand dessen der aufrufende Prozess (Elternprozess) eine Kopie von sich selbst erzeugt, einen sog. Kindprozess. Der Kindprozess übernimmt dabei die Daten, den Code, den Befehlszähler und die Dateideskriptoren vom Elternprozess und erhält vom Kernel (wie der Elternprozess und jeder andere Prozess auch) eine eigene Prozessnummer, die PID (engl. ,,Process IDentifier"). In der Folge verwaltet das Betriebssystem den Kindprozess als eigenständige Instanz des Programms und führt ihn unabhängig vom Elternprozess aus.
...


Meistens wird ein Fork von einem Exec gefolgt, wobei dann der Kindprozess durch Exec durch einen neuen Prozess "überschrieben" wird, aber noch alle offenen Dateideskriptoren erbt - das scheint mir IMHO aber hier nicht der Fall zu sein.

Zu deinen Punkten:
Im Fork läuft doch nicht das komplette Fhem parallel.
Doch, erstmal schon - es "läuft" nur nicht frei ab sondern hängt in der BlockingFn. Es führt nichts anderes des FHEM Prozesses aus. Die anderen Devices in FHEM sind dann praktisch "nicht dran" und sind AFAIK erstmal im Zustand "wartend" außen vor.

Es wird nur die gewünschte Handlung abgearbeitet und der Fork wieder beendet.
Genau, solange man also blockierend weitermacht in der BlockingFn dürfte keines der anderen Devices was machen können. Wenn man aber aus der BlockingFn irgendwie den anderen Devices Rechenzeit freigibt, dürften die IMHO loslegen.

Nach deiner Theorie würde z.B. Landroid bei jedem Fork versuchen eine Verbindung herzustellen.
Solange das Device im Kind nicht Rechenzeit erhält, kann es nicht loslegen. solange man also in der BlockingFn "am Stück rechnet", also komplett blockiert, passiert nichts in den anderen Devices im geerbtem FHEM.

Da jedesmal die gleich ID verwendet wird, hätten alle das Problem gesperrt zu werden, da die ID nur einmal erlaubt ist.
Ja, das wäre die Sorge. Deswegen die Eingangfrage in Posting #1 wie ihr damit umgeht. Ich glaube aber, dass das Problem in der Praxis nur in seltenen Sonderfällen auftritt, z.B. wenn man aus der BlockingFn nochmal forkt oder sonstwie nebenläufig arbeitet (Threads z.B.).

Edit:
Das Problem ist während der Entwicklung entstanden, dabei ist mir FHEM einige Male abgeschmiert, deswegen These #2:
Wenn man sein FHEM mehrfach neustartet, treibt das natürlich auch die WEB-API-Zugriffe nach oben. Das ist momentan meine favorisierte These, kommt am besten hin...

Edit #2:
Es scheint egal zu sein, ob man in der BlockingFn() FHEMWEB auf "attr TYPE=FHEMWEB plotfork 0" setzt, oder nicht. Es klappt auch ohne diese Einstellung. Vermutlich ist es also wirklich eher so gewesen wie bei These #2 - beim Testen einfach zu oft FHEM neugestartet.