[gelöst] Perl Alternative zu Fhem-Sleep/InternalTimer welche anonyme Sub erlaubt

Begonnen von Thyraz, 09 Februar 2018, 14:25:39

Vorheriges Thema - Nächstes Thema

Thyraz

Hallo zusammen,

nachdem mir das Thema während der letzten Monate immer wieder aufgestoßen ist,
schreib ich doch mal einen Erweiterungswunsch dazu:

Mich stört, dass ich in Perl Funktionen oder Modulen zum Verzögern von Befehlen immer auf InternalTimer zurückgreifen muss.
Genauer gesagt, dass man bei InternalTimer einen Funktionsname statt einen Zeiger oder alternativ eine anonyme Sub übergibt.

Dadurch müssen die verzögerten Befehle in eine extra Funktion ausgelagert werden, was bei Ein- bis Zweizeilern den Code unnötig fragmentiert.
Z.B. Aufruf des ganzen in einem Notify, Verzögerter Code in einer Sub in einer MyUtils.
Wenn man asynchrone Verarbeitung aus anderen Sprachen mit verzögerten Inline-Blöcken/-Closures kennt, weckt das eben Begehren. ;)
In Notifies kann man ggf. noch auf sowas ausweichen:

fhem("sleep 5;{Log 1, 'Test'}");

Spätestens in Modulen fühlt sich das aber "dirty" an.
Auch fehlt dann ein korrektes Syntaxhighlighting.

Ein Beispiel für eine ähnliche Problemstellung wurde in HttpUtils bei den nonblocking Funtkionen bereits hervorragend umgesetzt.
Man kann den Callback entweder als Zeiger auf eine extra Funktion (bei viel Code) angegeben, oder eben eine anonyme Sub direkt Inline schreiben:


HttpUtils_NonblockingGet({
url         => "bla",
hash        => $hash,
callback    => sub() {
Log 1, "Ich bin ein Inline-Callback;
}
});


Ich träume jetzt von etwas Ähnlichem für NoneBlocking Sleeps nach dem Schema:

delay(5, sub() {
  Log 1, "Ich bin ein 5 Sekunden verzögerter Befehl;
}


Gäbe es weitere Interessenten an so etwas?
Und wäre es überhaupt Möglich das ohne extreme Änderungen an den Innereien von Fhem?
Weiß nicht wie die Timer und ihre Callbacks intern gespeichert werden und ob es einen Grund gibt, warum das bei HTTPUtils keine Problem machen könnte,
bei Internen Timern hingegen schon.
Fhem und MariaDB auf NUC6i5SYH in Proxmox Container (Ubuntu)
Zwave, Conbee II, Hue, Harmony, Solo4k, LaMetric, Echo, Sonos, Roborock S5, Nuki, Prusa Mini, Doorbird, ...

herrmannj


Thyraz

Hm.. irgendwie schon, irgendwie "jein". ;)

Die "at"s landen dann ja als Modulinstanzen sichtbar in Fhem.
Soll sowas wirklich im Quellcode eines offiziellen Fhem Modul stehen?  :o

Könnte mir vorstellen, die Begeisterung der User wäre verhalten, wenn da andauernd "at"s auftauchen und verschwinden.

Das FHEM Sleep wird ja nicht umsonst gern als unbenanntes at bezeichnet.
Fhem und MariaDB auf NUC6i5SYH in Proxmox Container (Ubuntu)
Zwave, Conbee II, Hue, Harmony, Solo4k, LaMetric, Echo, Sonos, Roborock S5, Nuki, Prusa Mini, Doorbird, ...

CoolTux

Wenn es Dir um die Anwendung in einem Modul geht und da um Verzögerung, dann erzähl Mal was Du genau machen willst. Es gibt viele Wege und man muss ja nicht unbedingt verzögern.
Du kannst mit NotifyFn arbeiten oder auch mit einer Queue.
Kommt halt immer auf die Anforderung an.
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

Thorsten Pferdekaemper

Zitat von: Thyraz am 09 Februar 2018, 14:25:39Genauer gesagt, dass man bei InternalTimer einen Funktionsname statt einen Zeiger oder alternativ eine anonyme Sub übergibt.
Hast Du das ausprobiert? Ich übergebe zumindest manchmal Funktionszeiger und das geht. Ich würde vermuten, dass das auch mit anonymen Subs funktioniert.
Wenn man sich mal das Coding in fhem.pl anschaut, dann sehe ich nicht, warum das nicht gehen sollte.
Gruß,
   Thorsten
FUIP

Thyraz

Nein, habe ich tatsächlich nicht...

Da es in der Doku "Der Name der Funktion als Zeichenkette" heißt,
bin ich nicht auf die Idee gekommen, dass dies funktionieren könnte.

Es geht aber tatsächlich.
Und wir bekommen als nette Beigabe die Fähigkeit von Closures, dass sie die Variablen aus ihrem umgebenden Scope an sich binden. :)


my $text = "This text is declared before the delay";

InternalTimer( gettimeofday() + 5, sub() {
    Log 1, "$text";
}, undef);


Man hat ja meist mehrere Variablen die man dem InternalTimer übergeben muss, wofür es den arg Parameter gibt.
Da man aber nur einen Parameter übergeben kann, muss man oft erstmal eine Hash-Ref erstellen.
Das ist dann ja auch wieder zusätzlicher Code.

Kann man sich dank Closure sparen und direkt auf die umgebenden Variablen zugreifen.

Danke für die Rückmeldungen, jetzt muss ich hier erstmal ein wenig Code Refactoring betreiben.  :P
Fhem und MariaDB auf NUC6i5SYH in Proxmox Container (Ubuntu)
Zwave, Conbee II, Hue, Harmony, Solo4k, LaMetric, Echo, Sonos, Roborock S5, Nuki, Prusa Mini, Doorbird, ...

Thorsten Pferdekaemper

Hi,
so etwas in der Art müsste auch gehen:

InternalTimer( gettimeofday() + 5, sub() {
    my ($arg1,$arg2,$arg3) = @{$_[0]};
    # mach was mit $arg1 etc.
}, ["abc",42,6*9]);

Gruß,
   Thorsten
FUIP

Thyraz

Klaro, ob Hash-Ref oder Array ist ja erstmal egal.

Aber du hast mehr Tipparbeit durch die Wertübergabe und musst die Werte in der Funktion ja auch wieder aufdröseln.
Ist bei einer Closure unnötig, da du direkt auf die äußeren Variablen zugreifen kannst.

Die Zuweisung der Variable $text in meinem Code war ja nur beispielhaft.
Normal geht es ja um den Zugriff auf bereits bestehende Variablen des äußeren Contexts die dort sowieso bestehen.

Der Code beschränkt sich also auf

InternalTimer( gettimeofday() + 5, sub() {
    Log 1, "$textFromOuterScope $anotherVarFromOutside ...";
}, undef);


Was minimalistischer und einfacher zu lesen ist.
Fhem und MariaDB auf NUC6i5SYH in Proxmox Container (Ubuntu)
Zwave, Conbee II, Hue, Harmony, Solo4k, LaMetric, Echo, Sonos, Roborock S5, Nuki, Prusa Mini, Doorbird, ...

herrmannj

Zitat von: Thyraz am 09 Februar 2018, 22:04:20
Und wir bekommen als nette Beigabe die Fähigkeit von Closures, dass sie die Variablen aus ihrem umgebenden Scope an sich binden. :)
Achtung! Ideale Quelle für memory leaks

Solange der Timer läuft bleiben die vars im (anonymen) scope. Wenn Du jetzt (direkt oder indirekt) "von innen nach aussen" referenzierst bildet sich eine loop und die Speicher bleibt auf immer und ewig gebunden. Bei zyklischen timern dann immer wieder.

ToKa

Hallo zusammen,

habe jetzt InternalTimer so wie hier beschrieben in meiner myUtils im Einsatz. Das funktioniert wunderbar.

Da ich diese interne Funktion bislang nicht kannte, bin ich mir jetzt aber unsicher, ob man jeden Timer - auch wenn er abgelaufen ist - durch ein RemoveInternalTimer wieder löschen muss?

Beste Grüße
Torsten
RaspberryPi3 mit RaZberry2 und Conbee II
Fibaro: FGWPE/F-101 Switch & FIBARO System FGWPE/F Wall Plug Gen5, FGSD002 Smoke Sensor
EUROtronic: SPIRIT Wall Radiator Thermostat Valve Control
Shelly2.5 Rollladenaktoren
Zipato Bulb 2, Osram und InnrLight

CoolTux

Nein muss man nicht. Wenn er abgelaufen ist wird er auch entfernt.
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

ToKa

RaspberryPi3 mit RaZberry2 und Conbee II
Fibaro: FGWPE/F-101 Switch & FIBARO System FGWPE/F Wall Plug Gen5, FGSD002 Smoke Sensor
EUROtronic: SPIRIT Wall Radiator Thermostat Valve Control
Shelly2.5 Rollladenaktoren
Zipato Bulb 2, Osram und InnrLight

Thyraz


my $text = "This text is declared before the delay";

InternalTimer( gettimeofday() + 5, sub() {
    Log 1, "$text";
}, undef);


Zitat von: herrmannj am 09 Februar 2018, 23:00:06
Achtung! Ideale Quelle für memory leaks

Solange der Timer läuft bleiben die vars im (anonymen) scope. Wenn Du jetzt (direkt oder indirekt) "von innen nach aussen" referenzierst bildet sich eine loop und die Speicher bleibt auf immer und ewig gebunden. Bei zyklischen timern dann immer wieder.

Ich muss mal diese Leiche fleddern, da ich sowas Ähnlich heute wieder eingebaut habe und mich an diesen Thread erinnert habe.
Kann mir jemand erklären warum das genau bei Perl ein Problem ist, also was da intern genau passiert?

Mit ist klar, dass die anonyme Sub / Closure die Variable text bindet, also der RetainCount wahrscheinlich erhöht wird, oder was da auch immer genau an Implementierungsdetails passiert.

Wenn ich das ganze als Übergabeparameter machen würde statt auf die Variable außerhalb zu binden,
würde der Wert ja aber auch im Speicher gehalten werden müssen.
Soweit von der Speicherbelastung ja erstmal kein großer Unterschied.

Aber wie kommt es zum Memory Leak?
Wenn der InternalTimer abgelaufen ist, und die Sub ausgeführt, wird diese doch sicher von InternalTimer wieder freigegeben, oder?
Und wenn die Sub von keinem mehr gehalten wird, dürfte sie sich in Luft auflösen und der RetainCount von $text müsste sich doch auch wieder verringern, wodurch $text von der GarbageCollection aufgeräumt werden kann.

$text hält seinerseits ja wahrscheinlich keinen Verweis auf die anonyme Sub, oder?
Sonst wären Closures ja immer ein Memory Leak durch Circular References.

Fhem und MariaDB auf NUC6i5SYH in Proxmox Container (Ubuntu)
Zwave, Conbee II, Hue, Harmony, Solo4k, LaMetric, Echo, Sonos, Roborock S5, Nuki, Prusa Mini, Doorbird, ...

herrmannj

kommt nicht nicht per se zum leak, kann aber eben leicht passieren. Im Beispiel ist alles ok. Meistens macht so eine sub ja viel mehr.

Thyraz

Fhem und MariaDB auf NUC6i5SYH in Proxmox Container (Ubuntu)
Zwave, Conbee II, Hue, Harmony, Solo4k, LaMetric, Echo, Sonos, Roborock S5, Nuki, Prusa Mini, Doorbird, ...