Probleme mit aktueller Blocking Implementation

Begonnen von Dirk, 03 August 2013, 13:35:57

Vorheriges Thema - Nächstes Thema

Dirk

Hallo zusammen,

im aktuellem Blocking.pm werden beim BlockingCall neuerdings im Child ja alle offenen Verbindungen geschlossen.
Das macht unter Umständen auch sinn, und es gab dazu ja schon jede Menge Diskussionen.

Jetzt habe ich hier aber ein Problem damit.

Folgende Konstellation funktioniert:
- Modul öffnet eine Verbindung zu einem lokalem seriellen Gerät.
- BlockingCall wird aufgerufen, alle Verbindungen werden geschlossen.
- Um die Verbindung zum selben seriellen Gerät zu benutzen muss ich diese im Child neu öffnen.

Soweit so gut. Für das lokale Gerät funktioniert die zweite parallele Verbinung

Ein Problem tritt aber auf, wenn ich statt des seriellen Gerätes z.B. eine Verbindung zu einem Netzwerk-Gerät öffnen möchte, welches nur genau eine Verbindung zu lässt. Das klappt dann natürlich nicht.

In der ursprünglichen Version von Blocking.pm, also wo die Verbindungen nach dem Fork nicht geschlossen werden, funktioniert das ganze.

Hat jemand eine Idee für mich wie ich das Problem lösen kann?

Gruß
Dirk

justme1968

da alle filedescriptoren bei einem fork dupliziert werden muss  danach auf jeden fall jeweils eines der duplikate geschlossen werden. sonst führt das zu allen möglichen und zum teil sehr obskuren folgefehlern.

prinzipiell geht die augenblickliche implementierung davon aus das filedescriptoren da aufgemacht werden so sie gebraucht werden. d.h. im parent für den parent und im child für den child. folgerichtig werden die parent files für den child geschlossen.

wenn man dieses verhalten ändere möchte müsste man für jeden filedescriptor angeben ob er im parent oder im child geschlossen werden soll. dann könnte man auch mit einem paar sockets bidirektional zwischen parent und child komunizieren und wäre nicht auf telnet und auf nur eine richtung beschränkt.

aber erst mal ganz allgemein gefragt:
- warum machst du die verbindung im parent überhaupt auf wenn du sie im child nutzen willst?
- ist wirklich blocking call nötig oder wäre die select list mit einer readFn nicht sowieso der 'bessere' ansatz?

blickingcall ist oft nicht die richtige lösung. erst recht nicht wenn es mehrfach in kurzer zeit aufgerufen wird.
ohne genaueres über dein device und den konkreten anwendungsfall zu wissen lässt sich das schlecht beantworten.

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

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

Dirk

Zitat- warum machst du die verbindung im parent überhaupt auf wenn du sie im child nutzen willst?
Weil ich die Verbindung im Parent und im  Client brauche.

Die Implementation von HM-Wired betreffend gibt es Timing vorgaben die beachtet werden müssen. Vor allem betreffen ACK und Resend.
Diese Zeitvorgaben kann ich im Elternprozess aber nicht umsetzen bzw. garantieren, da ein Sleep diesen eben blocken würde. Und auf InternalTimer kann ich mich nicht verlassen wenn es um einige 100 ms geht. Daher sind diese Sachen in den Child ausgelagert.

Bisher funktioniert das auch ganz hervorragend.

Zitat- ist wirklich blocking call nötig oder wäre die select list mit einer readFn nicht sowieso der 'bessere' ansatz?
Vielleicht übersehe ich hier ja noch was? Ansonsten kann man das natürlich auch außerhalb von FHEM abgewickeln. Ich hatte aber gehofft, dass das nicht nötig ist.

Gruß
Dirk

justme1968

eine einzigen filedescriptor gleichzeitig in parent und child zu verwenden ist prinzipiell nicht sauber und funktioniert wenn überhaupt nur zufällig (vielleicht mit sockets messer als mit files) weil kein definiertes verhalten beim gleichzeitigen schreiben gibt. die duplizierten filedescriptoren  zeigen ja auf das gleiche kernel objekt mir nur einem einzigen buffer und filepointer.

warum brauchst du die verbindung im parent und child? wenn es auf das genaue timing ankommt ist es doch eigentlich genau kontraproduktiv wenn im child alles auf die ms genau gemacht wird und dann der parent einfach auf dem gleichen socket dazwischen funkt.

was spricht denn dagegen ausschliesslich den child prozess zur kommunikation zu verwenden und asynchron vom parent aus zu füttern? vielleicht reden wir gerade aneinander vorbei aber eine solche aufteilung hätte vielleicht sogar den vorteil das sie auch mit fhem2fhem funktionieren würde.

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

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

Dirk

Zitateine einzigen filedescriptor gleichzeitig in parent und child zu verwenden ist prinzipiell nicht sauber und funktioniert wenn überhaupt nur zufällig
Da das "damals" funktionierte gab es von meiner Seite aus da halt keine Überlegungen weiter.

warum brauchst du die verbindung im parent und child?Der Parent empfängt nur.
Nur der Child sendet, wartet auf ACK und wiederholt die Nachricht ggf. falls kein ACK kommt.

Zitatwenn es auf das genaue timing ankommt ist es doch eigentlich genau kontraproduktiv wenn im child alles auf die ms genau gemacht wird und dann der parent einfach auf dem gleichen socket dazwischen funkt.
Der Parent sendet nix. Falls es neue Nachrichten vom Parent gibt während der Child noch sendet, werden die im Parent gesammelt und dann abgearbeitet wenn der Child fertig ist. Daher währ hier das gemeinsame benutzen des Sockets aus meiner Sicht unkritisch.

Zitatwas spricht denn dagegen ausschliesslich den child prozess zur kommunikation zu verwenden und asynchron vom parent aus zu füttern?
Das könnte man ggf. machen. Dann würde der Child aber immer laufen. Und ich müsste eine separate Parent->Child Kommunikation implementieren damit der Child von dem was im Parent passiert auch was mitbekommt (Konfigurationsänderungen usw.)

Zitatvielleicht reden wir gerade aneinander vorbei aber eine solche aufteilung hätte vielleicht sogar den vorteil das sie auch mit fhem2fhem funktionieren würde.
Die aktuelle Implementierung sollte fhem2fhem bereits unterstützen. Ok, das hab ich aber noch nicht getestet.

Gruß
Dirk

justme1968

ZitatNur der Child sendet, wartet auf ACK und wiederholt die Nachricht ggf. falls kein ACK kommt.
und wie stellst du sicher das das ack nicht beim parent ankommt?

ZitatDaher währ hier das gemeinsame benutzen des Sockets aus meiner Sicht unkritisch.
genau das ist es aber leider nicht weil sich fhem z.b. bei einem shutdown restart nicht mehr sauber runter und wieder rauf fahren lässt weil sockets offen bleiben. du kannst auch nicht regeln ob parent oder child die antworten aus dem socket lesen.

ZitatDann würde der Child aber immer laufen
das wäre aber eigentlich der resourcen schonendere ansatz als für jede nachricht wieder neu zu forken.

auch wenn es "damals funktioniert hat" die alte implementierung hat ganz wirklich einige probleme gehabt (die zum teil auch noch nicht ganz behoben sind) und zu mehr als einem seiteneffekt geführt. und das war nur der fall wo ein socket nicht in parent und child gleichzeitig verwendet wurde. das kann lange gut gehen wird aber um so ärgerlicher dazwischenfunken wenn sich irgendewelche randbedingungen ändern. und das können so scheinbar unzusammenhängende dinge sein wie ein schnellerer prozessor (weil das timing nicht simmt), ein zusätzlicher prozessor kern (weil dann plötzlich parent und child ganz wirklich gleichzeitig laufen und nicht mehr nur virtuell), ein zusätzliches modul das auch blocking call verwendet (weil dann das select aus 'versehen' im falschen prozess aufgerufen wird) und vieles andere mehr.

sobald man mit mehreren Prozessen (und noch mehr mit mehreren threads) arbeitet lohnt es sich am anfang etwas mehr aufwand in die kommunikation zu stecken. es zahlt sich wirklich aus.

gruss
  andre

ps: das soll alles gar nicht so negativ sein wie es klingt sondern nur vermeiden das es später überraschungen gibt.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

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

rudolfkoenig

Ein Flag zum "nicht schliessen" des Fildescriptors beim BlockingCall kann ich gerne einfuehren, das verursacht keine Seiteneffekte und ist mit wenig Code realisierbar.

Aber aehnlich wie andre sehe ich nicht, was das loesen soll: wenn der Vaterprozess die Daten vom device nicht rechtzeitig liest, dann kann man die 100ms eh nicht einhalten, und wenn beide (Vater und Kind) lesen, dann gibt es keine Garantie, wer was bekommt. Dieser Flag wuerde nur den Fall loesen, falls direkt nach weglesen der Daten fhem.pl laenger als 100ms blockiert ist.

Die richtige Loesung mAn waere eine Kommunikation zw. Vater- und Kindprozess einzurichten, und alles an Device-Lesen/Schreiben im Kindprozess zu erledigen. Das Kind braucht natuerlich wieder ein select, da es auf Vater und Device gelichzeitig aufpassen muss.

Immerhin ist das der erste Fall, wo ich meine dass das Problem ohne einen zweiter Prozess/thread nicht gut zu loesen ist.

Dirk

Zitatund wie stellst du sicher das das ack nicht beim parent ankommt?
In diesem Fall bekommt der Parent den ACK natürlich auch, verarbeitet den aber nicht.

Zitatdas wäre aber eigentlich der resourcen schonendere ansatz als für jede nachricht wieder neu zu forken.
Hm, das hatte ich mir auch schon gedacht. Die Antwort hatte ich gehofft nicht zu bekommen :(

Dann kann man den Protokoll-Teil aber gleich in einen eigenen Prozess auslagern. Ggf. sogar in C geschrieben. Das währ noch mal etwas Ressourcen schonender.
Na mal sehen. Vermutlich wird es dann erst mal ein irgend ein gearteter Perl-Prozess ob aus FHEM geforkt oder einzeln laufend, mal sehen. wie ich das umbaue. Einzeln hätte den Vorteil, dass man nicht das komplette FHEM-Enviroment mit "rum schleppen" muss.

Dank dir.

Gruß
Dirk


Dirk

Hi Rudi,

ZitatImmerhin ist das der erste Fall, wo ich meine dass das Problem ohne einen zweiter Prozess/thread nicht gut zu loesen ist.
Der Meinung bin ich langsam auch.
Bisher haben wir mit CUL, HM-Lan usw. den Vorteil das das Protokoll da drin steckt. Das läuft für FHEM transparent.

Gruß
Dirk

rudolfkoenig

> In diesem Fall bekommt der Parent den ACK natürlich auch, verarbeitet den aber nicht.

So funktioniert das aber nicht. Sockets haben getrennte Schreibe- und Lesekanaele, das was ein Kind schreibt, landet nicht beim Vater und umgekehrt. Beim Schreiben sehe ich auch keine Schwierigkeit, nur beim Lesen, da aber massiv.

justme1968

ZitatEin Flag zum "nicht schliessen" des Fildescriptors beim BlockingCall kann ich gerne einfuehren, das verursacht keine Seiteneffekte und ist mit wenig Code realisierbar.

Aber aehnlich wie andre sehe ich nicht, was das loesen soll: wenn der Vaterprozess die Daten vom device nicht rechtzeitig liest, dann kann man die 100ms eh nicht einhalten, und wenn beide (Vater und Kind) lesen, dann gibt es keine Garantie, wer was bekommt. Dieser Flag wuerde nur den Fall loesen, falls direkt nach weglesen der Daten fhem.pl laenger als 100ms blockiert ist.

das flag an sich wurde das problem nicht direkt lösen wäre aber vorausetzung die parent/child kommunikation z.b. mit einem socketpair zu realisieren. mit dem flag könnte man dann in parent und child jeweils die 'richtige' seite zu machen und nicht beide.

ZitatIn diesem Fall bekommt der Parent den ACK natürlich auch, verarbeitet den aber nicht.
das funktioniert nicht. die daten die der parent gelesen hat sind 'weg'. das child kann sie nicht noch mal lesen.

ZitatBisher haben wir mit CUL, HM-Lan usw. den Vorteil das das Protokoll da drin steckt. Das läuft für FHEM transparent.
ich wollte schon vorschlagen in deinen rs485 adapter noch einen kleinen prozessor  zu bauen der sich genau um das timing kümmert :)

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

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

rudolfkoenig

>  Bisher haben wir mit CUL, HM-Lan usw. den Vorteil das das Protokoll da drin steckt.

Noch ein Grund einen separaten Prozess fuer diese Aufgabe zu bauen. Das kann mit BlockingCall oder ohne (wie der HM-USB Daemon) geloest werden. Ob das Programm in Perl geschrieben ist oder nicht, ist jedem freigestellt, obwohl mit perl das Problem der binary/compiler schon geloest ist.

Dirk

ZitatSo funktioniert das aber nicht. Sockets haben getrennte Schreibe- und Lesekanaele, das was ein Kind schreibt, landet nicht beim Vater und umgekehrt
Stimmt, jetzt wo du es sagst, das hatte wir auch schon in der Vergangenheit das Thema.

Hab nochmal in den Code geschaut, das ist doch schon wieder zu lange her :)
Also der Parent liest ausschließlich.
Der Child sendet, wenn der Parent ein ACK empfängt dann terminiert der Parent den Child und das Packet wurde als Versendet angesehen.
Falls der ACK nicht rechtzeitig kommt, dann gibt es von Child bis zu 2 Resend-Versuche, dannach beendet sich der Child.

Aber wie wir schon festgestellt haben, pack ich das in einen seperaten Prozess

Edit:
Zitat von: rudolfkoenig schrieb am Sa, 03 August 2013 16:08obwohl mit perl das Problem der binary/compiler schon geloest ist.
Das ist auch mein bevorzugtes Vorgehen derzeit. Portieren kann man das später immer noch.

Edit2:
Zitat von: justme1968 schrieb am Sa, 03 August 2013 16:07ich wollte schon vorschlagen in deinen rs485 adapter noch einen kleinen prozessor  zu bauen der sich genau um das timing kümmert :)
Die "dummen" Adapter sind so schön günstig, dass ich das eigentlich vermeiden wollte.

Gruß
Dirk

ntruchsess

Falls Du Dir mal ansehen möchtest, wie ich das im asynchronen OWX mit Threads gelöst habe (da sind die Anforderungen ähnlich: Timing im 100ms-Bereich ist zu schnell für InternalTimer, warten per select bremst fhem bei höheren Pollfrequenzen aber auch merklich aus):

11_OWX_Executor.pm wickelt die Kommunikation mit dem eigentlichen Device-modul (z.B. 11_OWX_SER.pm ab (wahlweise asynchron in einem Thread oder auch synchron, wenn die Plattform keine Threads unterstützt). Die Kommunikation mit dem Background-thread erfolgt über 2 Queues. 00_OWX.pm ruft für alle Aktionen im 11_OWX_Executor die passende methode auf, die ein Command-objekt in die Request-queue stellt. Parallel dazu sorgt es in seiner ReadFn dafür, dass die Response-Queue zyklisch gepollt (und damit auf die Anworten reagiert) wird.

Könnte man analog natürlich auf mit einem fork und sockets machen.

Gruß,

Norbert
while (!asleep()) {sheep++};

Dirk

Hallo Norbert,

danke für den Tipp. Das schaue ich mir auf jeden Fall mal an.

Gruß
Dirk

Dirk

Hallo Norbert,

ich habe mir das ganze einmal angesehen.
Sieht soweit auch ganz gut aus. Allerdings habe ich noch nicht alles verstanden. Denke ich werde das mal versuchen für meine Zwecke entsprechend zu benutzen.

Ein paar Fragen hab ich aber noch:
Zitatwarten per select bremst fhem bei höheren Pollfrequenzen aber auch merklich aus
In welchem Zusammenhang? Dachte das Threading sollte das verhindern?

Zitatwahlweise asynchron in einem Thread oder auch synchron, wenn die Plattform keine Threads unterstützt
welche Platform währe das z.B.? Wie läuft die Abarbeitung denn dann synchron? Wieder blockierend?

ZitatDie Kommunikation mit dem Background-thread erfolgt über 2 Queues
Wieso über 2 Queues? Wenn man mit Threads arbeitet sollte doch auch eine "Kommunikation" über shared vriablen möglich sein?

Gruß
Dirk

ntruchsess

Hallo Dirk,

Zitat von: Dirk schrieb am Di, 27 August 2013 14:19
Zitatwarten per select bremst fhem bei höheren Pollfrequenzen aber auch merklich aus
In welchem Zusammenhang? Dachte das Threading sollte das verhindern?
Die Aussage zum 'select' bezog sich auf den synchronen Fall ohne Threads.

Zitat von: Dirk schrieb am Di, 27 August 2013 14:19
Zitatwahlweise asynchron in einem Thread oder auch synchron, wenn die Plattform keine Threads unterstützt
welche Platform währe das z.B.? Wie läuft die Abarbeitung denn dann synchron? Wieder blockierend?
Naja, man kann ja nicht einfach davon ausgehen, dass jeder sein Perl mit Threadunterstützung compiliert hat. Auf Systemen mit wenig Ram macht das perl-threading auch wenig Sinn, da alle(!) vorhandenen Variablen beim Thread-start dupliziert werden. Ich weiß z.B. nicht, ob perl auf der Fritzbox Threads unterstützt. Der code ist jedenfalls so geschrieben, dass er beim Nichtvorhandensein von Threads trotzdem laufen müsste, dann natürlich (bei den kurzen, protokollbedingten Delays) wieder blockierend.

Zitat von: Dirk schrieb am Di, 27 August 2013 14:19
ZitatDie Kommunikation mit dem Background-thread erfolgt über 2 Queues
Wieso über 2 Queues? Wenn man mit Threads arbeitet sollte doch auch eine "Kommunikation" über shared vriablen möglich sein?
Mal ganz abgesehen davon, dass das zu lösende Problem (asynchrones Abarbeiten von Requests) an sich die Verwendung von Queues praktisch schon aufdrängt (irgendwo müssen die noch nicht abgearbeiteten Requests ja hin...), ist das Verwenden von über Queues gekoppelten Workern ein sehr bewährtes Pattern das hilft viele beim Multithreading mögliche Laufzeitfehler zu vermeiden. Wenn man shared Variablen direkt benutzt muss man sich um alles, was mit Multithreadding zu tun hat, selber kümmern (und darf alle dabei möglichen Fehler selber machen ;-) ). Schau Dir mal an, was Plattformen mit guter Unterstützung von Multithreadding so mitbringen (z.B. den Executor in Java). Da findest Du dieses Pattern (mit gutem Grund) überall wieder.

Gruß,

Norbert

while (!asleep()) {sheep++};

Dirk

Hallo Norbert,

Danke für deine Infos.
ZitatNaja, man kann ja nicht einfach davon ausgehen, dass jeder sein Perl mit Threadunterstützung compiliert hat.
Das stimmt natürlich.

Zitatdann natürlich (bei den kurzen, protokollbedingten Delays) wieder blockierend.
Und genau diesen Fall möchte ich auf jeden Fall verhindern.
Also fällt Threads schon mal weg. Bleibt also noch das Forken vom FHEM-Prozess, so mache ich das im Moment ja schon, nur das werde ich dann so umstellen, dass das es nur einen Parallelen Prozess dafür gibt. Und dieser übernimmt dann die Kommunikation.
Die alternative ist noch ein kleiner separater Perl-Prozess der diese Kommunikation ausschließlich übernimmt. Vor allem auf Systemen mit wenig RAM, muss dieser Prozess ja nicht das ganze FHEM-Enviroment mit sich "rumschleppen". Daher favorisiere ich derzeit diese Version.

Gruß
Dirk

ntruchsess

perl-threads sollten möglichst früh nach Prozessstart erzeugt werden, damit sie nicht unnötigen Ballast (in Form von belegtem Ram) mitkriegen. Wenn man Threads verwendet, dann sollten die entsprechenden Devices möglichst weit 'vorne' beim FHEM-start angeordnet sein. Forken ist vom Ram-bedarf her natürlich nicht besser.

ein kleiner separater perl-process der die Requests asynchron abarbeitet kann im Prinzip exakt so wie mein Worker-thread implementiert sein (Nur mit einer Socket-verbindung zwischen FHEM und dem Worker-prozess anstelle der beiden Queues). Das ist genau das gleich Pattern. Viel Erfolg dabei! :-)

Gruß,

Norbert
while (!asleep()) {sheep++};

ntruchsess

Zitat von: Dirk schrieb am Mi, 28 August 2013 11:38
Zitatdann natürlich (bei den kurzen, protokollbedingten Delays) wieder blockierend.
Und genau diesen Fall möchte ich auf jeden Fall verhindern.

Hallo Dirk,

im Prinzip habe ich das mit dem separaten Daemon-Prozess auch mal durchdacht - das wäre im Prinzip im Hinblick auf Zeitverhalten und Resourcenverbrauch eigentlich optimal (wenn man vom Kommunikationsoverhead über Sockets absieht). Nur wollte ich mich erst mal nicht mit der Steuerung des parallel laufenden Prozesses belasten (Themen wie automatisches Reconnect usw. sollen dann ja stabil gelöst sein), also habe ich das erst mal mit Threads gemacht. Lässt sich mit der gewählten Architektur (Kommunikation ausschließlich über Queues) ja vergleichsweise einfach in Multiprozessing umbauen. Wäre schön, wenn Du die Kommunikation und Multiprozesssteuerung so wiederverwendbar schreiben würdest, dass ich das einfach auf mein asynchrones OWX-modul übertragen könnte.

Gruß,

Norbert

while (!asleep()) {sheep++};

Dirk

Hallo Norbert,

ZitatWäre schön, wenn Du die Kommunikation und Multiprozesssteuerung so wiederverwendbar schreiben würdest, dass ich das einfach auf mein asynchrones OWX-modul übertragen könnte.
Diese Idee unterstütze ich.
Ich mache hier von FHEM explizit keinen Fork, da ich hier gerne auch etwas Ressourcen sparen möchte. Allerdings benutze ich einige Bibliotheken von FHEM.

Aktuell habe ich ein "server.pl" File welches einen lokalen Socket öffnet und Befehle per TCP entgegen nimmt. Darüber läuft dann die Kommunikation mit FHEM. Ich benutze hierfür TcpServerUtils.pm
Eine zweite Verbindung wird zu einem seriellen Device geöffnet. Hier kommen die Funktionen aus DevIo.pm zur Anwendung. Daher werden hier alle darin funktionierenden Verbindungen unterstützt.

Im Prinzip läuft im "server.pl" ein Loop, welcher beide FD's jeweils mit Select abfragt.
Alles zeitkritische und blockierende läuft hier parallel zu FHEM dann in diesem Prozess.

Das "server.pl" stellt das "Programm" für den neuen Prozess dar. Hier drin erfolgt auch die Konfiguration für die jeweilige spezifischen Anforderungen.
Alle wieder verwendbaren Funktionen, auch "sub main" mit der Schleife, sind aktuell in DevicedTools.pm. (Ein besserer Name ist mir aktuell nicht eingefallen)

ZitatNur wollte ich mich erst mal nicht mit der Steuerung des parallel laufenden Prozesses belasten
Man muss mal sehen was man hier steuern muss.
Eigentlich muss man ja "nur" dafür sorgen dass der Prozess gestartet und beendet wird. Das hatte ich vor im zugehörigen FHEM-Modul in DefFn und in UndefFn zu erledigen.

Beim Absturz von FHEM bleibt der Prozess dann natürlich "verweist". Da brauchts noch eine Idee. Ggf. beim Start von FHEM nach entsprechenden bereits laufenden Prozessen suchen und dann killen bzw. einfach weiter verwenden.

ZitatThemen wie automatisches Reconnect usw. sollen dann ja stabil gelöst sein
Ja Klar

Wie du siehst gibt es noch ein paar offene Punkte. Ideen nehme ich gerne mit auf :)
Ich schicke dir mal meinen Code wenn ich soweit fertig bin. Dann kann man den sicher noch etwas verfeinern.

Da ich hier im Moment auch einige Funktionen benutzen möchte, welche sich derzeit nur in fhem.pl befinden wurde ich vorschlagen diese Funktionen (Log, Timer, usw) in eine allgemeine Util.pm uder ähnlich auszulagern. Wenn ich mit der Entwicklung hier fertig bin, würde ich das Thema noch mal besprechen wollen.

Gruß
Dirk

justme1968

wäre es nicht passend BlokingCall so zu erweitern das die komminkation mit dem parent nicht mehr (unbedingt) über die telnet verbindung geschehen muss sondern über ein socket? socketpair bietet sich hier geradezu an.

ich finde es vor allem wichtig alles was das hauskeeping beim forken angeht so gut wie möglich an einer stelle (und das ist bisher BlockingCall) zu sammeln. und an keiner stelle in BlockingCall ist vorgegeben das der gestartete child prozess nicht 'ewig' laufen darf.

wenn der child prozess eine verbindung zum parent hat sollte er auch bemerken können wenn der parent nicht mehr da ist und sich selber beenden. alles andere wie suchen beim neustarten und beenden ist fehleranfällig und nicht zu verlässig.

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

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

Dirk

Zitatwäre es nicht passend BlokingCall so zu erweitern
Das wollte ich eben nicht.
Wenn wir FHEM forken dann schleppen wir in der Kopie jede Menge Code mit rum den man warscheinlich gar nicht benötigt und unnütz Speicher belegt. Vor allem kleineren Systemen wie der FritzBox würde das entgegen kommen. Daher ist die Idee einen leichtgewichtigen Service zu bauen, der nur die Schnittstelle zu der Hardware bereitstellt.

Zitatalles andere wie suchen beim neustarten und beenden ist fehleranfällig und nicht zu verlässig.
Theoretisch muss der Prozess beim Neustarten von FHEM dann gar nicht mal beendet und neu gestartet werden. Dieser könnte einfach weiterlaufen und FHEM benutzt diesen dann weiter wenn es merkt dass es noch läuft.
Der Vorteil währ, der Prozess könnte weiterhin Nachrichten von der Hardware empfangen und für FHEM ggf. buffern. So würden bei Absturz / Neustart von FHEM hier auch keine Nachrichten verloren gehen.

Gruß
Dirk

rudolfkoenig

Ich moechte BlockingCall als solches belassen: fuer Funktionsaufrufe, die laenger blockieren koennen, und wo der Autor sich erstmal nicht die Muehe machen will, Daten per select blockierungsfrei einzulesen. Ich sehe BlockingCall als QuickHack, nicht als richtige Loesung.

Dienste, die Zeitkritisches erledigen wollen, und die sich von FHEM wg. diversen Gruenden (Benutzer gestartetes sleep, langwierige SVG-Berechnung, etc) nicht ausbremsen lassen wollen, wuerde ich als separaten Server implementieren, also so, dass die auch eigenstaendig weiterlaufen, und sie FHEM als Client bedienen.

Bin gerne bereit FHEM so umzubauen, dass das leichter zu implementieren ist, aber nicht vor dem Release.

justme1968

das mit dem unnützen speicher würde ich zumindest erst mal verifizieren. das forken alleine verdoppelt ja nicht den speicherbedarf.

etwas kleines und leichtes unabhängig von fhem zu haben das aufgaben als extra server/daemon/dienst übernimmt ist sicher ein guter weg. mir ging  es nur um den das starten und herstellen der kommunikation fhem - dämon. das sollte an einer zentralen stelle passieren. ich denke blockingcall wäre gut weil ich auch für die bisherigen aufgaben die telnet version als nicht optimal ansehe.

ZitatTheoretisch muss der Prozess beim Neustarten von FHEM dann gar nicht mal beendet und neu gestartet werden. Dieser könnte einfach weiterlaufen und FHEM benutzt diesen dann weiter wenn es merkt dass es noch läuft.
Der Vorteil währ, der Prozess könnte weiterhin Nachrichten von der Hardware empfangen und für FHEM ggf. buffern. So würden bei Absturz / Neustart von FHEM hier auch keine Nachrichten verloren gehen.

die idee finde ich sehr gut.

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

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

Dirk

ZitatBin gerne bereit FHEM so umzubauen, dass das leichter zu implementieren ist, aber nicht vor dem Release.
Natürlich nicht :)

Ich würde meine derzeitige Lösung die Tage dann mal hier veröffentlichen. Ich vermute damit werden wir der Gesamtlösung sicher noch nicht genüge getan haben. Aber das kann man sicher als weitere Diskussionsgrundlage nehmen können.

Bisher habe ich die Logging- und die Timer-Funktionen ausgemacht, welche man aus fhem.pl auslagern könnte.
Ach, DoTrigger noch.

Ggf. auch den Mainloop. Somit könnte man sich überlegen ob man selbst fhem.pl dann auf einer Zentralen "serverTools.pm" oder wie auch immer die dann heißt aufsetzen lassen könnte.

Gruß
Dirk

justme1968

oben gab es den satz das dies der erste fall ist bei dem sich das problem nicht ohne einen zweiten prozess lösen lässt. ich denke es gibt inzwischen mindestens einen zweiten fall. die augenblickliche owserver nonblocking implementierung ist leider völlig wirkungslos (Link)und es gibt meiner meinung nach auch keine lösung ohne eigenen prozess da alle werte immer vom owfs geholt werden und man ohne diesen prozess das blockieren nicht in den griff bekommt.

ich habe inzwischen eine prototypische lösung mit einem nonBlockingCall der es erlaubt den geforkten prozess einmal zu starten und laufen zu lassen und die dann mit zwei pipes bidirektional zwischen parent und child kommuniziert. in parent und child läuft alles mit selectlist und ReadFn. parent und child erkennen auch wenn eine seite sich beendet und reagieren mit neustart des childs bzw. mit selber beenden.

im unterschied zum normalen BlockingCall werden nach dem forken alle filedescriptoren geschlossen und alle anderen module per undefine beendet und gelöscht. es gibt zwar noch ein paar kleinere unsauberkeiten aber nichts was sich nicht mit ein paar konventionen lösen ließe.

bevor ich jetzt einfach weiter mache würde ich gerne noch mal anregen die drei unterschiedlichen ansätze (dirk,rudi und meinen) zu vergleichen und schauen welche einsatzgebiete sich jeweils abdecken lassen. icha möchte zwar erst mal nur owserver blockierungsfrei zum laufen bekommen aber dafür das rad nicht neu erfinden bzw natürlich infrastruktur die demnächste kommen könnte mit nutzen. z.b. eine generelle ReadFromParentFn die im child statt der ReadFn bedient wird oder ein konstrukt das wie ParseFn funktioniert.

das vor allem rudis ansatz ein grösserer umbau ist und erst nach 5.5 kommt ist klar. aber vielleicht hast du ein paar minuten und kannst etwas genauer beschreiben wie es dann aussehen könnte. vor allem wie genau das starten der neuen 'server' funktioniert wenn z.b. zur laufzeit ein neues device definiert wird das so einen server braucht oder sogar zwei die zwar den selben server aber eben nicht genau den gleichen sondern eben eine zweite instanz brauchen.

vielleicht ist es auch eine option bei so einem umbau auch den normalen fhem prozess in zwei teile zu teilen. einen 'monitor' der alle anderen teile startet und niemals blockiert :) und einen (bzw mit den servern dann mehrere) weitere die die arbeit machen.

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

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

rudolfkoenig

> aber vielleicht hast du ein paar minuten und kannst etwas genauer beschreiben wie es dann aussehen könnte

Idee: ein Modul, der externe Prozesse startet, aber auch Perl-Programme aus FHEM/xxx. Per Attribut kann man shutdown/startup Verhalten definieren, oder Ueberwachungsmethode (kill -0, tcp/port, etc). fhem.pl muesste erweitert werden, damit die Ladereihenfolge solcher Module beeinflusst werden kann. Prozesse aus FHEM/xxx kommunizieren per TCP/IP mit dem Haupt-FHEM, und sollten eine zusaetzliche,"tote" TCP/IP Verbindung fuer die Ueberwachung ermoeglichen.

Das Teilen von FHEM sehe ich nocht nicht, da der Datenaustausch bzw. Verteilung (Readings/Events/usw) in meinen Augen zu aufwendig ist. Es kommt dazu, dass ein geforkter perl Prozess mehr Speicher belegt, als man denkt, das uebliche COW funktioniert nur teilweise. Das ist nur eine Beobachtung aus anderen Projekten mit grossen (GB+) Datenmengen, wenn jemand hier mehr weiss, dann her damit.

Dirk

Hallo Zusammen,

ich bin mit meiner Implementation schon ziemlich weit.
Der HM485-Interface-Server läuft.

Hier ein Paar Informationen zur aktuellen Implementation:

Ich habe ein Modul ServerTools.pm erstellt. Dieses stellt allgemeine Funktionen zur Verfügung.
ServerTools.pm wiederrum nutzt DevIo.pm und TcpServerUtils.pm
Im Prinzip stellt ServerTools die Initialisation von einem Serial-Device (DevIo) und dem TCP-device (TCPServerUtils) zur Verfügung und den Main-Loop, der dann beide Devices per Select "abholt" bzw. Daten zu den Devices schickt.
Somit kann man hiermit einen kleinen Server bauen, der die Brücke zwischen "dummer" Hardware und FHEM darstellt. in diesem Server können dann Zeitaufwendige Sachen erledigt werden ohne FHEM zu blockieren bzw. zeitkritische Aufgaben erledigt werden, ohne dass FHEM bei der Abarbeitung "stört".

Der eigentliche Server-Prozess ist eine eigenständige .pl Datei welche die ServerTools einbindet. Hier habe ich auf einen Fork Verzichtet, um eine "leichtgewichtigen" Prozess zu Verfügung zu haben. Hier drin werden dann auch alle Gerätespezifischen Funktionen abgearbeitet.

Achja: In Servertools ist derzeit eine "Abgespeckte" Kopie von Log3. Daher währ die Idee Log3 aus fhem.pl in ein eigenes Modul auszulagern. Ggf. zusammen mit anderen oft verwendeten Funktionen in eine Utils.pm ö.ä. Dann könnte man die hier direkt verwenden. Das vermeidet Codeduplizität.

Das Starten des Prozesses passiert bei mir im Moment noch im zugehörigen FHEM-Modul (HM485_INTERFACE.pm. Das könnte man nach Rudis Vorschlag auch noch allgemein zur Verfügung stellen.

Eine Prozessüberwachung erfolgt hier im Moment "nur" bei einem FHEM-Start über ps und entsprechenden commandline Vergleichen. Somit wird ein bereits laufender Prozess erkannt. Der Start des Prozesses erfolgt im Moment mit system(), so dass der Prozess bei einem "normalem" Ende von FHEM mit terminiert wird. Bei einem möglichem Abstuz von FHEM dann aber nicht. bei einem Neustart wird der bereits laufende Prozess aber erkannt und "wiederbenutzt". Wie gesagt, die Prozessüberwachung muss hier noch ausgebaut werden.

Grundsätzlich ist der HM485-Interface-Server so ausgelegt, dass dieser Unabhängig von FHEM laufen kann. Nicht abgeholte Nachrichten werden im Server gepuffert und an den Client ausgegeben sobald er wieder verbunden ist.

Hiermit will ich erstmal anfragen, ob sich hier schon mal jemand das ganze ansehen möchte.
Die Module sind zwar noch nicht ganz fertig, funktionieren aber schon soweit, und ich denke die kann man schon mal als Diskussionsgrundlage heranziehen.

Gruß
Dirk

justme1968

ich würde gerne mal schauen bzw. versuchen es mit/in OWServer zu verwenden.

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

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

Dirk

Hi Andre,

Anbei mal das Archiv.
Das so einfach ins FHEM-Verzeichniss entpacken.
Hier sind auch 2 Symlinks mit dabei.

in FHEM/HM485/SerialServer/HM485_SerialServer.pl ist der Server-Process.
Der Name wird noch geändert

Zum Testen musst du das Modul HM485-Serial definieren:


# Die Definition des Seriellen Devices (auf localhost:2000 hört der HM485_SerialServer.pl, shiehe unten)
define HM485_Interface HM485_INTERFACE localhost:2000

# Dieses Attribut ist zum Starten, Stoppen und Überwachen des Serverprozesses Gedacht.
# Derzeit wird hier nur der Start mit FHEM gesteuert. Die Überwachung ist noch nicht komplett.
# Ohne bindSerialServer 1 wird erwartet das der Server bereits läuft.
attr HM485_Interface bindSerialServer 1

# Hiermit wird das externe Interface mit dem der Server sich verbindet, konfiguriert
# Alternativ /dev/ttySX oder ähnlich
attr HM485_Interface server_device 192.168.178.15:5000

# Logattribute für den Server
attr HM485_Interface server_logVerbose 4

# ggf. eigenes Logfile für den Server
#attr HM485_Interface logFile /opt/FHEM/fhem.dev/log/hm485SerialServer.log


Der Serverprozess hat ggf. noch weitere Parameter. Diese sind alle per POD verfügbar und über hm485SerialServer.log --help bzw. hm485SerialServer.log --man Abrufbar. All diese Parameter sind dann aber vom Autor des Server-Entwicklers zu implementieren. Das macht ServerTools nicht.

Zum Testen hört der Server auf allen Interfaces. Ich bin mir nicht sicher ob man das so lassen soll, oder ob man aus Sicherheitsgründen das ganze auf localhost beschränken sollte.

Damit solltest du das dann schonmal testen können.

Gruß
Dirk

Update: File gelöscht.

Dirk

Solange das Modul noch nicht im FHEM-SVN ist, ist die aktuelle Version hier:
https://github.com/kc-GitHub/FHEM-HM485

Gruß
Dirk

ntruchsess

ich habe hierzu auch mal angefangen das generisch zu implementieren: AsyncDevice.pm.
Idee: Fhem öffnet einen ServerSocket, forkt einen Kind-prozess, dieser räumt intern alles auf, was nicht zum eigenen Device gehört und verbindet (dauerhaft) zum ServerSocket. Dabei werden sowohl im Parent, als auch im Child temporäre Devices für die Socket-kommunikation angelegt, damit das eigentliche Device im Childprozess sein I/O weiterhin ungestört über die selectlist abwickeln kann und der Modulentwickler sich um solche Details nicht kümmern muss.

- Norbert
while (!asleep()) {sheep++};