SubProcess.pm: Frage zum letzten Patch

Begonnen von zap, 02 Juni 2017, 12:31:22

Vorheriges Thema - Nächstes Thema

zap

Hallo,

mit dem letzten Patch wurde in SubProcess.pm ein gepuffertes Lesen eingeführt. Ich zitiere mal aus der Funktion readFrom():


210   my $size = unpack ('N', $header);
211   my $buffer;
212   while($size> 0) {
213     my $bytes = sysread ($fh, $buffer, $size);
214     if (!defined ($bytes)) {
215       $self->{lasterror} = $!;
216       return undef;
217     }
218     $data.= $buffer;
219     $size-= $bytes;
220   }


Ich nehme an, dass die Schleife dafür sorgen soll, dass ein Datagramm auch dann vollständig gelesen wird, wenn ein sysread() bei großen Datagrammen je Aufruf nur einen Teil der Daten lesen kann.

Sollte soweit funktionieren. Allerdings kann ich mir eine Situation vorstellen, bei der diese Änderung zu einer Endlosschleife führt. Nämlich dann, wenn ein Datagramm vom "Sender" nicht komplett geschrieben wurde bzw. wenn beim Schreiben ein Fehler auftrat. Dann wäre z.B. die Länge des Datagramms $size=100, aber die gelesenen Nutzdaten $bytes=70. Somit würde $size immer auf einem Rest von 30 Byte "sitzen bleiben" und die Schleife würde nie beendet werden.

Daher schlage ich folgende Änderung vor:


214     if (!defined ($bytes)) {
215       $self->{lasterror} = $!;
216       return undef;
217     }
            last if ($bytes == 0);   # wenn keine weiteren Daten anstehen
218     $data.= $buffer;
219     $size-= $bytes;


Alternativ könnte man in dem Fal auch undef zurück geben. Je nachdem, ob unvollständige Daten verworfen oder weiter verarbeitet werden sollen.

2xCCU3, Fenster, Rollläden, Themostate, Stromzähler, Steckdosen ...)
Entwicklung: FHEM auf AMD NUC (Ubuntu)
Produktiv inzwischen auf Home Assistant gewechselt.
Maintainer: FULLY, Meteohub, HMCCU, AndroidDB

Dr. Boris Neubert

Hallo zap,

Danke für die Analyse.

Ähnliches hatte ich vor der nun realisierten Situation einführen wollen. Das Problem ist jedoch, dass der Fall vorkommt, dass sysread() beim ersten Aufruf 0 zurückliefert, obwohl das schreibende Ende der Pipe noch Bytes zu füttern hat (Race Condition). In diesem Fall würde der Prozess zu früh abbrechen.

Es gibt sicher sophistiziertere Lösungen mit asynchronem Lesen. Aber - in Anlehnung an ein bekanntes Bonmot - es war Nacht und ich brauchte den Code.

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

zap

#2
Zitat von: Dr. Boris Neubert am 02 Juni 2017, 17:32:52
Ähnliches hatte ich vor der nun realisierten Situation einführen wollen. Das Problem ist jedoch, dass der Fall vorkommt, dass sysread() beim ersten Aufruf 0 zurückliefert, obwohl das schreibende Ende der Pipe noch Bytes zu füttern hat (Race Condition). In diesem Fall würde der Prozess zu früh abbrechen.

Hallo Boris,

angenommen, sysread() liefert beim ersten Aufruf 0 zurück und readFrom() wird beendet. Würde in dem Fall FHEM nicht beim nächsten select auf den Socket-Descriptor feststellen, dass noch Daten da sind und X_Read() und damit readFrom() erneut aufrufen? Die Daten würden dann ggf. etwas später gelesen werden.

Das ist jetzt nur eine Vermutung. Ich habe schon ziemlich viel Zeit mit der Übertragung von Daten zwischen Child und Parent verbracht. Eine zufriedenstellende bzw. 100% verlässliche Lösung habe ich nicht gefunden.

Daher verwende ich in HMCCU inzwischen Shared-Memory-Queues (Modul Thread::Queue) für den Datenaustausch zwischen den Threads. Die Socketpairs verwende ich nur um ein paar Byte Daten an den Parent zu schicken und so dem FHEM I/O Mechanismus zu signalisieren, dass Daten in der Queue bereitstehen. FHEM ruft dann X_Read() auf, das die Queue ausliest. Die Queues übernehmen die Pufferung.

Viele Grüße
Dirk

2xCCU3, Fenster, Rollläden, Themostate, Stromzähler, Steckdosen ...)
Entwicklung: FHEM auf AMD NUC (Ubuntu)
Produktiv inzwischen auf Home Assistant gewechselt.
Maintainer: FULLY, Meteohub, HMCCU, AndroidDB

Dr. Boris Neubert

Zitat von: zap am 02 Juni 2017, 19:50:00
angenommen, sysread() liefert beim ersten Aufruf 0 zurück und readFrom() wird beendet. Würde in dem Fall FHEM nicht beim nächsten select auf den Socket-Descriptor feststellen, dass noch Daten da sind und X_Read() und damit readFrom() erneut aufrufen? Die Daten würden dann ggf. etwas später gelesen werden.

Genau, Dirk, das meinte ich mit asynchron. In dem Anwendungsfall, für den ich die Änderung brauchte, polle ich allerdings den Socket. Schön ist das nicht, aber es ging schneller (Es war Nacht...).

Fazit: es wäre besser (stilistisch und praktisch), das Rücklesen der vom Subprocess geschriebenen Daten über die globale select-Schleife in fhem.pl zurück in das Device zu füttern, das den Subprocess losgetreten hat.

Ich habe noch eine halbfertige Variante von OWServer herumliegen, wo ich die gesamte Abfrage des Busses in einem nebenläufigen Suprocess erledige. Der Subprocess pollt regelmäßig die Geräte am Bus und soll die Ergebnisse asynchron in den Hauptprozess einschleusen. Da will ich das so realisieren.

Wenn sich Subprocess für den Anwendungsfall (Calendar, Hintergrundverarbeitung eines Kalenders) bewährt, würde ich das ganze so auf die nächste Stufe heben.

Viele Grüße
Boris

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