Modul zur Standardisierung nebenläufiger Prozesse

Begonnen von Dr. Boris Neubert, 24 Februar 2015, 20:40:41

Vorheriges Thema - Nächstes Thema

Dr. Boris Neubert

Hallo,

wir sehen uns häufig der Anforderung ausgesetzt, länger laufende Anfragen oder Event-Schleifen in FHEM zu programmieren. Ein Beispiel findet sich in 10_OWServer.pm. Um diese Aufgabe standardisiert zu unterstützen, habe ich ein Modul SubProcess.pm entwickelt.


  my $subprocess= SubProcess->new( { onRun => \&onRun, onExit => \&onExit } );
  my $pid= $subprocess->run();

  $subprocess->wait();
  $subprocess->terminate();
  $subprocess->kill();


Im Unterschied zu BlockingCall ist das Modul leichtgewichtig und kommuniziert über Pipes oder SocketPairs in transparenter Form mit dem FHEM-Hauptprozess, da es sich wie ein externes Gerät verhält, das Informationen die select-Hauptschleife in fhem.pl einschleust. Erreicht wird dies durch die Strophe


  $hash->{FD}= $subprocess->child();
  delete($readyfnlist{"$name.$pid"});   
  $selectlist{"$name.$pid"}= $hash;


in dem Modul, welches den Subprozess losgetreten hat.

Und nun macht sich Ernüchterung breit! Sowohl mit autoflush-Pipes, forciertem flush, $|++ oder anderen Verrenkungen sowie dem ganzen statt mit Pipes mit Socketpair kommen im FHEM-Hauptprozess die Daten nur in Bündeln an, wenn von einem anderen Filedescriptor gelesen wird, oder, bei Socketpair, interessanterweise genau jede Minute ein Wert (ich schreibe 100 Werte im 3-Sekundentakt, die im Minutentakt wieder aus der Hauptschleife herauspurzeln).

Wer kann helfen?

Ich habe das Modul sowie ein Testmodul beigefügt. Definiert wird ein Testgerät mittels

define T SubProcessTester

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

justme1968

#1
hallo boris,

vielleicht habe ich etwas falsch verstanden... nach dem define T SubProcessTestersollte doch der parent prozess nicht blockieren oder ?

das tut er bei mir. bzw es wird kein child gestartet. ich sehe im log nur die Step... nachrichten, habe aber keinen zusätzlichen fhem prozess. nach den 100 steps beendet sich fhem komplett.

worauf bezieht sich die aussage das SubProcess leichtgewichtiger ist als BlockingCall?

zum autoflush: ich meine es muss autoflush(1) heissen.

gruss
  andre

ps: ist es absicht das du das freigeben der filedescriptoren im child nicht aus BlockigCall übernommen hast? ohne wird es die probleme neustart und co. geben.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Dr. Boris Neubert

Hallo Andre,

Zitat von: justme1968 am 24 Februar 2015, 20:58:31
vielleicht habe ich etwas falsch verstanden... nach dem define T SubProcessTestersollte doch der parent prozess nicht blockieren oder ?

das tut er bei mir. bzw es wird kein child gestartet. ich sehe im log nur die Step... nachrichten, habe aber keinen zusätzlichen fhem prozess. nach den 100 steps beendet sich fhem komplett.

Interessant. Verstehe ich nicht, warum er bei Dir nicht forkt sondern onRun() im Parent-Prozess ausführt. Bist Du sicher?

Bei mir (Perl 5.18.2 auf KUbuntu 14.04) sehe ich


neubert   6579  6575  0 17702 25376   1 21:01 pts/6    00:00:00 /usr/bin/perl /users/neubert/Development/Perl/fhem-code/fhem/fhem.pl /users/neubert/Development/Perl/fhem-data/fhem.conf
neubert   6581  6579  0 12705 13072   1 21:01 pts/6    00:00:00 /usr/bin/perl /users/neubert/Development/Perl/fhem-code/fhem/fhem.pl /users/neubert/Development/Perl/fhem-data/fhem.conf


Im FHEM-Log steht


...
2015.02.24 21:15:10 5: Cmd: >define T SubProcessTester<
2015.02.24 21:15:10 5: Loading /users/neubert/Development/Perl/fhem-code/fhem/FHEM/98_SubProcessTester.pm
2015.02.24 21:15:10 5: SubProcess 6734 created.
2015.02.24 21:15:10 1: RUN RUN RUN RUN...
2015.02.24 21:15:10 1: Step 0
...
2015.02.24 21:15:10 3: Converting 'attr global port 7072 global' to 'define telnetPort telnet 7072 global'
2015.02.24 21:15:10 5: Loading /users/neubert/Development/Perl/fhem-code/fhem/FHEM/98_telnet.pm
2015.02.24 21:15:10 3: telnetPort: port 7072 opened
2015.02.24 21:15:10 5: Triggering global (1 changes)
2015.02.24 21:15:10 5: Notify loop for global INITIALIZED
...
2015.02.24 21:15:10 0: Server started with 5 defined entities (version $Id: fhem.pl 8066 2015-02-22 13:33:26Z rudolfkoenig $, os linux, user neubert, pid 6733)
2015.02.24 21:15:13 1: Step 1
2015.02.24 21:15:16 1: Step 2
2015.02.24 21:15:19 1: Step 3
2015.02.24 21:15:22 1: Step 4
2015.02.24 21:15:25 1: Step 5
2015.02.24 21:15:28 1: Step 6
2015.02.24 21:15:31 1: Step 7
2015.02.24 21:15:34 1: Step 8
2015.02.24 21:15:37 1: Step 9
...
2015.02.24 21:16:07 1: Step 19
2015.02.24 21:16:09 1: DEBUG>T has data to read!
2015.02.24 21:16:09 5: T read: 0

2015.02.24 21:16:10 1: Step 20
2015.02.24 21:16:13 1: Step 21
...
2015.02.24 21:20:04 1: Step 98
2015.02.24 21:20:07 1: Step 99
2015.02.24 21:20:09 1: DEBUG>T has data to read!
2015.02.24 21:20:09 5: T read: 4

2015.02.24 21:20:10 1: EXITED!
2015.02.24 21:21:09 1: DEBUG>T has data to read!
2015.02.24 21:21:09 5: T read: 5
...


Die Funktion onRun() schreibt munter Zeile für Zeile "Step xxx" ins Log. Und ab und an kommt bei mir "DEBUG>T has data to read!" gefolgt von "T: read: xxx".

In 300 Sekunden ist das bis 100 gelaufen, während im Minutentakt die DEBUG-Meldungen weiter kommen. Woher kommt denn bitte der Minutentakt??

Ich kann mich die ganze Zeit per telnet localhost 7072 mit dem Hauptprozess verbinden und FHEM ganz normal bedienen.

Zitat
worauf bezieht sich die aussage das SubProcess leichtgewichtiger ist als BlockingCall?

BlockingCall nutzt den Telnet-Port zur Kommunikation, was etwa die Hälfte des Codes ausmacht. 

shutdown restart sowie selbständiges und forciertes Beenden funktionieren ohne weiteres Zutun bei subprocess reibungslos, ein $subprocess->wait() zur rechten Zeit räumt auch Zombies weg.

Warum ich das so machen will? Ich will in einem nebenläufigen Prozess in langlaufenden Operationen meinen Rechnerpark überwachen sowie mittels des EventListeners vom VirtualBox-API meinen virtuellen Maschinen lauschen. Dazu möchte ich nicht mehr wie derzeit noch einen extra Dämon starten, zu dem sich FHEM verbindet.

Zitat
zum autoflush: ich meine es muss autoflush(1) heissen.

Hilft leider auch nicht.  :(

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

justme1968

hallo boris,

das einzige das ich im log habe ist das hier:2015.02.24 23:38:50 1: [21532] RUN RUN RUN RUN...
2015.02.24 23:38:50 1: [21532] Step 0
2015.02.24 23:38:53 1: [21532] Step 1
2015.02.24 23:38:56 1: [21532] Step 2


die pid ist die des geforkten child. der parent hat sich genau dann schon beendet. alle konnections sind wegen den fehlenden close auf den child prozess übergegangen und der blockiert.

ich habe gerade versucht rauszufinden wo und warum der parent prozess bei mir nicht mehr existiert. aber ich habe absolut noch keine idee.

das fehlverhalten zeigt aber zufällig genau das problem mit den fehlenden close für alle deskriptoren. das funktioniert nur so lange wie im parent alles glatt läuft. wenn der parent aus irgendeinem grund abstürzt wird das im child nicht bemerkt und ein neu starten schlägt so lange fehl wie der child prozess nicht beendet wird. das wait im parent hat hier keine auswirkungen.

gruss
  andre
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Dr. Boris Neubert

Hallo Andre,

auf welcher Plattform mit welchem Kernel und Perl passiert das bei Dir?

Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

justme1968

mac os x 10.10.2 und das mit ausgelieferte perl 5.18

gruss
  andre
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Dr. Boris Neubert

Hallo Andre,

unter MacOS X kann ich leider bei mir nicht testen.

Geht denn bei Dir überhaupt fork()? Was sagt strace (gibt's das unter MacOS?)?

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

justme1968

ja. fork geht natürlich. meine version der gleichen idee (forken und kommunikation per socketpair) aus dem sandbox thread funktioniert ja im prinzip genau so und hat das problem nicht. deshalb verstehe ich es ja nicht auch BlockingCall das OWServer fork funktionieren.

es gibt etwas ähnliches wie strace. ich schaue mal ob ich damit mehr rausfinde.

gruss
  andre
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

immi

boris, andre
I can confirm Andre experience on macos.

What can I see:

2015.03.01 20:07:10 1: RUN RUN RUN RUN...
2015.03.01 20:07:10 1: Step 0
2015.03.01 20:07:13 1: Step 1
2015.03.01 20:07:16 1: Step 2
2015.03.01 20:07:19 1: Step 3
2015.03.01 20:07:22 1: Step 4
2015.03.01 20:07:25 1: Step 5
2015.03.01 20:07:28 1: Step 6
2015.03.01 20:07:31 1: Step 7
2015.03.01 20:07:34 1: Step 8



From activity monitor I can see that
-the original process generate a second one
-the original process starts to consume all my memory
-when also my computer cannot deliver any memory more 60GB , the original process crashes
immi

Dr. Boris Neubert

Zitat von: justme1968 am 01 März 2015, 20:09:31
ja. fork geht natürlich. meine version der gleichen idee (forken und kommunikation per socketpair) aus dem sandbox thread funktioniert ja im prinzip genau so und hat das problem nicht. deshalb verstehe ich es ja nicht auch BlockingCall das OWServer fork funktionieren.

Genau, sandbox war's, was ich als Vorbild nehmen wollte und nicht mehr gefunden habe. Habe mir den Code angesehen und glaube, dass er bis auf das Einschleusen der Ausgabe aus dem Kindprozess über das socketpair (oder die pipe) in die globale Schleife identisch ist zum SubProcess. Mysteriös...

Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Dr. Boris Neubert

Zitat von: immi am 01 März 2015, 20:26:33
From activity monitor I can see that
-the original process generate a second one
-the original process starts to consume all my memory
-when also my computer cannot deliver any memory more 60GB , the original process crashes

That's an interesting observation. Would you mind checking if it's related to the
- insertion in the global select list (lines 152-154 in 98_SubProcessTester.pm),
- the writing to the parent (in onRun in 98_SubProcessTester.pm)
by simply removing these lines and repeat your test?

Cheers,
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

immi

#11
Hi Boris
by commenting out 152 to 154 --> a new process is generated and the memory is not filled out (parent and child respectively  20MB and 1 MB)
when the counter goes to 100 the second process exits

2015.03.02 20:10:11 1: RUN RUN RUN RUN...
2015.03.02 20:10:11 1: Step 0
2015.03.02 20:10:14 1: Step 1
2015.03.02 20:10:17 1: Step 2
2015.03.02 20:10:20 1: Step 3
2015.03.02 20:10:23 1: Step 4
2015.03.02 20:10:26 1: Step 5
2015.03.02 20:10:29 1: Step 6
2015.03.02 20:10:32 1: Step 7
2015.03.02 20:10:35 1: Step 8
2015.03.02 20:10:38 1: Step 9
2015.03.02 20:10:41 1: Step 10
2015.03.02 20:10:44 1: Step 11
2015.03.02 20:10:47 1: Step 12
2015.03.02 20:10:50 1: Step 13
2015.03.02 20:10:53 1: Step 14
2015.03.02 20:10:56 1: Step 15
2015.03.02 20:10:59 1: Step 16
2015.03.02 20:11:02 1: Step 17
2015.03.02 20:11:05 1: Step 18
2015.03.02 20:11:08 1: Step 19
2015.03.02 20:11:11 1: Step 20
2015.03.02 20:11:14 1: Step 21
2015.03.02 20:11:17 1: Step 22
2015.03.02 20:11:20 1: Step 23
2015.03.02 20:11:23 1: Step 24
2015.03.02 20:11:26 1: Step 25
2015.03.02 20:11:29 1: Step 26
2015.03.02 20:11:32 1: Step 27
2015.03.02 20:11:35 1: Step 28
2015.03.02 20:11:38 1: Step 29
2015.03.02 20:11:41 1: Step 30
2015.03.02 20:11:44 1: Step 31
2015.03.02 20:11:47 1: Step 32
2015.03.02 20:11:50 1: Step 33
2015.03.02 20:11:53 1: Step 34
2015.03.02 20:11:56 1: Step 35
2015.03.02 20:11:59 1: Step 36
2015.03.02 20:12:02 1: Step 37
2015.03.02 20:12:05 1: Step 38
2015.03.02 20:12:08 1: Step 39
2015.03.02 20:12:11 1: Step 40
2015.03.02 20:12:14 1: Step 41
2015.03.02 20:12:17 1: Step 42
2015.03.02 20:12:20 1: Step 43
2015.03.02 20:12:23 1: Step 44
2015.03.02 20:12:26 1: Step 45
2015.03.02 20:12:29 1: Step 46
2015.03.02 20:12:32 1: Step 47
2015.03.02 20:12:35 1: Step 48
2015.03.02 20:12:38 1: Step 49
2015.03.02 20:12:41 1: Step 50
2015.03.02 20:12:44 1: Step 51
2015.03.02 20:12:47 1: Step 52
2015.03.02 20:12:50 1: Step 53
2015.03.02 20:12:53 1: Step 54
2015.03.02 20:12:56 1: Step 55
2015.03.02 20:12:59 1: Step 56
2015.03.02 20:13:02 1: Step 57
2015.03.02 20:13:05 1: Step 58
2015.03.02 20:13:08 1: Step 59
2015.03.02 20:13:11 1: Step 60
2015.03.02 20:13:14 1: Step 61
2015.03.02 20:13:17 1: Step 62
2015.03.02 20:13:20 1: Step 63
2015.03.02 20:13:23 1: Step 64
2015.03.02 20:13:26 1: Step 65
2015.03.02 20:13:29 1: Step 66
2015.03.02 20:13:32 1: Step 67
2015.03.02 20:13:35 1: Step 68
2015.03.02 20:13:38 1: Step 69
2015.03.02 20:13:41 1: Step 70
2015.03.02 20:13:44 1: Step 71
2015.03.02 20:13:47 1: Step 72
2015.03.02 20:13:50 1: Step 73
2015.03.02 20:13:53 1: Step 74
2015.03.02 20:13:56 1: Step 75
2015.03.02 20:13:59 1: Step 76
2015.03.02 20:14:02 1: Step 77
2015.03.02 20:14:05 1: Step 78
2015.03.02 20:14:08 1: Step 79
2015.03.02 20:14:11 1: Step 80
2015.03.02 20:14:14 1: Step 81
2015.03.02 20:14:17 1: Step 82
2015.03.02 20:14:20 1: Step 83
2015.03.02 20:14:23 1: Step 84
2015.03.02 20:14:26 1: Step 85
2015.03.02 20:14:29 1: Step 86
2015.03.02 20:14:32 1: Step 87
2015.03.02 20:14:35 1: Step 88
2015.03.02 20:14:38 1: Step 89
2015.03.02 20:14:41 1: Step 90
2015.03.02 20:14:44 1: Step 91
2015.03.02 20:14:47 1: Step 92
2015.03.02 20:14:50 1: Step 93
2015.03.02 20:14:53 1: Step 94
2015.03.02 20:14:56 1: Step 95
2015.03.02 20:14:59 1: Step 96
2015.03.02 20:15:02 1: Step 97
2015.03.02 20:15:05 1: Step 98
2015.03.02 20:15:08 1: Step 99
2015.03.02 20:15:11 1: EXITED!

immi


p.s. actually, to be more precise, line 154 is the problem

Dr. Boris Neubert

Thank you, immi, this helps. Will need to understand the fhem main loop to know why this happens.
bn
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Dr. Boris Neubert

Alright, I found the bug. I mixed up file descriptor and file handle.

I replaced the SubProcess.pm module and the 98_SubProcessTester.pm device in the first post of this thread.

The SubProcessTester devices spawns a subprocess that writes back ten times a counter variable every 5 seconds with a blocking sleep. The device updates a "step" reading from the counter and FHEM remains responsive all the time.

I would be glad if immi and Andre could test this on their MacOS box.

Kind regards
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

immi

Hi Boris
-memory issue gone.
-fork works.
-comunication: I  was not sure, therefore I added some logs


sub SubProcessTester_Read($) {
  my ($hash) = @_;
  my $name= $hash->{NAME};
  Debug "$name has data to read!";
  my $subprocess= $hash->{fhem}{subprocess};
  my ($bytes, $result);
  $bytes= sysread($subprocess->child(), $result, 1024*1024);
  if(defined($bytes)) {
    chomp $result;
    readingsSingleUpdate($hash, "step", $result, 1);
    Log3 $hash, 2, "$name: the chidl told $result";
  } else {
    Log3 $hash, 2, "$name: $!";
    $result= undef;
  }
  return $result;
}


To my opinion everythiing is fine now

2015.03.08 16:02:19 1: RUN RUN RUN RUN...
2015.03.08 16:02:19 1: DEBUG>T has data to read!
2015.03.08 16:02:19 2: T: the chidl told 0
2015.03.08 16:02:24 1: DEBUG>T has data to read!
2015.03.08 16:02:24 2: T: the chidl told 1
2015.03.08 16:02:29 1: DEBUG>T has data to read!
2015.03.08 16:02:29 2: T: the chidl told 2
2015.03.08 16:02:34 1: DEBUG>T has data to read!
2015.03.08 16:02:34 2: T: the chidl told 3
2015.03.08 16:02:39 1: DEBUG>T has data to read!
2015.03.08 16:02:39 2: T: the chidl told 4
2015.03.08 16:02:44 1: DEBUG>T has data to read!
2015.03.08 16:02:44 2: T: the chidl told 5
2015.03.08 16:02:49 1: DEBUG>T has data to read!
2015.03.08 16:02:49 2: T: the chidl told 6


good job
immi

justme1968

hallo boris,

ja. das schaut jetzt viel besser aus. es blockiert nichts mehr und ich habe nach dem fork zwei prozesse die sauber kommunizieren.

gruss
  andre

ps: ich habe für meine sanbox variante das Log3 in fhem.pl so erweitert das es die pid mit raus schreibt wenn es nicht der haut prozess ist. ich denke das wäre für alle diese auf fork basierten varianten eine gute grundlage.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Dr. Boris Neubert

Danke immi und Andre für Eure Tests!

Andre, ist das Log3 mit Angabe der PID schon Teil des Standards? Bzw. wo ich das?

Viele Grüße
Boris

Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

justme1968

es ist nich nicht im standard. eine version ist in meinem sandbox patch.

im prinzip ist es nur beim starten die pid merken und in Log3 die aktuelle pid mit ausgeben wenn sie sich von der gemerkten unterscheidet.

ich weiß nicht ob rudi das einbauen würde.

könntest du dir vorstellen zur kommunikation zwischen den prozessen z.b. json zu verwenden? mit einer art mini protokoll ließe es sich einfacher erweitern und ich könnte dann auch die sandbox darauf aufsetzen.

gruß
  andre
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Dr. Boris Neubert

Zitat von: justme1968 am 08 März 2015, 21:42:30
es ist nich nicht im standard. eine version ist in meinem sandbox patch.

Habe ich Tomaten auf den Augen gehabt? Kannst Du bitte die aktuelle Version hier anhängen?

Zitat
könntest du dir vorstellen zur kommunikation zwischen den prozessen z.b. json zu verwenden? mit einer art mini protokoll ließe es sich einfacher erweitern und ich könnte dann auch die sandbox darauf aufsetzen.

Bist Du Telepath? Genau daran habe ich auch gedacht, um standardisiert aus dem Subprocess heraus Readings in den Hauptprozess zu füttern. Idealerweise ein extra Modul für IPC und RPC vermittels JSON.

Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

justme1968

die aktuelle version ist (immer noch) hier zu finden: http://forum.fhem.de/index.php/topic,18275.msg173732.html#msg173732.

in der sandbox version hatte ich es so gemacht das die ganz normalen fhem funktionen für readings update zusätzlich an den parent weiter gereicht werden und dort ein setreading auslösen. in der gegenrichtung wird ein attr an das child durchgereicht und dort auch ausgeführt.

das durchreichen sollte dann auch für ReadingVal eingebaut werden so das erst lokal geschaut wird ob es das device gibt, ansonsten im parent.

dadurch können viele module (alle die die standard routinen verwenden und nicht direkt im hash readings oder attribute setzen) ohne weitere änderungen in so eine sandbox gesteckt werden. entweder einzeln oder als gruppe.

den schritt das protokoll sauber zu machen hatte ich zwar schon vor aber noch nicht gemacht.

in dem verlinkten thread ist auch kurz beschrieben das in jeder sandbox ein telnet modul läuft mit dem man sich in die sanbox verbinden kann um zu debuggen.

gruss
  andre
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Markus Bloch

Hallo zusammen,

ich habe mir mal soeben die eingecheckte Version von SubProcess.pm angeschaut um mal zu schauen, wie ich das in PRESENCE nutzen könnte, nun stellt sich mir gerade ein paar Fragen:

- offenbar kann man aktuell keine Parameter für die onRun-Funktion und die onExit-Funktion setzen. Wie kann ich Argumente übergeben? In einem Modul muss ich ja irgendwo wissen zu welchem Device dieser Aufruf gehört, welche Parameter dafür relevant sind zwecks Zuordnung des Ergebnis.

- Wie kann man ein Funktionsergebnis der onRun-Funktion auswerten?
- Kann man Status-Updates an den Aufrufenden senden?
- In dem Beispiel SubProcessTester ist noch eine Read-Funktion definiert. Wozu ist die genau gedacht?

Danke im Vorraus für ein paar erhellende Worte.

Gruß
Markus
Developer für Module: YAMAHA_AVR, YAMAHA_BD, FB_CALLMONITOR, FB_CALLLIST, PRESENCE, Pushsafer, LGTV_IP12, version

aktives Mitglied des FHEM e.V. (Technik)

Dr. Boris Neubert

Hallo Markus,

Zitat von: Markus Bloch am 29 März 2015, 23:15:27
- offenbar kann man aktuell keine Parameter für die onRun-Funktion und die onExit-Funktion setzen. Wie kann ich Argumente übergeben? In einem Modul muss ich ja irgendwo wissen zu welchem Device dieser Aufruf gehört, welche Parameter dafür relevant sind zwecks Zuordnung des Ergebnis.

- Wie kann man ein Funktionsergebnis der onRun-Funktion auswerten?
- Kann man Status-Updates an den Aufrufenden senden?
- In dem Beispiel SubProcessTester ist noch eine Read-Funktion definiert. Wozu ist die genau gedacht?

am besten schaust Du Dir das edukative Beispiel

contrib/SubProcess/98_SubProcessTester.pm

an.

Ich habe gerade eine Version davon eingecheckt, bei der ich beispielhaft eine Variable foobar im subprocess setze und in onRun auswerte,

Die Status-Updates funktionieren über die globale select-Schleife: Du schreibst in der Run-Funktion auf den Parent-Handle und wertest es im Modul in der Read-Funktion aus.

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Markus Bloch

Ah super vielen Dank, das hilft schonmal ungemein weiter.

Was ich noch vorschlagen würde ist eine Funktion SubProcess_SimpleRead() analog zu DevIo_SimpleRead zu machen, so das man in der ReadFn des Moduls, welches einen SubProcess spawnt mit einem Funktionsaufruf die entsprechenden Daten abrufen kann. Das erspart das ganze gefummel mit select() und ist deutlich intuitiver.

So dass in deinem Beispiel die ReadFn folgendermaßen aussieht:

#####################################
# called from the global loop, when the select for hash->{FD} reports data
sub SubProcessTester_Read($) {

  my ($hash) = @_;
  my $name= $hash->{NAME};
 
  #Debug "$name has data to read!";
 
  my $subprocess= $hash->{fhem}{subprocess};
 
  my $result = SubProcess_SimpleRead($subprocess);

  if(defined($result))
  {
    readingsSingleUpdate($hash, "step", $result, 1);
  } else {
    Log3 $hash, 2, "no data received";
  }
  return $result;
}


Dann denke ich, wäre das ne super Sache.

Vielen Dank.

Gruß
Markus
Developer für Module: YAMAHA_AVR, YAMAHA_BD, FB_CALLMONITOR, FB_CALLLIST, PRESENCE, Pushsafer, LGTV_IP12, version

aktives Mitglied des FHEM e.V. (Technik)

herrmannj

Hi,

in fronthem benutze ich ein ähnliches Konstrukt.

Folgende Erfahrungen:
* nonblocking zum und vom fork. Wenn der irgendwas longrunning macht kann er sonst fhem still legen. Nonblocking read ist mMn nicht ganz optimal in der fhem main select umgesetzt.
* der fork kann sterben ohne das papa das sofort merkt. Folgende write killen den papa wenn er das nicht abfängt.
* wenn non-blocking müssen fork und papa die gegenseitigen Nachrichten auch in chunks verabeiten können (Meint: ein read liefert "Mach mal dies un" .... später "d das"). Braucht festgelegte Nachrichtenformate, trenner.
* Für fronthem habe ich noch Kommandas von papa an fork wie "shutdown" weil sonst "shutdown restart" immer meckert (obwohl es das eigentlich nicht dürfte)

Wieviel davon jetzt für das Nebenlauf-Skelett zum tragen kommt weiß ich nicht - möchte nur meine Erfahrung teilen.

vg
jörg

Dr. Boris Neubert

Hallo Markus,

Zitat von: Markus Bloch am 30 März 2015, 23:35:19
Was ich noch vorschlagen würde ist eine Funktion SubProcess_SimpleRead() analog zu DevIo_SimpleRead zu machen, so das man in der ReadFn des Moduls, welches einen SubProcess spawnt mit einem Funktionsaufruf die entsprechenden Daten abrufen kann. Das erspart das ganze gefummel mit select() und ist deutlich intuitiver.

Danke für Deine Anregung. Ich habe das als subprocess->read() eingebaut. SubProcess.pm und das Beispiel sind aktualisiert und eingecheckt.

Grüße
Boris

Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Dr. Boris Neubert

Hallo Jörg,

Danke für Deine Erfahrungen. Dazu habe ich Fragen und Anmerkungen.

Zitat von: herrmannj am 31 März 2015, 00:29:11
* nonblocking zum und vom fork. Wenn der irgendwas longrunning macht kann er sonst fhem still legen. Nonblocking read ist mMn nicht ganz optimal in der fhem main select umgesetzt.

Ich verstehe nicht, was Du meinst. Wenn der Kindprozess losgetreten ist, blockiert FHEM maximal für die Zeit, welche die ReadFn des verwendenden Moduls benötigt, vom Kindprozess zu lesen, nachdem dieser etwas auf den in der globalen/main select-Schleife überwachten Handle geschrieben hat.
Zitat
* der fork kann sterben ohne das papa das sofort merkt. Folgende write killen den papa wenn er das nicht abfängt.

Im Moment habe ich noch keinen Anwendungsfall, bei dem der Elter aufs Kind schreibt, obwohl das latürnich  :P schon vorgesehen ist. GGf. müsste ich analog zum subprocess->read() ein subprocess->write() bauen, welches vorher das schon existierende subprocess->running() abfragt, ob das Kind noch lebt. Habe dazu eine Notiz in meine lokale Version geschrieben.

Zitat
* wenn non-blocking müssen fork und papa die gegenseitigen Nachrichten auch in chunks verabeiten können (Meint: ein read liefert "Mach mal dies un" .... später "d das"). Braucht festgelegte Nachrichtenformate, trenner.

Stimmt, das ist von der eigentlichen Anwendungslogik des Verwenders abhängig. Im Moment gibt es ja auch Ideen, die Kommunikation zwischen Kind und Elter optional über ein JSON-API zu standardisieren.

Zitat
* Für fronthem habe ich noch Kommandas von papa an fork wie "shutdown" weil sonst "shutdown restart" immer meckert (obwohl es das eigentlich nicht dürfte)

Yep, daran habe ich auch gedacht. Der Verwender muss daran denken (im Beispielmodul realisiert).

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

herrmannj

Hallo Boris

perfekt - Du setzt das in Teilen ja auch anders um.
ZitatIch verstehe nicht, was Du meinst. Wenn der Kindprozess losgetreten ist, blockiert FHEM maximal für die Zeit, welche die ReadFn des verwendenden Moduls benötigt, vom Kindprozess zu lesen, nachdem dieser etwas auf den in der globalen/main select-Schleife überwachten Handle geschrieben hat.
Natürlich. Ich meine die andere Richtung, Parent zum Child. Natürlich unter der Voraussetzung das Parent/Child einen Dialog führen wenn der Prozess losgetreten ist. Wobei Ich mir nicht sicher bin ob das Deiner Intention entspricht.

vg
jörg

Dr. Boris Neubert

Zitat von: herrmannj am 01 April 2015, 22:07:58
Ich meine die andere Richtung, Parent zum Child. Natürlich unter der Voraussetzung das Parent/Child einen Dialog führen wenn der Prozess losgetreten ist. Wobei Ich mir nicht sicher bin ob das Deiner Intention entspricht.

Verstehe. Der Kindprozess hat derzeit keine Möglichkeit, asynchron Botschaften vom Elterprozess zu empfangen. Er müsste also auf den Parent-Handle pollen. während er etwas anderes tut. Wenn das Kind zu lange mit etwas anderem beschäftigt ist, würde dann der Elterprozess (und damit FHEM) tatsächlich blockieren.

Gibt es dafür eine Lösung?

Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

justme1968

die lösung (oder zumindest eine lösung) ist im child eine (oder die gleiche) main loop mit select und readFn zu haben wie im normalen (parent) fhem prozess.

hmm.. ich hoffe die vielen klammern machen es nicht undurchsichtiger.

wenn man die idee bis zu ende spinnt landet man wieder bei der sandbox variante bei der in parent und child im prinzip die ganze fhem infrastruktur zur verfügung steht.

gruss
  andre
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

herrmannj

Zitat von: Dr. Boris Neubert am 01 April 2015, 22:21:26
Verstehe. Der Kindprozess hat derzeit keine Möglichkeit, asynchron Botschaften vom Elterprozess zu empfangen. Er müsste also auf den Parent-Handle pollen. während er etwas anderes tut. Wenn das Kind zu lange mit etwas anderem beschäftigt ist, würde dann der Elterprozess (und damit FHEM) tatsächlich blockieren.

Exakt. In fronthem bin ich dazu den Weg gegangen das handle non-blocking aufzusetzen, beim write EWOULDBLOCK abzufragen und einen zusätzlichen Puffer zu pflegen. Das führt dann eben auch dazu das der client nicht davon ausgehen darf bei einem read eine vollständige Botschaft zu empfangen. Es werden dann genau so viele Zeichen geschrieben wie non-blocking möglich und dann gewartet bis der client durch read wieder Platz schafft.

Ob der Weg von Andre das vereinfacht kann ich nicht beurteilen.

vg
jörg

justme1968

ich denke es sind drei punkte die zusammenspielen und deren lösung sich überlappen:
- blockieren verhindern
- aktives pollen verhindern
  - gestückelte nachrichten

select und und main loop kann die ersten beiden lösen und ermöglicht im child auch gleich noch InternalTimer und lesen von unterschiedlichen quellen sie z.b. anderen geräten und datenquellen

protokoll wie z.b. json würde den dritten punkt lösen.

nicht blockierende sockets würden nur bei 1. helfen. für 2. wäre trozdem select nötig und 3. geht ohne protokoll (egal wie minimal) sowieso nicht da es noch mehr ursachen gibt aus denen eine nachricht nicht vollständig zum lesen verfügbar ist oder mehr als einenachricht im buffer steht.

gruß
  andre
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

Dr. Boris Neubert

Zitat von: herrmannj am 01 April 2015, 22:54:25
Exakt. In fronthem bin ich dazu den Weg gegangen das handle non-blocking aufzusetzen, beim write EWOULDBLOCK abzufragen und einen zusätzlichen Puffer zu pflegen.

Kannst Du mir bitte sagen, in welchen beiden Routinen im Fronthem diese Kommunikation stattfindet?
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Dr. Boris Neubert

Zitat von: justme1968 am 01 April 2015, 23:09:10
protokoll wie z.b. json würde den dritten punkt lösen.

Bevor ich google oder selber denke: gibt es für die Verarbeitung fragmentierter Nachrichten einen Königsweg? Was meine ich damit? Nehmen wir an, dass wir JSON sprechen. Dann kann es vorkommen, dass halbe, ganze, anderthalbe, ... Nachrichten übertragen werden:


{ foo: "b
ar"
} { abcd: "hallo" } { foo: "


Simpel lässt sich das lösen, wenn man als Transprotokoll z.B. die Form [Nachrichtenlänge] [n Bytes Nachricht] wählt.

Will man nur JSON ohne Transportprotokoll nutzen, muss man schon parsen und sammeln. bis man ein vollständiges JSON-Objekt gefunden hat. Das wird dann aus dem Nachrichtenstream entfernt und an die höheren Gehirnregionen übergeben, während der Rest gepuffert wird. Das ist aufwendig selbst zu machen, aber vielleicht gibt es dafür ja ein Modul.

Im Moment favorisiere ich, in SubProcess das o.g. simple Protokoll i.V.m. mit dem Vorschlag von Jörg zu nichtblockierenden Handles.

Viele Grüße
Boris


Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

justme1968

für json hatte ich bis lang nichts gefunden.

für xml gibt es eine lib die das könnte, die ist aber so schwergewichtig das ich im harmony modul auf ein paar regex für diesen zweck ausgewichen bin. da gibt es aber den vorteil das es keine beliebigen daten bzw. nachrichten sind.

sich das leben mit einem <länge><json> format einfacher zu machen ist der bessere ansatz.

ich würde dieses protokoll mit select und non blocking sockets vorschlagen. das select ist wegen InternalTimer und nicht-busy wait wichtig.

gruss
  andre
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

herrmannj

Zitat von: Dr. Boris Neubert am 01 April 2015, 23:23:02
Kannst Du mir bitte sagen, in welchen beiden Routinen im Fronthem diese Kommunikation stattfindet?
Gern. (Da ist noch nicht alles top, das ist noch Prozess. Im Lehrbuch läßt sich das noch nicht abdrucken)

https://github.com/herrmannj/fronthem/blob/master/FHEM/01_fronthem.pm

Der parent schreibt via fronthem_ipcWrite(@) auf den fork
Der parent liest via fronthem_ipcRead($) vom fork.

Der (bisher eine existierende) fork liest via fronthem_wsIpcRead(@) vom parent
Die Kommunikation vom fork an den parent erfolgt derzeit noch (inkonsequent; eben gewachsen) in den einzelne Routinen, zB in fronthem_wsConnect(@) : my $size = $conn->server()->{ipc}->send($msg."\n",0); (todo:))

Um Dir das lesen zu erleichtern: weil ich sehr auf sicherheit achte speichere ich die PID beim forken und der client muss sich mit der PID authorisieren. So möchte ich verhindern das "böse" Prozesse die Schnittstelle mißbrauchen.

Ich lese Deinen thread sehr aufmerksam mit weil ich gerade dabei bin fronthem für plots zu erweitern. Dazu forke ich weitere Prozesse (lesen aus dem filelog) die eben auch sehr die bidriektionale Kommunikation benötigen, ist im Prinzip exakt die gleiche Aufgabenstellung and der Du arbeitest.

Für das Protokoll verwende ich JSON. Das oben richtig beschriebene Problem mit den abgeschnittenen Nachrichten adressiere ich in dem ich jede vollständige JSON Nachricht mit crlf abschliesse und das in der msg maskiere.

$rv = $serv->{'ipc'}->recv($msg, POSIX::BUFSIZ, 0);
unless (defined($rv) && length $msg) {
$serv -> shutdown();
return undef;
}
$serv->{buffer} .= $msg;
while (($serv->{buffer} =~ m/\n/) && (($msg, $serv->{buffer}) = split /\n/, $serv->{buffer}, 2))
{
eval {
$msg = decode_json($msg);
};
fronthem_forkLog3 ($serv->{ipc}, 1, "$serv->{id} ipc decoding error $@") if ($@);
fronthem_wsProcessInboundCmd($serv, $msg);
}


vg
jörg


Dr. Boris Neubert

Hallo,

ich habe nun die subprocess-Klasse um die folgenden Funktionen erweitert:

writeToChild()
readFromChild()
writeToParent()
readFromParent()


Die bisherige Funktion read() wurde dabei zu readFromChild().

Ich benutze nicht-blockierende Sockets und select in der bidirektionalen symmetrischen Kommunikation zwischen Child und Parent. Getestet habe ich das über eine Set-Funktion im 98_SubProcessTester.pm, welches per

set T send laberlaberlaber


einen Text zum Child sendet. Die Set-Funktion kehrt sofort wieder, auch wenn das Child in der onRun-Funktion noch in der fünfsekündigen Warteschleife (sleep 5) steht.

Zerissene Texte sollte es keine geben. Ich habe dazu ein simples Transportprotokoll implementiert, das jeder Übertragung vom Child zum Parent oder vom Parent zum Child die Länge des übermittelten Skalars als 32bit unsigned int (network byte order) übergibt. Auf diese Weise trenne ich das Transportprotokoll von einem etwaigen vom Anwender festzulegendem Kommunikationsprotokoll (z.B. JSON) in der Payload der Übertragung.

Jeder Aufruf von writeTo...() ist eine Übertragung. readFrom...() liefert entweder undef oder den mit writeTo...() gesendeten Skalar vollständig zurück.

Werde als nächstes versuchen, mit Hilfe von subprocess das VirtualBox-API in den für mich benötigten Teilen in FHEM zu integrieren.

Beste Grüße
Boris


Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Markus Bloch

Hallo Boris,

finde ich an sich eine tolle Sache. Das ermöglicht eine einfache non-blocking Kommunikation zu einem Child-Prozess (insbesondere bei dauerhaft laufenden Prozessen).

Jetzt stellt sich mir nur eine Frage: Funktioniert das auch unter Windows? So wie ich das sehe funktioniert das glaube ich nur unter Unix-basierten Systemen (Linux/Mac).

Hintergrund warum ich frage: Ich hatte in der Vergangenheit (besonders in der Anfangszeit von PRESENCE) immer wieder von Problemen bei Windows-Usern gehört. Damals ging es um das generelle Thema "forking unter Windows" was ja aktuell jeder Perl-Interpreter etwas anders implementiert. Der eine als Threads, der andere als neuer Hauptprozess. Ich weis nicht, ob solche Interpreter unter Windows auch UNIX-Sockets können.

Ich nehme mal an, SubProcess.pm ist ausschließlich für Unix-Systeme gedacht, gehe ich da richtig in der Annahme?

Viele Grüße

Markus
Developer für Module: YAMAHA_AVR, YAMAHA_BD, FB_CALLMONITOR, FB_CALLLIST, PRESENCE, Pushsafer, LGTV_IP12, version

aktives Mitglied des FHEM e.V. (Technik)

Dr. Boris Neubert

Hallo Markus,

ich habe beim Lesen der Doku zu fork und socketpair nichts spezielles gelesen, das systemspezifisch sei. Ich habe es aber auch nur unter Linux, genauer Ubuntu 15.04, getestet. Ich meine, dass am Anfang des Threads auch jemand unter OS X erfolgreich war. Windows müsste jemand testen. Freiwillige vor!

Viele Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!