In der Vergangenheit habe ich mit dem Modul SubProcess.pm experimentiert. Leider gab es in Praxis Performance Probleme, wenn sehr viele Daten vom Subprozess an den Parent geschickt wurden. Daher habe ich mir überlegt, es mal mit Threads zu versuchen und für den Datenaustausch nicht wie SubProcess.pm Sockets zu verwenden sondern Shared Memory Queues, die das Modul Thread::Queue bereitstellt. Die Queues fangen hohes Datenaufkommen ab. Die Daten können verarbeitet werden, wenn "Zeit dazu ist".
Das Modul benötigt die Perl Module threads und Thread::Queue.
Ich würde das Modul gerne einchecken, wenn nichts dagegen spricht, um es auch in meinen Modulen HMCCUxxx verwenden zu können. Falls ein Einchecken nicht erwünscht ist (weil es ein Hilfsmodul ist), müsste ich die Funktionalität direkt in meinen Modulen einbauen. Ich denke jedoch, auch andere Entwickler könnten das Modul hilfreich finden.
Die Funktionsweise von SubThread.pm ist an die von SubProcess.pm angelehnt. Hier eine schnelle Übersicht der Funktionen:
new ({ onRun => \&OnRunFunction, onExit => \&OnExitFunction, termsig => "Signal", hash => $hash })
Legt ein neues SubThread Objekt an. Der Parameter onRun ist Pflicht, die anderen sind optional. Die Funktionen On... bekommen beim Aufruf als einzigen Parameter eine Referenz auf das SubThread-Objekt übergeben. Der Parameter termsig legt das Signal fest, das die Funktion terminate() an den Workerthread schickt, z.B. 'INT'. Wenn termsig nicht angegeben wird, hat terminate() keine Funktion. Mit dem Parameter hash kann der Hash eines FHEM Devices übergeben werden. Auf diesen kann in den onRun und onExit Funktionen per $worker->{hash} zugegriffen werden, wobei $worker der Funktionsparameter ist.
Das erstellte Objekt sollte im Master-Prozess (FHEM) z.B. im Hash des aufrufenden Device gespeichert werden, Beispiel:
$hash->{helper}{thread} = SubThread->new ({ onRun => \&X_WorkerRun, onExit => \&X_WorkerExit, termsig => 'INT', hash => $hash });
workerThread ()
Gibt eine Referenz auf das eigentliche Thread-Objekt des Moduls threads zurück.
isRunning (), isDetached ()
Prüfen ob der Workerthread läuft oder ob er vom Master-Prozess (FHEM) abgekoppelt ist.
run ()
Workerthread starten. Es wird die mit onRun definierte Funktion aufgerufen und anschließend, sofern angegeben, die mit onExit definierte Funktion. Gibt 0 zurück, wenn der Workerthread nicht gestartet werden konnte. Sonst die ID des Workerthreads (Integer > 0).
terminate ()
Schickt das mit terminate definierte Signal an den Workerthread. Dieses Signal sollte in der onRun Funktion abgefangen und die onRun Funktion beendet werden. Beispiel:
sub MyWorkerThread () {
my $worker = shift;
my $hash = $worker->{hash};
my $run = 1;
$SIG{'INT'} = sub { $run = 0; };
while ($run) {
# do things ...
my $data = ...
$worker->sendToMaster ($data);
}
}
detach ()
Entkoppelt einen Workerthread vom Master-Prozess (FHEM). Nützlich, um Fehlermeldungen beim Beenden von FHEM zu vermeiden, wenn noch Workerthreads aktiv sind. Die Funktion terminate() entkoppelt den Workerthread automatisch.
signal ($sig, $detach)
Schickt das Signal sig an den Workerthread. Wenn detach = 1 ist, wird der Workerthread vom Master-Prozess entkoppelt.
pendingData ()
Prüft, ob Daten in der Queue vorhanden sind. Wenn diese Funktion im Master-Prozess aufgerufen wird, prüft sie, ob Daten vom Workerthread anstehen. Bei Aufruf im Workerthread wird geprüft, ob Daten vom Master-Prozess anstehen. Im Master-Prozess kann pendingData () z.B. in einer X_Ready() Funktion verwendet werden. Voraussetzung ist, dass der Hash des aufrufenden Moduls in den Hash %readyfnlist eingetragen wird. Beispiel:
sub X_Ready () {
return $hash->{helper}{thread}->pendingData ();
}
sub X_Read () {
while (my $item = $hash->{helper}{thread}->receiveFromWorker ()) {
# Process data
}
}
sendToMaster ($data)
Wird vom Workerthread aufgerufen und schreibt data in die Queue des Master-Prozesses. Dieser kann die Daten mit receiveFromWorker () lesen.
receiveFromMaster ()
Wird vom Workerthread aufgerufen und liest Daten aus der Queue des Workerthreads, die der Master-Prozess mit sendToWorker () bereitgestellt hat.
sendToWorker ($data)
Wird vom Master-Prozess aufgerufen und schreibt data in die Queue des Workerthreads. Dieser kann die Daten mit receiveFromMaster () lesen.
receiveFromWorker ()
Wird vom Master-Prozess aufgerufen und liest Daten aus der Queue des Master-Prozesses, die der Workerthread mit sendToMaster () bereitgestellt hat.
Die Funktionen sendToWorker() und receiveFromMaster() können auch verwendet werden, um dem Workerthread zu signalisieren, dass er sich beenden soll. In diesem Fall könnte der Master-Prozess ein 'STOP' an den Workerprozess schicken, auf das dieser mit dem Verlassen der onRun Funktion reagiert.
Ich habe gerade perlthrtut durchgelesen, und meine urspruenglichen Bedenken gegen Threads in FHEM sind jetzt groesstenteils weg. Was mir unklar war, dass Variablen nur nach explizitem :shared Schluesselwort gemeinsam sind, alles andere wird kopiert.
Bei den Signalen bin ich noch nicht ganz sicher, es heisst, je nach Implementierung ist nicht festgelegt, welcher Thread die (echten, nicht "thread"-) Signale kriegt. Das wuerde bei den (vmtl. kaum genutzen) SIGHUP zu falschen Ergebnissen fuehren, wenn man sie nicht zum Hauptprozess weiterleitet. Ob es bei ALRM richtig funktioniert, weiss ich nicht, das ist vmtl. auch kaum relevant. Wenn doch, dann aber schwer zu debuggen.
Noch ein Punkt: manche Perl Installation unterstuetzen keine Threads.
Ich habe trotzdem nichts dagegen, wenn es eingecheckt wird, haette aber gerne die Meinung von Anderen gehoert.
@zap: ganz unabhängig vom für und wieder zu threads und prozessen: auf den ersten blick verstehe ich nicht warum dieses modell so viel schneller sein soll als die lokalen sockets.
nach meinem verständniss:
- die socket version wird in die fhem event loop eingehängt, sobald daten da sind wird ReadFn automatisch aufgerufen. d.h. eingehende daten werden so schnell wie möglich verarbeitet. wenn der rechner zu langsam ist für die menge der daten macht er eventuell nicht mehr viel anderes
- in der thread version werden die daten nicht automatisch verarbeitet sondern man muss irgendwie selber regelmäßig pendingData() aufrufen und dann verarbeiten. daten werden nur im 'pooling' intervall verarbeitet (d.h. potentiell verzögert), es ist natürlich noch zeit für anderes aber wenn der rechner zu langsam ist füllt sich die queue.
stimmt das? oder verstehe ich etwas völlig falsch?
und die lokalen sockets sind doch auch nicht viel mehr als ein shared memory fifo pro richtung bei dem einer schreibt und einer liest.
gruss
andre
Zitat von: justme1968 am 01 Januar 2017, 16:15:53
@zap: ganz unabhängig vom für und wieder zu threads und prozessen: auf den ersten blick verstehe ich nicht warum dieses modell so viel schneller sein soll als die lokalen sockets.
Nicht schneller. Durch die Verwendung von Shared Memory Queues für den Datenaustausch können aber mehr Daten gepuffert werden, sodass große Datenmengen nicht zu Datenverlust führen. Bei den Sockets habe ich sogar mit diversen TCP Parametern auf Linux Ebene experimentiert. Dadurch konnte ich das Problem etwas entschärfen, jedoch nicht komplett eliminieren. Immer wenn der Child-Prozess eine großen Menge Daten in das Socket-Handle geschrieben hat, kam es zu I/O Fehlern und Daten gingen verloren. Ich hätte alles mühsam zwischenspeichern und nach und nach nochmal an den Parent-Prozess schicken müssen.
Zitat
- in der thread version werden die daten nicht automatisch verarbeitet sondern man muss irgendwie selber regelmäßig pendingData() aufrufen und dann verarbeiten. daten werden nur im 'pooling' intervall verarbeitet (d.h. potentiell verzögert), es ist natürlich noch zeit für anderes aber wenn der rechner zu langsam ist füllt sich die queue.
Nein, die Daten werden nicht verzögert verarbeitet. In der Thread Version nutzt man den X_Ready()/X_Read() Mechanismus. D.h. das Thread nutzende Modul/Device trägt sich in %readfnlist ein. FHEM ruft in seinem Loop dann die X_Ready() auf. In X_Ready() wird per pendindData() geprüft, ob Daten anliegen. Wenn die Rückgabe wahr ist, ruft FHEM X_Read() auf. Beispiel s.o. bei der Beschreibung von pendingData().
Zitat
und die lokalen sockets sind doch auch nicht viel mehr als ein shared memory fifo pro richtung bei dem einer schreibt und einer liest.
Im Prinzip ja, jedoch nur mit eingeschränktem Einfluss auf die Größe des I/O Puffers.
hmmm... ich glaube ich verstehe es immer noch nicht ganz.
zum einen verstehe ich nicht wie daten verloren gehen können. wenn man auf einer seite etwas in das socket steckt ist garantiert das sie auf der anderen seite ankommen. auf der schreibenden seite muss man bei einem blockierenden socket garnichts weiter tun, bei einem nicht blockierenden muss man schauen ob das socket 'voll' ist und gegebenenfalls warten. bei beidem muss man nicht zwischenspeichern.
und zum anderen hängt die readyFn nicht direkt im select sondern wird je nach platform (windows alle 0.1 sekunden, unix alle 5 sekunden) aufgerufen. unabhängig davon ob daten da sind. d.h. unter linux würden bei einem fhem das keine anderen sockets offen hat die per select überwacht werden würde die readyFn nur alle 5 sekunden aufgerufen. d.h. nicht sobald daten da sind sondern verzögert. das ist im prinzip pollen.
ein socket das direkt in die %selectlist eingehängt wird triggert dagegen die readFn automatisch sofort sobald daten da sind.
wie viele daten sind denn viel?
Zitat von: justme1968 am 01 Januar 2017, 17:39:21
zum einen verstehe ich nicht wie daten verloren gehen können. wenn man auf einer seite etwas in das socket steckt ist garantiert das sie auf der anderen seite ankommen. auf der schreibenden seite muss man bei einem blockierenden socket garnichts weiter tun, bei einem nicht blockierenden muss man schauen ob das socket 'voll' ist und gegebenenfalls warten. bei beidem muss man nicht zwischenspeichern.
Die Situation ist so: Ich starte einen RPC-Server in einem Subprozess, der Daten von einem externen Gerät (Homematic CCU) empfängt. Sobald der RPC-Server Daten bekommt, muss er sie in den Socket schreiben. Alternative wäre zu puffern. Problem: Direkt nach dem Start des RPC-Servers schickt die CCU Daten für alle ihre Geräte. Wenn man viele Geräte hat, kommen da schnell 7-10 Kb zusammen. Beim Wegschreiben an den Parent per Socket kommt es dann zu Write Errors. Im "Normalbetrieb" bei gelegentlichen Updates von der CCU funktioniert alles.
Zitat
und zum anderen hängt die readyFn nicht direkt im select sondern wird je nach platform (windows alle 0.1 sekunden, unix alle 5 sekunden) aufgerufen. unabhängig davon ob daten da sind. d.h. unter linux würden bei einem fhem das keine anderen sockets offen hat die per select überwacht werden würde die readyFn nur alle 5 sekunden aufgerufen. d.h. nicht sobald daten da sind sondern verzögert. das ist im prinzip pollen.
ein socket das direkt in die %selectlist eingehängt wird triggert dagegen die readFn automatisch sofort sobald daten da sind.
wie viele daten sind denn viel?
Mist. Das mit den 5 Sekunden wußte ich nicht. In dem Fall müsste ich doch pollen. Oder ich missbrauche %selectlist als Trigger. Hat jetzt aber alles nicht direkt etwas mit dem generellen Ansatz zu tun, Threads in FHEM zu unterstützen.
nein. hat alles nicht prinzipiell etwas mit threads zu tun.
aber so lange es um einen einzigen parallelen code pfad geht sollte alles genau so einfach auch ohne threads gehen.
der default socket buffer sollte 4k groß sein und sich bis auf 65k vergrößern lassen. das sollte also eigentlich gehen. ist es wirklich so das die daten von der ccu schneller übers netz kommen als fhem sie verarbeiten kann? übers netz kommen die daten doch auch in mehreren häppchen oder?
sind das beim schreiben wirklich fehler? oder nur der hinweis das das socket 'voll' ist und man warten soll? das selber buffern sind zwei zeilen perl.
was passiert wenn du das socket vom child zum parent blockierend machst? dann wird automatisch gewartet.
Ich hatte den Puffer vergrößert und auch diverse Timeouts hochgestuft. Richtig zuverlässig hat das alle nicht funktioniert. Die CCU schickt leider direkt nach der Registrierung des RPC Servers alle ihre Devices mit allen Kanälen in einem grossen RPC Request an den Server. Dazu kommt, dass der Server selbst nicht multithreaded ist (RPC::XML::Server Funktion server_loop()). Während der Verarbeitung der Daten nimmt er keine neuen entgegen. Wenn das zu lange dauert wg der Pufferung gibt es Fehler in der CCU. Im Extremfall hört sie sogar auf, weiter Daten zu schicken.
Also alles sehr zeitkritisch. Daher gehe ich derzeit den Umweg über Filequeues. übrigens wird jeRPC Schnittstelle der CCu ein Prozess/Thread gestartet. Das können bis zu 4 sein. Die Umstellung auf Threads hätte 2 Vorteile:
1. Threads benötigen weniger Speicher, da nicht jedes Mal der komplette fhem Prozess geklont wird
2. Ich komme von den Filequeues Weg (ok, ggf kann man Thread::Queue auch ohne Threads einsetzen, habe ich nicht getestet)
Eventuell kann ich auch das Polling durch den FHEM Standard IO ersetzen. Das ist durch die X_Ready Thematik mit den 5 Sekunden allerdings fraglich.
anyway: das eine oder andere Modul nutzt ja bereits Threads. Aber wenn das Hilfsmodul nicht gewünscht ist, baue ich das eben direkt in mein Modul ein (klingt jetzt vielleicht etwas frustriert, ist aber nicht so gemeint. Ich sehe das wirklich leidenschaftslos)
P.S. das mit den 5 Sekunden macht aber FHEM unter Windows weitgehend unbrauchbar. Da wird ja X_Ready zwingend verwendet, wenn ich das richtig verstanden habe
ich persönlich sehe mehr Vorteile als Nachteile mit der Unterstützung von Threats
mögliche Szenarien sehe ich wie folgt:
Umstellung der IO Devices auf Threats --> weniger Timingprobleme
HTTP Calls --> entspannteres Timing bei langsamen/offline Seiten (Blocking Probleme)
Sonos --> Entfall vom Neustart des Forks, wenn Änderungen am Device vorgenommen wurden
Workaround für Windows evtl. notwendig; Fritzbox wird offiziell nicht mehr unterstützt
Wie Rudi auch schon schrieb, Debugging kann wirklich Freude bereiten
Zitatder default socket buffer sollte 4k groß sein und sich bis auf 65k vergrößern lassen.
Auf einem Ubuntu VM schauen die Zahlen anders aus (min, default, max):
$ cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 4194304
Unter OSX scheint es auch groesser zu sein:
# sysctl net.inet.tcp.recvspace
net.inet.tcp.recvspace: 131072
Zitat1. Threads benötigen weniger Speicher, da nicht jedes Mal der komplette fhem Prozess geklont wird
Tippe auf das Gegenteil. Laut perlthrtut werden fuer perl-threads alle Perl-Variablen kopiert (was mich beruhigt :) ). Beim fork/clone wird COW verwendet, ich vermute, dass bei perl-threads diese Technik (wg. zu teuer, da keine MMU-Unterstutzung) nicht gemacht wird. Zahlen wurden mich wirklich interessieren, wie man sowas "richtig" misst, ist mir aber nicht ganz klar, vlt. reicht ein free.
ZitatAber wenn das Hilfsmodul nicht gewünscht ist
Ich habe das nicht gesagt, und auch (soweit ich es verstehe) andre nicht. Es waere nur nett zu verstehen, warum das Problem mit den herkoemmlichen Mtteln wie SubProcess oder BlockingCall nicht realisiert werden kann, evtl. koennen wir das dann fixen.
ZitatP.S. das mit den 5 Sekunden macht aber FHEM unter Windows weitgehend unbrauchbar. Da wird ja X_Ready zwingend verwendet, wenn ich das richtig verstanden habe
Unter Windows wird ReadyFn zum Pruefen der seriellen Schnittstellen verwendet, da diese nicht mit select geprueft werden koennen. Hier ist der Timeout maximal 0.1s. Auf anderen Plattformen ist er maximal 5s, da ReadyFn hier (etwas vereinfacht) nur zum wiederauffinden ausgesteckter USB-Geraete verwendet wird. Maximal heisst: wenn auf den per select ueberwachbaren Verbindungen nichts passiert.
ich bin nicht generell gegen threads und ich habe auch nicht prinzipiell etwas gegen ein generelles hilfsmodul.
aber ich sehe threads eher fhem intern für die diversen io devices die timing kritisch sind und um blockieren generell zu vermeiden.
in der reinen kommunikation zwischen instanzen sehe ich nicht warum threads besser sind als zwei prozesse. bzw. wenn sie es tatsächlich ob es auf fhem seite ein problem gibt das wir beheben sollten.
was den speicherverbrauch oder die geschwindigkeit beim forken angeht: ich glaube nicht das dies für aktuelle betriebssysteme auf aktuellen rechner für diesen anwendungsfall (ein permanenter nebenläufiger codepfad) relevant ist.
ohne das es negativ klingen oder zu frustration führen soll: threads haben (manchmal) ihre daseinsberechtigung. aber ich habe schon mehr als einmal gesehen das threads als das allheilmittel mittel angesehen wurden die sie dann nicht waren weil das problem konzeptionell ganz woanders lag. manchmal haben threads das eigentliche problem auch nur zugekleistert und noch schwieriger zu finden gemacht. sehr vieles ist mit 'altmodischen' und einfacher zu debuggenden methoden genau so möglich. oder den effekt den man durch die threads sieht ist ein zufälliger nebeneffekt der konventionell genau so umsetzbar wäre.
einem wirklich vorteil von threads sehe ich in der kommunikation der haupt instanz mit möglichen io instanzen über shared memory. aber nur wenn das global für alle ios und ohne polling umgesetzt wird. und das ist leider auch genau das szenario das mit potentiellen deadlocks am schwierigsten zu debuggen ist.
ich verstehe das buffer problem nicht.
Alles was ich senden möchte landet zu erst in eine perl var.
Von dort aus schreibe ich (so lange möglich) in das handle und lösche entsprechend aus der perl var.
Wenn der dazugehörige IO buffer voll ist bekomme ich ein E_WOULDBLOCK. Dann schreibe ich nichts mehr in das handle sondern warte bis dessen select wieder Bereitschaft meldet.
Die perl var nimmt weiterhin Daten entgegen.
Mit diesem Vorgehen bin ich bisher immer sehr gut gefahren und das gilt doch als etablierte Vorgehensweise. Wo treten denn in dieser Kette die locks auf ?
vg
joerg
Zitatich verstehe das buffer problem nicht.
Beispiel: eine Bibliotheksroutine, die mir Daten erst zurueckliefert, wenn sie komplett da sind, und das kann Minuten oder Stunden dauern. Und sie wird sehr ungemuetlich, wenn ich die gelieferten Daten nicht sofort abnehme, (z.Bsp. weil ich noch die alten Daten an FHEM weitergebe, und das dauert, da FHEM gerade Plots rechnet), dann bricht die andere Seite der Bibliotheksroutine die Verbindung ab.
Da faellt mir nur ein einen weiteren Thread zu starten, um die Daten langsam an dem Haupt-FHEM weiterzugeben, damit der andere Thread die Bibliotheksroutine bedienen kann. Man diesen Extra-Schreib-Thread sparen, wenn die Daten im TCP-Puffer passen (je nach OS 100kb-4mb)
wobei das mit subProcess oder Blocking genau so geht. forken, (stunden) warten bis die daten da sind und dann per socket langsam an fhem übergeben. threads nicht nötig.
Zitat von: herrmannj am 02 Januar 2017, 10:45:52
ich verstehe das buffer problem nicht.
Alles was ich senden möchte landet zu erst in eine perl var.
Von dort aus schreibe ich (so lange möglich) in das handle und lösche entsprechend aus der perl var.
Wenn der dazugehörige IO buffer voll ist bekomme ich ein E_WOULDBLOCK. Dann schreibe ich nichts mehr in das handle sondern warte bis dessen select wieder Bereitschaft meldet.
Die perl var nimmt weiterhin Daten entgegen.
Mit diesem Vorgehen bin ich bisher immer sehr gut gefahren und das gilt doch als etablierte Vorgehensweise. Wo treten denn in dieser Kette die locks auf ?
vg
joerg
Das verhält sich im vorliegenden Fall so:
- RPc Server Subprozess akzeptiert eingehende Verbindung und nimmt Datenpaket entgegen
- Nun versucht er, die Daten an den FHEM Hauptpozess weiter zu geben (write auf socket)
- wichtig: während dieser Datenweitergabe ist der RPc Server blockiert und nimmt keine weiteren Daten entgegen
- jede verzögerte Weiterleitung der Daten erhöht die Gefahr, dass der Sender aufgibt oder Daten verwirft.
Wie Rudi geschrieben hat: einzige Möglichkeit ist in dem Fall, die Weiterleitung in einen weiteren Prozess auszulagern. Oder eben meine Löshng, die Daten nicht in die IO Schleife einhukippen sondern erst mal in eine Queue zu schreiben.
Ich habe vor einigen Monaten wirklich lange mit SubProcess experimentiert, auch mit Unterstützung von Boris. Mit Änderung von Timeouts und Puffergrössen. War leider nicht erfolgreich. Natürlich würde das Problem mit Threads auch auftreten, daher die Verwendung von den Shared Memory Queues quasi als Ersatz für die IO Puffer des Betriebssystems
warum ist der prozess während der weitergabe blockiert?
der prozess schreib ein häppchen oder so viel wie möglich zum parent und kann sofort weiter auf die daten der ccu warten. d.h. so wie joerg schreibt: eine einzige perl var an die die ccu daten hinten angehängt und am anfang abgeschnitten und zum parent geschickt wird sollte als buffer völlig reichen.
ich vermute mal (und das ist nicht böse gemeint) dass das Problem woanders liegt. Ich vermute das der socket zum parent blockierend ist. Wenn der non-blocking ist (wäre) dann würde das von Dir beschriebene Verhalten nicht auftreten.
Jetzt kann man da beliebig forks/threads/komplett separate Prozesse drum legen - das Problem bleibt. Wenn der Socket non-blocking ist braucht man hingegen auch den sub-prozess nicht mehr. Der sub-prozess ist die richtige Medizin wenn viel CPU ZEIT* am Stück benötigt wird. Für IO Prozesse ist er ohnehin das falsche Mittel.
Für IO Prozesse ist das Mittel der Wahl immer SELECT / NON BLOCKING SOCKETS. Die sorgen (auf OS Ebene) dafür das nur soviel in den Socket (pipe etc) geschrieben wird wie der aufnehmen kann. Außerdem wird per select gemeldet wenn der socket wieder Daten entgegennehmen kann weil er auf der anderen Seite welche losgeworden ist.
Das vergrößern der IO buffer hilft dementsprechend auch nur scheinbar. Es passt dann zwar mehr rein, irgendwann ist der buffer aber trotzdem voll. Die Größe des IO buffers spielt daher auch keine relevante Rolle weil Du ohnehin etwas brauchst (die perl var) um den Rest dort zwischenzuspeichern.
Wie gesagt, bitte nicht falsch verstehen. Ich vermute das Du das Problem an der falschen Stelle anpackst. Damit wäre es möglich die Komplexität - damit die Fehlerquellen - beliebig hoch zu treiben ohne die eigentliche Ursache wirklich anzupacken.
vg
joerg
(*) edith:
oder ich ein ganz genaues Timing benötige.
Zitat von: justme1968 am 02 Januar 2017, 12:24:25
warum ist der prozess während der weitergabe blockiert?
der prozess schreib ein häppchen oder so viel wie möglich zum parent und kann sofort weiter auf die daten der ccu warten. d.h. so wie joerg schreibt: eine einzige perl var an die die ccu daten hinten angehängt und am anfang abgeschnitten und zum parent geschickt wird sollte als buffer völlig reichen.
Und was macht der Subprozess mit den verbliebenen Daten in var, wenn keine weiteren mehr von der cCU kommen? Dann fehlt quasi der Trigger , um den Rest zu schreiben.
- 10 Byte werden empfangen
- davon werden 5 Byte an den Parent geschickt und 5 bleiben im Puffer
- Subprozess geht wieder in seine Accept Schleife und wartet auf Daten der CCU
Die verbleibenden 5 Byte werden also irgendwann geschrieben, aber nicht unbedingt zeitnah.
@hermannj: der Subprozess wird für den RPC Server gebraucht. Der läuft in einem Listen/Accept Loop und ruft für jede eingehende Verbindung eine zuvor registrierte Callback Funktion auf. Und solange diese Funktion nicht zurück kehrt, nimmt er keine neuen Verbindungen an
Aber die Diskussion hier war insofern hilfreich, dass ich ein paar neue Ideen bekommen habe.
auch hier hilft select. das socket zum schreiben kann genau so in die selectlist eingetragen werden. wenn wieder geschrieben werden kann meldet sich das select.
das habe ich sehr wohl verstanden :) @zap (überschnitten mit andre post)
Tipp: schau Dir den gesamten Komplex non-blocking IO mal genau an. Der macht genau Alles was Du beschreibst und suchst.
https://www.google.com/search?q=non-blocking+IO#q=non+blocking+io+perl
ZitatUnd was macht der Subprozess mit den verbliebenen Daten in var, wenn keine weiteren mehr von der cCU kommen? Dann fehlt quasi der Trigger , um den Rest zu schreiben.
- 10 Byte werden empfangen
- davon werden 5 Byte an den Parent geschickt und 5 bleiben im Puffer
- Subprozess geht wieder in seine Accept Schleife und wartet auf Daten der CCU
eben nicht:
die listen/accept loop fliegt raus. Fhem macht alles das bereits in der select loop.
Der socket der auf das listen wartet wird im %selectlist registiriert.
Der socket der Daten vom RPC holt wird in der %selectlist registirert
Der socket der an den Parent schickt (Du ahnst es :) ) ... wird in der %selectlist registriert.
Und nach einige andere ...
Und egal welcher von denen jetzt "zuckt", die selectloop meldet Dir das.
Wenn also eine Aktion auf dem Socket stattfindet wo Du Dein listen hast und wo Du accepten möchtest ruft fhem von sich aus die sub auf die Du angibst. Dort fährst Du den accept *und machst den socket sofort ebenfalls non-blocking* ! Danach ebenfalls in der %selectlist registirieren.
Fhem meldet Dir auch *während* (!!!) Du auf den connect evtl clients wartest das der parent Daten entgegennehmen kann. Kannst Du direkt (während des wartens auf connect!) die Daten raus schicken.
Vertrau mir, Du versuchst gerade eine Schraube mit dem Hammer in die wand zu bekommen, Du benötigst aber Bohrer, Dübel und Schraubenzieher. ;)
vg
joerg
Zitat von: herrmannj am 02 Januar 2017, 14:55:17
Vertrau mir, Du versuchst gerade eine Schraube mit dem Hammer in die wand zu bekommen, Du benötigst aber Bohrer, Dübel und Schraubenzieher. ;)
Danke :D Ich schaue mir das alles mal an. Allerdings befürchte ich, dass ich - wenn ich nur den selectlist Mechanismus verwende - die RPC Server Logik aus dem Modul RPC::XML::Server nachbauen muss.
Und um diese ganzen Sockets zu registrieren, muss ich wohl ähnlich wie im Plex-Modul hidden Devices definieren, da ja jedes Device nur einen Filedescriptor haben kann.
Wenn das alles sich über die selectlist abbilden lässt, braucht es ja überhaupt keine Hintergrundprozesse mehr in FHEM. Dann hätte ich mir die Nervenschlacht mit SubProcess.pm vor einigen Monaten sparen können. Das sollte man auch den Modulentwicklern mal mitteilen, die jetzt Subprozesse und Threads verwenden.Ok, streiche das oben. Ich nutze also doch einen SubProzess. Allerdings nutzt der SubProzess ebenfalls den selectlist Mechanismus. Nach dem 2. Lesen deines Posts habe ich es glaube ich kapiert. Allerdings ohne SubProcess.pm, da dort alles in OnRun() abläuft und der FHEM Loop nie zum Zug kommt. Also ein simpler fork() bei dem der Child Prozess sofort zurückkehrt.
Also erst mal kein Multithreading in FHEM. Ich versuche es mit SubProzess. Blöd ist nur das RPC Handling.
pro socket brauchst du einen minimalen hash den du in die select list hängst und der alle nötigen dinge enthält um aus der readFn die durch das select aufgerufen wird rückwärts an die für dich nötigen daten zu kommen. d.h. in den hash steckst du alles an kontext der für diese eine verbindung nötig ist.
wenn du nur einen socket hast kannst du dazu direkt den device hash selber verwenden. wenn es mehrere sockets sind brauchst du eigene hashes. der device hash kümmert sich dann meist um das socket für die eingehenden verbindungen. nach dem accept legt er dann der hash für die jeweilige verbindung an und hängt ihn in die selectlist. (es ist übrigens nicht zwingend nötig das diese hashes von selben TYPE sind wie das haupt device. je nach anwendungsfall kann man hier spielen und über einen eigenen TYPE auch unterschiedliche readFn für unterschiedliche socket arten umsetzen)
das mit den versteckten hashes ist im prinzip bei allen devices so die verbindungen von aussen annehmen. z.b. auch fhemweb, telnet, ...
plex ist noch etwas spezieller da es mindestens eine hand voll sockets für die client und server broadcast und multicast discovery braucht, zusätzlich noch verbindungen pro client für die timeline plus eine websocket verbindung pro server.
schau mal ob es nicht sogar ohne extra prozess geht. für das reine handling mehrere sockets braucht es das nämlich noch nicht. und dann sparst du dir auch das komplette parent/child handling. wenn es tatsächlich noch timing probleme gibt weil etwas anderes in fhem zu viel zeit braucht kannst du das ganze immer noch per Blocking oder SubProcess auslagern.
viel spass beim basteln :)
Als Nachtrag: habe jetzt mal versucht, dem FHEM select Loop den FD eines mit Open() geöffneten Strings unterzujubeln. War keine gute Idee. Das hat FHEM aber sowas von zerlegt :-)
Wollte über das Schreiben in diesen String den Aufruf von X_Read zu triggern.
spontan würde ich sagen das könnte daran liegen das so ein FD auf einen string kein echter fd auf os ebene ist (so wie files, pieps und sockels) sondern eine perl besonderheit und sich deshalb nicht mit select verträgt.
der fd zum testen sollte vermutlich auch zwei 'enden' haben. einen zum schreiben und einen zum lesen an dem die daten ankommen. ich weiß gerade nicht ob das für normale files zutrifft. für den string ziemlich sicher nicht.
ZitatFD eines mit Open() geöffneten Strings
Was ist das eigentlich? Kann irgendwie nichts Sinnvolles darunter vorstellen. Dem Select kann man unter Linux/OSX mW alles geben, was zu einem OS-Filedescriptor zugeordnet wird. Unter Windows geht das nur fuer Netzwerk-FD, da MS vor 20+ Jahren beim Kopieren des Netzwerk-Kodes von BSD select zwar uebernommen hat, aber nicht fuer den Rest (Files, Serielle Schnittstellen, etc) implementiert hat.
mit neueren perl versionen kann man strings (memory) und auch here documents wie files öffnen.
aber wie oben geschrieben tippe ich mal das dies keine os files sind sondern irgend eine perl emulation die sich dann auch nicht mit select verträgt.
Zitat von: rudolfkoenig am 04 Januar 2017, 09:30:53
Was ist das eigentlich? Kann irgendwie nichts Sinnvolles darunter vorstellen. Dem Select kann man unter Linux/OSX mW alles geben, was zu einem OS-Filedescriptor zugeordnet wird. Unter Windows geht das nur fuer Netzwerk-FD, da MS vor 20+ Jahren beim Kopieren des Netzwerk-Kodes von BSD select zwar uebernommen hat, aber nicht fuer den Rest (Files, Serielle Schnittstellen, etc) implementiert hat.
Sowas (ab Perl 5.8 ):
my $string = '';
open($fh, "+<", \$string);
print $fh "Content for String";
my $c = <$fh>;
http://docstore.mik.ua/orelly/perl4/cook/ch08_24.htm
Aber Haken dran. Ist nicht so wichtig. War eh ne blöde Idee.