Blocking Call: Unterschied zwischen den Versionen
K (typo) |
K (typo) |
||
(9 dazwischenliegende Versionen von 5 Benutzern werden nicht angezeigt) | |||
Zeile 3: | Zeile 3: | ||
= Allgemein = | = Allgemein = | ||
Das Modul Blocking.pm wurde von Rudolf König entwickelt um in FHEM Funktionsaufrufe zu ermöglichen die relativ viel Zeit in Anspruch nehmen und normalerweise FHEM damit zum Stillstand für die Dauer der Ausführung | Das Modul Blocking.pm wurde von Rudolf König entwickelt, um in FHEM Funktionsaufrufe zu ermöglichen, die relativ viel Zeit in Anspruch nehmen und normalerweise FHEM damit zum Stillstand (für die Dauer der Ausführung der Funktion) bringen würde. | ||
Um so etwas zu verhindern kann man mit Hilfe von Blocking.pm eine Funktion über einen Fork des Hauptprozesses unabhängig davon abarbeiten und das | Um so etwas zu verhindern, kann man mit Hilfe von Blocking.pm eine Funktion über einen Fork des Hauptprozesses unabhängig davon abarbeiten und das Ergebnis dieser Funktion optional an den Hauptprozess übergeben. | ||
= Benutzung = | = Benutzung = | ||
Zeile 51: | Zeile 51: | ||
* Veränderungen an internen Variablen von FHEM (Device Hashes, Timers, usw.) werden innerhalb eines BlockingCalls durchgeführt und sind dort auch sichtbar, haben aber keinerlei Einfluss auf den eigentlichen FHEM Hauptprozess und alle Definitionen. Solche Veränderungen müssen an die finishFn delegiert werden (z.B. durch zusätzliche Rückgabewerte), da es sich um einen Fork-Prozess handelt, der sich nach Abschluss des BlockingCall selbst zerstört. | * Veränderungen an internen Variablen von FHEM (Device Hashes, Timers, usw.) werden innerhalb eines BlockingCalls durchgeführt und sind dort auch sichtbar, haben aber keinerlei Einfluss auf den eigentlichen FHEM Hauptprozess und alle Definitionen. Solche Veränderungen müssen an die finishFn delegiert werden (z.B. durch zusätzliche Rückgabewerte), da es sich um einen Fork-Prozess handelt, der sich nach Abschluss des BlockingCall selbst zerstört. | ||
* Wenn viele Definitionen aktiv sind, welche BlockingCalls parallel starten kann es zu einem relativ hohen Memory-Footprint kommen aufgrund mehrfach laufenden Fork-Prozessen. Dies kann auf schwachbrüstiger Hardware zu Speicherengpässen führen. Ab FHEM Revision 11917 lassen sich die maximal | * Wenn viele Definitionen aktiv sind, welche BlockingCalls parallel starten kann es zu einem relativ hohen Memory-Footprint kommen aufgrund mehrfach laufenden Fork-Prozessen. Dies kann auf schwachbrüstiger Hardware zu Speicherengpässen führen. Ab FHEM Revision 11917 lassen sich die maximal parallel laufenden BlockingCalls durch das globale Attribut <code>blockingCallMax</code> begrenzen (Standardwert: ''unbegrenzt''). Sofern die maximale parallele Anzahl an BlockingCalls erreicht ist, werden weitere Calls in eine Warteschlange eingereiht und ausgeführt, sobald laufende Calls beendet werden. | ||
= Blocking.pm für Modulentwickler = | = Blocking.pm für Modulentwickler = | ||
Blocking.pm ist aktuell sowohl für Enduser, als auch für Modulentwickler gedacht. Die Benutzung in einem Modul birgt allerdings einige Stolperfallen auf die hier näher eingegangen wird. | Blocking.pm ist aktuell sowohl für Enduser, als auch für Modulentwickler gedacht. Die Benutzung in einem Modul birgt allerdings einige Stolperfallen, auf die hier näher eingegangen wird. | ||
== return-Wert von BlockingCall immer in $hash abspeichern == | == return-Wert von BlockingCall immer in $hash abspeichern == | ||
Zeile 69: | Zeile 69: | ||
== Sicherstellen, dass immer nur ein BlockingCall gleichzeitig läuft == | == Sicherstellen, dass immer nur ein BlockingCall gleichzeitig läuft == | ||
Sobald ein BlockingCall läuft könnte man | Sobald ein BlockingCall läuft könnte man rein theoretisch direkt einen weiteren BlockingCall starten usw. Dadurch würden mehrer parallele Durchläufe nebenher laufen und könnten den FHEM-Server dadurch unnötig belasten oder sogar auch überlasten. Daher sollte man immer beim Start eines BlockingCall prüfen, ob bereits ein weiterer BlockingCall läuft oder bei mehreren parallelen Blocking Calls eine entsprechende Prüfung einführen um die maximale Anzahl an gleichzeitigen BlockingCalls innerhalb einer Definition zu begrenzen. | ||
<font color="grey">$hash->{helper}{RUNNING_PID} = BlockingCall(…)</font> unless(exists($hash->{helper}{RUNNING_PID})); | <font color="grey">$hash->{helper}{RUNNING_PID} = BlockingCall(…)</font> unless(exists($hash->{helper}{RUNNING_PID})); | ||
Dies funktioniert nur dann, wenn man in der finishFn (und später auch abortFn) $hash->{helper}{RUNNING_PID} wieder löscht. Somit ist immer sichergestellt, | Dies funktioniert nur dann, wenn man in der finishFn (und später auch abortFn) $hash->{helper}{RUNNING_PID} wieder löscht. Somit ist immer sichergestellt, dass nur ein BlockingCall zur gleichen Zeit läuft. Wenn gerade ein BlockingCall aktiv ist aufgrund eines Set-Befehls, könnte man eine Meldung ausgeben. | ||
<u>Beispiel:</u> | <u>Beispiel:</u> | ||
Zeile 149: | Zeile 149: | ||
So kann man z.B. den Rückgabestring "Wert1|Wert2|Wert3" verwenden und diesen anschließend mit split() wieder in ein Array verwandeln und dann via Index gezielt darauf zugreifen. | So kann man z.B. den Rückgabestring "Wert1|Wert2|Wert3" verwenden und diesen anschließend mit split() wieder in ein Array verwandeln und dann via Index gezielt darauf zugreifen. | ||
Eine weitere Möglichkeit auch kompliziertere Datenstrukturen | Eine weitere Möglichkeit auch kompliziertere Datenstrukturen zurückzugeben ist <code>JSON->new->encode()</code> (Perl-Modul JSON) oder <code>toJSON</code> (aus fhem.pl) zum serialisieren und <code>JSON->new->decode()</code> zum deserialisieren zu verwenden. Achtung: Man kann hierfür zwar auch <code>encode_json</code> bzw. <code>decode_json</code> aus dem Perl-Modul JSON verwenden, sollte dabei aber beachten, dass hierdurch auch das encoding geändert wird! Außerdem führen unerwartete Rückgaben (mit Ausnahme von der Serialisierung durch <code>toJSON</code>) ggf. dazu, dass FHEM komplett beendet wird; alle decoding- und encoding-Anweisungen aus dem JSON-Modul sollten daher unbedingt durch eine (Block-) <code>eval</code>-Anweisung geschützt werden: | ||
<syntaxhighlight lang="Perl"> | |||
my $decoded; | |||
if ( !eval { $decoded = JSON->new->decode($content) ; 1 } ) { | |||
Log3($hash->{NAME}, 1, "JSON decoding error in BlockingCall return: $@"); | |||
#weiterer Code für Fehlerbehandlung... | |||
return; | |||
} | |||
</syntaxhighlight> | |||
== Rückgabewert muss unbedingt den Namen des Devices enthalten, von dem der Call gestartet wurde == | == Rückgabewert muss unbedingt den Namen des Devices enthalten, von dem der Call gestartet wurde == | ||
Zeile 159: | Zeile 167: | ||
Daher ist es wichtig, den Devicenamen beim Start des Blockingcalls zu übergeben. | Daher ist es wichtig, den Devicenamen beim Start des Blockingcalls zu übergeben. | ||
== Verwenden einer UndefFn um laufende BlockingCalls zu beenden == | == Verwenden einer UndefFn und ShutdownFn um laufende BlockingCalls zu beenden == | ||
Wenn gerade ein BlockingCall läuft und man will FHEM herunterfahren oder neustarten kann das problematisch werden, da ja immer noch Unterprozesse laufen und so einen kompletten Stop verhindern können. | Wenn gerade ein BlockingCall läuft und man will FHEM herunterfahren oder neustarten kann das problematisch werden, da ja immer noch Unterprozesse laufen und so einen kompletten Stop verhindern können. | ||
Daher muss in der UndefFn des Moduls sichergestellt werden, dass ein laufender BlockingCall beendet wird. Dazu wird als Argument der Return-Hash von BlockingCall benötigt in dem unter anderem die PID des Unterprozesses enthalten ist. Die Funktion BlockingKill() erledigt dann den Rest. | Daher muss in der [[DevelopmentModuleIntro#X_Undef|UndefFn]] und [[DevelopmentModuleIntro#X_Shutdown|ShutdownFn]] des Moduls sichergestellt werden, dass ein laufender BlockingCall beendet wird. Dazu wird als Argument der Return-Hash von BlockingCall benötigt in dem unter anderem die PID des Unterprozesses enthalten ist. Die Funktion BlockingKill() erledigt dann den Rest. | ||
<u>Beispiel</u> | <u>Beispiel</u> | ||
Zeile 178: | Zeile 186: | ||
return undef; | return undef; | ||
} | } | ||
= Anzeige der laufenden Blocking Calls = | |||
Mit dem FHEM-Befehl <code>blockinginfo</code> lassen sich die laufenden [[Blocking Call|Blocking Calls]] anzeigen. | |||
= Begrenzen der Blocking Calls = | |||
Mit dem globalen [[Attribute|Attribut]] <code>blockingCallMax</code> können die gleichzeitig laufenden [[Blocking Call|Blocking Calls]] begrenzt werden. | |||
= Module, die Blocking.pm verwenden = | = Module, die Blocking.pm verwenden = | ||
Zeile 198: | Zeile 214: | ||
* '''73_MPD.pm''' | * '''73_MPD.pm''' | ||
* '''73_PRESENCE.pm''' (siehe dazu [[Anwesenheitserkennung]]) | * '''73_PRESENCE.pm''' (siehe dazu [[Anwesenheitserkennung]]) | ||
* '''76_SMAInverter.pm''' | |||
* '''93_DbLog.pm''' (nicht alle Funktionen) | |||
* '''93_DbRep.pm''' | * '''93_DbRep.pm''' | ||
* '''98_HMinfo.pm''' | * '''98_HMinfo.pm''' |
Aktuelle Version vom 18. Juli 2022, 09:29 Uhr
Dieser Artikel soll Hinweise und Best Practices im Umgang mit dem Modul Blocking.pm und den daraus bereitgestellten Funktionen bieten.
Allgemein
Das Modul Blocking.pm wurde von Rudolf König entwickelt, um in FHEM Funktionsaufrufe zu ermöglichen, die relativ viel Zeit in Anspruch nehmen und normalerweise FHEM damit zum Stillstand (für die Dauer der Ausführung der Funktion) bringen würde.
Um so etwas zu verhindern, kann man mit Hilfe von Blocking.pm eine Funktion über einen Fork des Hauptprozesses unabhängig davon abarbeiten und das Ergebnis dieser Funktion optional an den Hauptprozess übergeben.
Benutzung
Das Modul stellt primär die Funktion BlockingCall() zur Verfügung um eine Perl-Funktion "non-blocking" auszuführen.
BlockingCall($blockingFn, $arg, $finishFn, $timeout, $abortFn, $abortArg);
Argument | Optional | Beispiel | Beschreibung |
---|---|---|---|
$blockingFn |
nein | "speedtest_DoSpeedtest" | Der Name der Perlfunktion, die "non-blocking" ausgeführt werden soll (ohne Klammern, nur der reine Funktionsname). |
$arg |
nein | "DeviceName|Argument1|Argument2" | Das Funktionsargument, das der blockingFn übergeben werden soll. |
$finishFn |
ja | "speedtest_SpeedtestDone" | Die Funktion, die mit dem Funktionsergebnis der blockingFn als Parameter aufgerufen werden soll, sobald diese zuende ist. Diese Funktion wird dabei mit dem Returnwert als erstes Argument aufgerufen. |
$timeout |
ja | 120 | Sofern eine finishFn verwendet wird, kann ein optionales Timeout gesetzt werden, sobald der Aufruf abgebrochen wird, sobald dieser timeout in Sekunden verstrichen ist. |
$abortFn |
ja | speedtest_SpeedtestAborted | Wenn der Aufruf aufgrund eines überschrittenen Timeouts abgebrochen wird, so wird die abortFn aufgerufen, sofern eine definiert ist. |
$abortArg |
ja | $hash | Im Falle eines Abruchs soll die abortFn mit diesem Argument aufgerufen werden. (Dient primär der Zuordnung eines solchen Abbruchs für Modulentwickler. |
Die Funktion BlockingCall() gibt bei Erfolg eine Hashreferenz mit mehreren Items zurück. Dieser Hash ist folgendermaßen aufgebaut.
$hash->{pid} |
→ Die Prozess-Id, unter der dieser Aufruf abgearbeitet wird. |
$hash->{fn} |
→ Der Name der blockigen, der mit diesem Aufruf abgearbeitet wird. |
$hash->{finishFn} |
→ Der Name der finishFn, die diesen Aufruf nach Erfolg verarbeiten wird. |
$hash->{abortFn} |
→ Der Name der abortFn, die im Fehlerfall aufgerufen wird. |
$hash->{abortArg} |
→ Das Argument, das im Fehlerfall an die abortFn übergeben werden soll. |
Einschränkungen
Aktuell sind beim Einsatz von Blocking.pm folgende Einschränkungen zu beachten.
- Veränderungen an internen Variablen von FHEM (Device Hashes, Timers, usw.) werden innerhalb eines BlockingCalls durchgeführt und sind dort auch sichtbar, haben aber keinerlei Einfluss auf den eigentlichen FHEM Hauptprozess und alle Definitionen. Solche Veränderungen müssen an die finishFn delegiert werden (z.B. durch zusätzliche Rückgabewerte), da es sich um einen Fork-Prozess handelt, der sich nach Abschluss des BlockingCall selbst zerstört.
- Wenn viele Definitionen aktiv sind, welche BlockingCalls parallel starten kann es zu einem relativ hohen Memory-Footprint kommen aufgrund mehrfach laufenden Fork-Prozessen. Dies kann auf schwachbrüstiger Hardware zu Speicherengpässen führen. Ab FHEM Revision 11917 lassen sich die maximal parallel laufenden BlockingCalls durch das globale Attribut
blockingCallMax
begrenzen (Standardwert: unbegrenzt). Sofern die maximale parallele Anzahl an BlockingCalls erreicht ist, werden weitere Calls in eine Warteschlange eingereiht und ausgeführt, sobald laufende Calls beendet werden.
Blocking.pm für Modulentwickler
Blocking.pm ist aktuell sowohl für Enduser, als auch für Modulentwickler gedacht. Die Benutzung in einem Modul birgt allerdings einige Stolperfallen, auf die hier näher eingegangen wird.
return-Wert von BlockingCall immer in $hash abspeichern
Die Funktion BlockingCall() gibt als return-Wert einen Hash mit mehreren Informationen zurück. Dieser Ergebnis-Hash ist in mehrerer Hinsicht sehr wichtig für die Benutzung in einem Modul, dazu in den folgenden Punkten mehr.
Eine gute Möglichkeit ist es dieses Ergebnis unterhalb von $hash->{helper} zu platzieren. So sieht es der Enduser nicht, kann aber bei Bedarf via list-Befehl angezeigt werden.
Beispiel:
$hash->{helper}{RUNNING_PID} = BlockingCall( … );
Sicherstellen, dass immer nur ein BlockingCall gleichzeitig läuft
Sobald ein BlockingCall läuft könnte man rein theoretisch direkt einen weiteren BlockingCall starten usw. Dadurch würden mehrer parallele Durchläufe nebenher laufen und könnten den FHEM-Server dadurch unnötig belasten oder sogar auch überlasten. Daher sollte man immer beim Start eines BlockingCall prüfen, ob bereits ein weiterer BlockingCall läuft oder bei mehreren parallelen Blocking Calls eine entsprechende Prüfung einführen um die maximale Anzahl an gleichzeitigen BlockingCalls innerhalb einer Definition zu begrenzen.
$hash->{helper}{RUNNING_PID} = BlockingCall(…) unless(exists($hash->{helper}{RUNNING_PID}));
Dies funktioniert nur dann, wenn man in der finishFn (und später auch abortFn) $hash->{helper}{RUNNING_PID} wieder löscht. Somit ist immer sichergestellt, dass nur ein BlockingCall zur gleichen Zeit läuft. Wenn gerade ein BlockingCall aktiv ist aufgrund eines Set-Befehls, könnte man eine Meldung ausgeben.
Beispiel:
BlockingCall("speedtest_DoSpeedtest", $name."|".$server, "speedtest_SpeedtestDone", 120, …);
speedtest_SpeedtestDone($) { … delete($hash->{helper}{RUNNING_PID}); … }
Nutzung von abortFn und abortArg
Mal angenommen man führt einen BlockingCall aus und dieser wird innerhalb des gesetzten Timeouts von 5 Sekunden nicht durchgeführt. Dann erhält man kein Ergebnis via der gesetzten finishFn. Das kann dazu führen, das ein InternalTimer nicht mehr neu gestartet werden kann, der zyklisch diesen BlockingCall ausführen soll um Daten zu ermitteln oder einen Status.
Um einen solchen Abbruch aufgrund des erreichten Timeouts innerhalb eines Moduls zu erkennen, muss der BlockingCall mit einer abortFn und einem abortArg gestartet werden.
- abortFn - die Funktion die im Falle eines Abbruch des Aufrufs gestartet werden soll
- abortArg - das Argument mit der diese Funktion aufgerufen werden soll. Dies díent in einer Modulumgebung der Zuordnung des korrekten Devices bei mehreren Definitionen. Üblicherweise wird hierfür $hash verwendet.
BlockingCall("speedtest_DoSpeedtest", $name."|".$server,"speedtest_SpeedtestDone", 120, "speedtest_SpeedtestAborted", $hash);
sub speedtest_SpeedtestAborted($) { my ($hash) = @_; delete($hash->{helper}{RUNNING_PID}); Log3 $hash->{NAME}, 3, "BlockingCall for ".$hash->{NAME}." was aborted"; RemoveInternalTimer($hash); InternalTimer(gettimeofday()+10, …) # falls mit disable-Attribut gearbeitet wird, muss dieses hier geprüft werden }
BlockingCall muss als Argument eine Referenz auf das Ursprungs-Device enthalten
Um später die Ergebnisse korrekt zuordnen zu können, ist es wichtig das korrekte Device zu kennen. Normalerweise wird in FHEM dazu der $hash-Zeiger verwendet um auf das entsprechende Device zu verweisen. Als Argumente für die BlockingFn können Hashes, Zeiger und sonstiges verwendet werden. Wichtig dabei ist, dass man aus den Argumenten irgendwie den Device-Namen ableiten kann, da man diesen später in der finishFn über die Variable $defs{…} wieder in die $hash-Referenz umwandeln kann.
Beispiel
BlockingCall("speedtest_DoSpeedtest", $hash->{NAME}."|".$server,…);
sub speedtest_DoSpeedtest($) { my ($string) = @_; my ($name, $server) = split("\\|", $string); … return "$name|$speedarr[0]|$speedarr[1]|$speedarr[2]"; }
sub speedtest_SpeedtestDone($) { my ($string) = @_; return unless(defined($string)); my @a = split("\\|",$string); my $hash = $defs{$a[0]}; … }
Rückgabewerte des BlockingCalls nur als Einzeiler-String
Bei Funktionen die mit BlockingCall aufgerufen werden muss der Rückgabewert entweder eine Zahl, oder ein einzeiliger String sein, da dieser über den Telnetprompt als Perl-Befehl ( {finishFn("returnvalue")} ) zurückgegeben wird.
Wenn man mehrere separate Werte zurückgeben möchte, kann man diese entweder mit einem Trennzeichen (z.B. Pipe) versehen, oder bei einem Text mit Zeilenumbrüchen alles mit Base64 oder Hex encoden.
So kann man z.B. den Rückgabestring "Wert1|Wert2|Wert3" verwenden und diesen anschließend mit split() wieder in ein Array verwandeln und dann via Index gezielt darauf zugreifen.
Eine weitere Möglichkeit auch kompliziertere Datenstrukturen zurückzugeben ist JSON->new->encode()
(Perl-Modul JSON) oder toJSON
(aus fhem.pl) zum serialisieren und JSON->new->decode()
zum deserialisieren zu verwenden. Achtung: Man kann hierfür zwar auch encode_json
bzw. decode_json
aus dem Perl-Modul JSON verwenden, sollte dabei aber beachten, dass hierdurch auch das encoding geändert wird! Außerdem führen unerwartete Rückgaben (mit Ausnahme von der Serialisierung durch toJSON
) ggf. dazu, dass FHEM komplett beendet wird; alle decoding- und encoding-Anweisungen aus dem JSON-Modul sollten daher unbedingt durch eine (Block-) eval
-Anweisung geschützt werden:
my $decoded;
if ( !eval { $decoded = JSON->new->decode($content) ; 1 } ) {
Log3($hash->{NAME}, 1, "JSON decoding error in BlockingCall return: $@");
#weiterer Code für Fehlerbehandlung...
return;
}
Rückgabewert muss unbedingt den Namen des Devices enthalten, von dem der Call gestartet wurde
Um einen Rückgabewert dem korrekten Device zuordnen zu können muss im Rückgabewert der Funktion, die mit BlockingCall gestartet wird, der Device-Name enthalten sein, um damit die Referenz auf das richtige Device herstellen zu können.
z.B. als Returnstring "Devicenamen|Readingwert1|Readingwert2|…"
Daher ist es wichtig, den Devicenamen beim Start des Blockingcalls zu übergeben.
Verwenden einer UndefFn und ShutdownFn um laufende BlockingCalls zu beenden
Wenn gerade ein BlockingCall läuft und man will FHEM herunterfahren oder neustarten kann das problematisch werden, da ja immer noch Unterprozesse laufen und so einen kompletten Stop verhindern können.
Daher muss in der UndefFn und ShutdownFn des Moduls sichergestellt werden, dass ein laufender BlockingCall beendet wird. Dazu wird als Argument der Return-Hash von BlockingCall benötigt in dem unter anderem die PID des Unterprozesses enthalten ist. Die Funktion BlockingKill() erledigt dann den Rest.
Beispiel
sub speedtest_Undefine($$) { my ($hash, $arg) = @_; RemoveInternalTimer($hash); BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID})); return undef; }
Anzeige der laufenden Blocking Calls
Mit dem FHEM-Befehl blockinginfo
lassen sich die laufenden Blocking Calls anzeigen.
Begrenzen der Blocking Calls
Mit dem globalen Attribut blockingCallMax
können die gleichzeitig laufenden Blocking Calls begrenzt werden.
Module, die Blocking.pm verwenden
Folgende Module verwenden aktuell Blocking.pm um langwierige Funktionalitäten auszulagern:
- 10_pilight_ctrl.pm
- 23_KOSTALPIKO.pm
- 23_LUXTRONIK2.pm
- 38_JawboneUp.pm
- 42_SYSMON.pm
- 55_GDS.pm
- 59_OPENWEATHER.pm
- 59_PROPLANTA.pm
- 70_EFR.pm
- 70_JSONMETER.pm
- 70_Jabber.pm
- 70_SML.pm
- 72_FRITZBOX.pm
- 73_MPD.pm
- 73_PRESENCE.pm (siehe dazu Anwesenheitserkennung)
- 76_SMAInverter.pm
- 93_DbLog.pm (nicht alle Funktionen)
- 93_DbRep.pm
- 98_HMinfo.pm
- 98_Text2Speech.pm
- 98_UbiquitiMP.pm
- 98_WOL.pm
- 98_update.pm