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