Timerverwaltung - lib für parallele Funktionsaufrufe

Begonnen von Beta-User, 02 Mai 2021, 14:44:03

Vorheriges Thema - Nächstes Thema

Beta-User

Zitat von: herrmannj am 03 Mai 2021, 14:14:14
passt doch. Wenn er nicht mehr benötigt wird musst Du ihn halt "abräumen".
Soweit erkennbar, funktioniert das.

Zitat
Pass nur auf: klassisches mem leak:
$hash->{TIMER}->{$id}->{'hash'}->{TIMER}->{$id}->{'hash'}->{TIMER}->{$id}->{'hash'}->{TIMER}->{$id}->{'hash'} ...
Darüber muss ich wohl noch eine Weile nachdenken. Wenn es ein akutes Thema ist, ist das "schon immer" ein Problem; nach meinem bisherigen Verständnis wird aber nicht $hash komplett da reingeschrieben, sondern nur die Referenz auf $hash, was unschädlch sein sollte.

(Konkret: der Schnippsel ab hier:)
Zitat von: Beta-User am 03 Mai 2021, 11:32:46

sub setRegisteredInternalTimer {
[...]


Zitat von: CoolTux am 03 Mai 2021, 14:04:32
bei ASC habe ich das erkennen an den funcHash gebunden. Sowohl für Sunrise und Sunset Fahrten als auch die SelfDefense Fahrten habe ich eigene TimerHashs welche entsprechend an das Rolloobject gebunden werden.
Das ist dann dasselbe Prinzip, soweit erkennbar (der Modulcode ist für jemanden Ungeübten wie mich praktisch nicht mehr zu überblicken); nur dass eben der Hash woanders "aufgehoben" wird.

Zitat von: Thorsten Pferdekaemper am 03 Mai 2021, 13:46:59
Hi,
ich glaube, dass es ganz nett wäre, wenn wir Timer hätten, die in etwa so wie setTimeout / clearTimeout in JavaScript funktionieren.
Also in etwa so:

my $timerHandle = setTimer($timeStamp, \&myFunc, $arg1, $arg2, $arg3,...);

clearTimer($timerHandle);

Das kann man sich natürlich auch selbst basteln...
Gruß,
   Thorsten
Na ja, es ging grade drum, das nicht umbedingt immer wieder selbst basteln zu müssen (das geht ja "immer"), sondern eben um einen Weg, der (u.a. memory-leak-) sicher ist...

Für's erste täte es mir genau ein weiteres Argument bei InternalTimer, beliebig viele müssen es ja gar nicht sein, das macht es im Zweifel nur unnötig kompliziert...


Mit anonymen subs habe ich auch schon rumexperimentiert, ala (Pseudocode):
  InternalTimer($ts, sub _doTimer->($hash,'arg1'), $hash);
}

Das war mir aber zu suspekt für irgendwelche unangenehmen Nebenwirkungen und - soweit ich mich entsinne - kann man in fhemdebug timerList auch nicht mehr erkennen, wer für sowas verantwortlich ist.

Zitat von: rudolfkoenig am 03 Mai 2021, 14:36:44
Oder abwarten dass die Funktion aufgerufen wird, und dann nichts machen.
Ja, was aber voraussetzt, dass die Funktion noch gefunden wird zum Aufrufzeitpunkt. Ich meine damit mal "Probleme" gehabt zu haben, wenn die letzte Modulinstanz vor Timer-Ablauf gelöscht wurde. (Probleme = kompletter FHEM-Crash im Testsystem...)

Zitat
Ob was zu machen ist oder nicht, kann man im Hash ablegen.
Klar, sobald man eine "erweiterte Datenstrktur" übergibt (egal, ob Hash oder Array), kann man alles mögliche im Folgecode entscheiden, der muss nur wieder gefunden werden bzw. noch geladen sein (und mit den Argumenten umgehen können).

In Array statt Hash sehe ich jetzt für meinen Anwendungsfall keinen wirklichen Vorteil, oder übersehe ich was (v.a. in Bezug auf das meory leak-Thema)?
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

Thorsten Pferdekaemper

Zitat von: Beta-User am 03 Mai 2021, 14:48:20Wenn es ein akutes Thema ist, ist das "schon immer" ein Problem; nach meinem bisherigen Verständnis wird aber nicht $hash komplett da reingeschrieben, sondern nur die Referenz auf $hash, was unschädlch sein sollte.
$hash ist ja die Referenz, sonst wär's %hash...
Memory Leaks entstehen ja gerade dadurch, dass man zyklische Referenzen hat. Dann kann das der Garbage Collector nicht mehr wegräumen und es bleibt ewig stehen. Abhilfe schafft bleiben lassen oder
https://perldoc.perl.org/Scalar::Util#weaken

ZitatNa ja, es ging grade drum, das nicht umbedingt immer wieder selbst basteln zu müssen (das geht ja "immer"), sondern eben um einen Weg, der (u.a. memory-leak-) sicher ist...
Ganz meine Meinung...

Gruß,
   Thorsten
FUIP

rudolfkoenig

ZitatDas ist nicht ganz dasselbe. Es funktioniert nur, wenn die Argumente immer unterschiedlich sind.
Nope. Es funktioniert, da "my $timerHandle" immer unterschiedlich ist.

ZitatWenn es ein akutes Thema ist, ist das "schon immer" ein Problem;
Ich gehe davon aus, aus dem gleichen Grund muss man bei XML::Dom dispose explizit aufrufen.

ZitatJa, was aber voraussetzt, dass die Funktion noch gefunden wird zum Aufrufzeitpunkt.
Da sowohl Funktion, wie auch Parameter in der Timerverwaltung gemerkt wurden, sollte das kein Problem sein.
Ob die Funktion mit dem Argument noch was anfangen kann, ist eine andere Geschichte.

ZitatIn Array statt Hash sehe ich jetzt für meinen Anwendungsfall keinen wirklichen Vorteil,
Ich auch nicht, ich wollte die Unterschiede aber minimal halten.

Thorsten Pferdekaemper

Zitat von: rudolfkoenig am 03 Mai 2021, 15:11:29
Nope. Es funktioniert, da "my $timerHandle" immer unterschiedlich ist.
Ja, natürlich... (Gibt es eigentlich kein Hand-an-die-Stirn-Icon?) Es wird ja nachher die Referenz verglichen und nicht deren Inhalt.
Gruß,
   Thorsten
FUIP

Beta-User

Zitat von: rudolfkoenig am 03 Mai 2021, 15:11:29
Ich gehe davon aus, aus dem gleichen Grund muss man bei XML::Dom dispose explizit aufrufen.
Dann werde ich mir das bei Gelegenheit mal ansehen und auch auf eine "NAME:modifier"-Variante umstellen, die dann $hash wieder aus dem Namen ableitet (und ein return macht, wenn das nicht klappt; eine RenameFn muss dann wohl auch her...).
(Oder hatte ich den Hinweis jetzt falsch verstanden?)
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

Zitat(Oder hatte ich den Hinweis jetzt falsch verstanden?)
Nein.
Aber ueblicherweise wird beim TimerAufruf oder Timer Loeschen im globalen Hash der Zeiger auf dem TimerHash entfernt, und damit die Endloskette aufgebrochen. Ein Speicherloch existiert nur, wenn zwei Hashes sich gegenseitig merken, und das Modul keinen Zeiger auf einen der beiden mehr hat.

Beta-User

Ok, dann war es vermutlich aus diesem Grund bisher kein (offensichtliches) Problem.

Gibt es Ideen für eine RenameFn?
Wenn $arg vorher "$old_name:$modifier" war, dann muss das Argument neu dann "$new_name:$modifier" sein.
(Ok, dann beantworte mir die Frage gleich selbst: es muss dann eben $time unter dem alten Namen im "TIMER"-Hash gespeichert gewesen sein, dann sollte das klappen, die alten einfach zu löschen und unter neuem Namen anzulegen).
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

Beta-User

#22
Zitat von: Beta-User am 03 Mai 2021, 16:23:23
es muss dann eben $time unter dem alten Namen im "TIMER"-Hash gespeichert gewesen sein, dann sollte das klappen, die alten einfach zu löschen und unter neuem Namen anzulegen).
Was ggf. noch fehlt, ist die aufzurufende Funktion...

Anbei dann mal ein erster Versuch, das in eine lib zu packen, Doku wäre wohl noch zu ergänzen (wo und wie?)

Eine RenameFn könnte dann so aussehen:
sub Rename {
    my $new_name = shift // return;
    my $old_name = shift // return;

    my $hash = $defs{$new_name} // return;
    return renameAllRegIntTimer($hash, $new_name, $old_name);
}


Eine aufzurufende Funktion könnte sich so aus dem Sumpf ziehen:
sub Twilight_fireEvent {
    my $arg = shift // return;

    my ( $name,$modifier ) = split m{:}xms, $arg;
    my $hash = $defs{$name} // return;
[...]


Meinungen dazu?
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

Ich spare mir den Umbennenungsaufwand, indem ich nicht "Name", sondern $defs{Name} merke.
Rename aedert den $hash->{NAME}, und der Rest bleibt gleich.

Thorsten Pferdekaemper

Zitat von: Beta-User am 03 Mai 2021, 15:30:10
Dann werde ich mir das bei Gelegenheit mal ansehen und auch auf eine "NAME:modifier"-Variante umstellen, die dann $hash wieder aus dem Namen ableitet (und ein return macht, wenn das nicht klappt; eine RenameFn muss dann wohl auch her...).
(Oder hatte ich den Hinweis jetzt falsch verstanden?)
Falls das die Maßnahme gegen das Memory-Leak ist, dann wären ggf. Weak References besser. Wie schon angemerkt:
https://perldoc.perl.org/Scalar::Util#weaken
Meiner Meinung nach ist das das "Mittel der Wahl" für zyklische Referenzen.
Gruß,
   Thorsten
FUIP

Beta-User

Irgendwie habe ich bei den beiden letztgenannten Hinweisen noch  gedankliche Hänger, vermutlich weil mein Verständnis dessen, was eine Referenz ist irgendwie noch unvollständig ist:
Zitat von: rudolfkoenig am 03 Mai 2021, 18:28:09
Ich spare mir den Umbennenungsaufwand, indem ich nicht "Name", sondern $defs{Name} merke.
Rename aedert den $hash->{NAME}, und der Rest bleibt gleich.
Da fehlt mir das konkrete Anwendungsbeispiel.
Wenn ich in die von herrmannj vorgeschlagene HASH-Type Variante nehme, also
my $p = {
  'instance' => $hash,
  'foo' => 'bar',
  'id' => $id++,
...
InternalTimer($ts, \&_doTimer, $p);
}


ändere zu
my $p = {
  'instance' => $defs{'instanceName'},
  'foo' => 'bar',
  'id' => $id++,
...
InternalTimer($ts, \&_doTimer, $p);
}

sollte doch eigentlich dasselbe drinstehen, oder? Irgendwie war ich bisher davon ausgegangen, dass $hash (einer FHEM-Device-Instanz) in der FHEM-üblichen Verwendung) dasselbe sei wie $defs{'instanceName'}.
Das scheint aber irgendwie nicht der Fall zu sein.
Oder war es so gemeint, dass der "Text" (z.B.) "HASH(3732AB)" anstatt des Namens verwendet werden soll und dann hinterher einfach quasi automatisch wieder als zur Referenz auf den Hash "erstarkter" Variableninhalt genutzt werden kann :o ?

Zitat von: Thorsten Pferdekaemper am 03 Mai 2021, 19:21:05
Falls das die Maßnahme gegen das Memory-Leak ist, dann wären ggf. Weak References besser. Wie schon angemerkt:
https://perldoc.perl.org/Scalar::Util#weaken
Meiner Meinung nach ist das das "Mittel der Wahl" für zyklische Referenzen.
Gruß,
   Thorsten


Demnach erzeugt das "verhashen" einer Variable keine Kopie, sondern eine Referenz und das hier würde einen memory-leak vermeiden, ohne, dass  die Funktionalität leidet:

my $instance = $hash;
weaken($instance);
my $p = {
  'instance' => $instance,
  'foo' => 'bar',
  'id' => $id++,
...
InternalTimer($ts, \&_doTimer, $p);
}

Oder habe ich auch da mal wieder was missverstanden oder übersehen...?
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

Zitatsollte doch eigentlich dasselbe drinstehen, oder?
Steht ja auch, und eine Aenderung wegen rename ist nicht notwendig.

Wg. weaken: in dem gezeigten Beispiel sehe ich keine zirkulaere Referenz, und keine Notwendigkeit von weaken.
Es waere was Anderes wenn:
- $p _auch_ in $hash eingetragen wird
- beim Aufruf von _doTimer (oder RemoveInternalTimer) $p aus $hash (und umgekehrt $hash aus $p) nicht ausgetragen wird
- $hash spaeter entfernt wird, wieder ohne explizites Entfernen des $p Eintrags.

Anders formuliert:
- perl hat kein Garbage Collection, sondern arbeitet mit Referenz-Zaehler.
- wenn etwas verwendet wird (im hash, Funktionsaufruf, etc), wird der Zaehler erhoeht, und wenn es aus dem Hash entfernt, Ende des Blocks kommt, etc, wird der Zaehler dekrementiert.
- wenn der Zaehler auf 0 dekrementiert wird, dann wird der Speicher freigegeben, und bei allen Unterobjekten der Zaehler dekrementiert.
- bei zirkulaeren Referenzen wird der Zaehler nie auf 0 kommen, deswegen wird es nie freigegeben (ohne kuenstliche Sachen wie weaken, was wiederum ziemlich sicher andere Nebeneffekte hat, sonst waere es nicht optional).

Thorsten Pferdekaemper

Zitat von: Beta-User am 04 Mai 2021, 08:35:17
und das hier würde einen memory-leak vermeiden, ohne, dass  die Funktionalität leidet:

my $instance = $hash;
weaken($instance);
my $p = {
  'instance' => $instance,
  'foo' => 'bar',
  'id' => $id++,
...
InternalTimer($ts, \&_doTimer, $p);
}

Nicht ganz. Wenn man eine schwache Referenz einer normalen zuweist, dann wird daraus wieder eine starke. D.h. $p->{instance} ist eine normale Referenz.
So geht es richtig:

my $p = {
  'instance' => $hash,
  'foo' => 'bar',
  'id' => $id++,
...
weaken($p->{instance});
InternalTimer($ts, \&_doTimer, $p);
}

Zusätzlich muss man noch bei der Verwendung abfragen, ob das Teil noch da ist. Bei normalen Referenzen kann man sich darauf verlassen, dass $p->{instance} definiert ist. Bei schwachen Referenzen kann es passieren, dass $p->{instance} "undefiniert" wird. Das passiert, wenn ansonsten keine Referenzen mehr darauf zeigen.
D.h. bei Verwendung muss man immer...

if(defined($p->{instance})) {
...
}


Zitat von: rudolfkoenig am 04 Mai 2021, 09:22:50
Wg. weaken: in dem gezeigten Beispiel sehe ich keine zirkulaere Referenz, und keine Notwendigkeit von weaken.
Ich war davon ausgegangen, dass irgendwo sowas steht wie

$hash->{TIMER} = $p;


Zitat
- perl hat kein Garbage Collection, sondern arbeitet mit Referenz-Zaehler.
Ich dachte "Garbage Collection" ist der Oberbegriff. Die Müllabfuhr kommt halt dann, wenn man sie bestellt (Reference Counting) und nicht regelmäßig überallhin (Mark and Sweep).

Zitat
(ohne kuenstliche Sachen wie weaken, was wiederum ziemlich sicher andere Nebeneffekte hat, sonst waere es nicht optional).
Wie meinst Du das? Man kann es ja schlecht nicht-optional machen. Dann würde nämlich keine Referenz jemals was zählen und alles würde sofort wieder weggeworfen werden. Normalerweise will man das ja nicht.
Das Reference Counting in Perl scheint ja so implementiert zu sein, dass sofort alles freigegeben bzw. schwache Referenzen sofort auf undef gesetzt werden. Dadurch merkt man wahrscheinlich relativ schnell, wenn was falsch läuft. Bei Mark and Sweep können schwache Referenzen natürlich lustig werden.

Gruß,
   Thorsten
FUIP

rudolfkoenig

ZitatMan kann es ja schlecht nicht-optional machen. Dann würde nämlich keine Referenz jemals was zählen und alles würde sofort wieder weggeworfen werden. Normalerweise will man das ja nicht.
Genau das meinte ich. Man sollte es nicht ohne Nachdenken verwenden.

Thorsten Pferdekaemper

FUIP