Blocking DNS in HttpUtils_NonblockingGet

Begonnen von Markus M., 12 Mai 2016, 17:07:28

Vorheriges Thema - Nächstes Thema

rudolfkoenig

Die DevIo_OpenDev() Funktion sichert bisher zu, dass nach dem Aufruf die Verbindung zum Geraet steht. Man muesste also einen zusaetzlichen Callback spezifizieren, damit nonblocking Connect aufgerufen werden kann -> Ohne Aenderung im jeweiligen Modul gibt es kein nonblocking Connect.

Gibt es dafuer einen Bedarf?

StefanStrobel

Hallo,

ich denke der Mehrwert ist vor allem in den Ready-Funktionen beim Reconnect.
Wenn ein Gerät beim Starten von Fhem nicht erreicht werden kann, ist eine kurze Verzögerung vermutlich kaum ein Problem.

Wenn aber ein Gerät im laufenden Betrieb temporär nicht mehr erreichbar wird und dadurch Fhem regelmäßig beim Versuch die Verbindung wieder aufzubauen blockiert, wäre ein nonblocking Connect sehr hilfreich. Ich würde es zumindest in 98_Modbus.pm einbauen.

Gruss
    Stefan

rudolfkoenig

Ab sofort gibt es die Moeglichkeit in DevIo_OpenDev das nicht blockierende Http_Connect zu verwenden (was in diesem Fall nichts mit HTTP zu tun hat), wenn man einen 4. Parameter (den callback) spezifiziert.

Bisher:
my $ret = DevIo_OpenDev($hash, 0, "FBAHA_DoInit");
Falls jemand nonblocking connect will:
DevIo_OpenDev($hash, 0, "FBAHA_DoInit", sub(){
  my ($hash, $err) = @_;
  Log3 $name, 2, "$name: $err" if($err);
});


Die bisherige Variante bleibt natuerlich erhalten.
Sobald das optionale 4. Parameter spezifiziert ist, wird es immer aufgerufen, selbst wenn das (wie bei USB) synchron passiert. Sinnvoll ist es nur fuer TCP/IP, die Verbindung wird dann vom HttpUtils_Connect hergestellt (das ist Teil vom HttpUtils_NonblockingGet)

Fuer die bisherigen Faelle musste ich etwas Code umsortieren. Ich hoffe, dass ich dabei nichts uebersehen habe, trotzdem bitte ich alle, etwas aufzupassen, und Probleme hier melden.

dev0


rudolfkoenig

HttpUtils_NonblockingGet hat(te) das Problem, dass sie nicht funktioniert, wenn direkt nach dem Aufruf blockierender Kode laenger als das eingestellte Timeout  (default 4s) ausgefuehrt wird. Das kann z.Bsp. bei der Initialisierung passieren, oder bei der Schaltung von vielen Geraeten, die jeweils fuer ein bisschen Blockierung sorgen.

Um das Problem zu loesen habe ich eine globale Variable $selectTimestamp eingefuehrt (enthaelt den Zeitstempel vor dem select Aufruf), und die Routinen in Http_NonblockingGet versuchen anhand dieser Variable zu erkennen, ob es sich um das beschriebene Problem handelt. Das Verfahren ist nicht perfekt, wer ein Besseres hat, soll sich melden. Und falls jemandem ein Problem mit der Loesung auffaellt, der auch.

Loredo

#35
Ich habs für das ONKYO_AVR Modul jetzt mal zumindest für den initialen Verbindungsaufbau drin, funktioniert problemlos:


    # connect using serial connection (old blocking style)
    if (   $hash->{DeviceName} =~ m/^UNIX:(SEQPACKET|STREAM).*)$/
        || $hash->{DeviceName} =~ m/^FHEM:DEVIO:(.*)(.*))/ )
    {
        my $ret = DevIo_OpenDev( $hash, 0, "ONKYO_AVR_DevInit" );
        return $ret;
    }

    # connect using TCP connection (non-blocking style)
    else {
        # add missing port if required
        $hash->{DeviceName} = $hash->{DeviceName} . ":60128"
          if ( $hash->{DeviceName} !~ m/^(.+)[0-9]+)$/ );

        DevIo_OpenDev(
            $hash, 0,
            "ONKYO_AVR_DevInit",
            sub() {
                my ( $hash, $err ) = @_;
                Log3 $name, 2, "ONKYO_AVR $name: $err" if ($err);
            }
        );
    }


Für den Re-Connect in der ReadyFn bin ich mir jetzt nicht ganz sicher, ob dort die initFn tatsächlich fehlen darf?



sub ONKYO_AVR_Ready($) {
    my ($hash) = @_;

    if ( ReadingsVal( $hash->{NAME}, "state", "disconnected" ) eq
        "disconnected" )
    {

        DevIo_OpenDev(
            $hash, 1, undef,
            sub() {
                my ( $hash, $err ) = @_;
                Log3 $name, 2, "ONKYO_AVR $name: $err" if ($err);
            }
        );

        return;
    }

    # This is relevant for windows/USB only
    my $po = $hash->{USBDev};
    my ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags );
    if ($po) {
        ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags ) = $po->status;
    }
    return ( $InBytes && $InBytes > 0 );
}
Hat meine Arbeit dir geholfen? ⟹ https://paypal.me/pools/c/8gDLrIWrG9

Maintainer:
FHEM-Docker Image, https://github.com/fhem, Astro(Co-Maintainer), ENIGMA2, GEOFANCY, GUEST, HP1000, Installer, LaMetric2, MSG, msgConfig, npmjs, PET, PHTV, Pushover, RESIDENTS, ROOMMATE, search, THINKINGCLEANER

StefanStrobel

Ich bin gerade am Testen bzw. versuche zu verstehen, wie der DevIo_OpenDev mit TCP nonblocking zu verwenden ist. Folgendes kommt mir seltsam vor:
In DevIo_OpenDev im Callback für HttpUtils_Connect wird - falls $err gesetzt ist - kein doTcpTail aufgerufen. Damit wird auch NEXT_OPEN nicht gesetzt und beim ersten connect kommt auch nichts zur $readyfnlist hinzugefügt. Ist das ein Bug oder verstehe ich da noch was nicht?
Man könnte natürlich viele Dinge auch in die _Ready-Funktion der Module verlagern, aber da NEXT_OPEN ja schon existiert, wäre es ja eleganter wenn es auch verwendet wird.

Ab Zeile 330 in DevIo:

   if($callback) {
      use HttpUtils;
      my $err = HttpUtils_Connect({     # Nonblocking
        timeout => $timeout,
        url     => "http://$dev/",      # not really http
        NAME    => $hash->{NAME},
        noConn2 => 1,
        callback=> sub() {
          my ($h, $err, undef) = @_;
          return $callback->($hash, $err) if($err);
          # hier wird in kein doTcpTail aufgerufen wenn $err übergeben wurde
          return &$doCb("") if(!&$doTcpTail($h->{conn}));
          return &$doCb(&$doTailWork());
        }
      });
      return &$doCb($err) if($err);
      return undef;

    } else {
      my $conn = IO::Socket::INET->new(PeerAddr => $dev, Timeout => $timeout);
      # hier wird in jedem Fall doTcpTail aufgerufen
      return &$doCb("") if(!&$doTcpTail($conn));
    }



Unabhängig davon: verstehe ich es richtig, dass ein Modul im Callback für DevIo_OpenDev prüfen sollte, ob bereits ein Open läuft und sich noch nicht zurückgemeldet hat? Sonst gibt es evt. viele parallele Opens ...

Gruss
    Stefan

rudolfkoenig

ZitatIst das ein Bug oder verstehe ich da noch was nicht?
War wohl Bug, habe die Aenderungen nicht auf reconnect getestet, danke fuer den Hinweis.
Habs geaendert, kurz getestet und eingecheckt.

ZitatUnabhängig davon: verstehe ich es richtig, dass ein Modul im Callback für DevIo_OpenDev prüfen sollte, ob bereits ein Open läuft und sich noch nicht zurückgemeldet hat? Sonst gibt es evt. viele parallele Opens ...
Ja, da es auch richtig gebaute Server gibt, die mehrere parallele Anfragen verkraften oder sogar wuenschen.

StefanStrobel

Vielen Dank!
Jetzt fehlt noch ein Aufruf von doCB falls NEXT_OPEN noch nicht erreicht ist.
Zudem wäre es jetzt schön wenn das aufrufende Modul die NEXT_OPEN Verzögerung beeinflussen könnte.
Ein Patch-Vorschlag anbei.

Was (optisch) noch nicht ganz optimal ist, ist das jetzt entstehende Logging von den HttpUtils bzw. dessen Rückmeldungen an den Callback.


2016.06.30 20:54:48 3: aaModTest: Open callback: connect to http://192.168.70.148:502 timed out
2016.06.30 20:55:08 4: HttpUtils url=http://192.168.70.148:502/


vielleicht könnte man HttpUtils_Connect noch so erweitern, dass beim Aufruf durch DevIo kein Http:// vorangestellt werden muss und dass damit die Meldungen von den HttpUtils auch kein Http:// mehr enthalten?
Man könnte natürlich auch den reinen TCP-Connect aus HttpUtils herauslösen.

Gruss
    Stefan

rudolfkoenig

ZitatJetzt fehlt noch ein Aufruf von doCB falls NEXT_OPEN noch nicht erreicht ist.
Davon bin ich noch nicht ueberzeugt: warum ist das notwendig?
Weiterhin wird die Funktion ohne Fehlermeldung aufgerufen, das "fuehlt" sich nicht richtig an.

ZitatZudem wäre es jetzt schön wenn das aufrufende Modul die NEXT_OPEN Verzögerung beeinflussen könnte.
Diese Aenderung habe ich uebernommen und eingecheckt.

ZitatWas (optisch) noch nicht ganz optimal ist, ist das jetzt entstehende Logging von den HttpUtils bzw. dessen Rückmeldungen an den Callback.
Stimmt, das stelle ich aber erst um, wenn das Ganze "gereift", und verwendet wird.

StefanStrobel

Hallo,

ich hatte mir die Schnittstelle bisher so "zusammengereimt", dass der Callback ähnlich wie bei den HttpUtils auf jeden Fall aufgerufen wird und dass das aufrufende Modul daran erkennt, dass der Verbindungsversuch zum Ende gekommen ist.
Wenn das nicht so gedacht ist, dann bräuchte das aufrufende Modul ein anderes Erkennungsmerkmal um zu wissen ob es noch auf einen callback "warten" soll bzw. ob es weitere Verbindungsversuche zurückstellen soll oder nicht.

Die _Ready-Funktion hatte ich bisher so angedacht:

sub Modbus_OpenCB($$)
{
   my ($hash, $msg) = @_;
   my $name = $hash->{NAME};
   Log3 $name, 3, "$name: Open callback: $msg" if ($msg);
   delete $hash->{BUSY_OPENDEV};
}

sub Modbus_Ready($)
{
    my ($hash) = @_;
    my $name = $hash->{NAME};
    return if ($hash->{BUSY_OPENDEV})   # still waiting for callback to last open
    if($hash->{STATE} eq "disconnected") {
        $hash->{BUSY_OPENDEV} = 1;
        return DevIo_OpenDev($hash, 1, 0, \&Modbus_OpenCB);
    }
    ...
}


Ohne eine Art BUSY_OPENDEV würde die _Ready-Funktion unter Umständen (hängt dann vom Timing und der Wartezeit für NEXT_OPEN ab) mehrere redundante Verbindungsversuche auslösen. Zusätzlich würde ich vermutlich auch noch einen zweiten Timeout einbauen, damit BUSY_OPENDEV nicht ewig gesetzt bleibt wenn mal etwas nicht funktioniert.

Gruss
    Stefan

rudolfkoenig

Die Verbindung ist in diesem Fall weder endgueltig schiefgegangen, noch gelungen, deswegen wollte ich mir den Callback sparen. Sonst gibt einer eine Fehlermeldung aus, oder versucht was zu uebertragen, und beides ist ja falsch.

Falls man nur eine Verbindung haben will, dann muss man den Flag vor dem Open setzen, und nicht im callback, sonst erzeugen 2 Userlevel-Aufrufe (define/set/get/etc) auch bei schnell reagierende Gegenstelle zwei Verbindungen: Open ist nicht blockierend.

StefanStrobel

Hallo,

jetzt kann ich leider nicht folgen.
Dass Open nicht blockiert, war ja das Ziel. Der Callback, der im hash an HttpUtils_Connect übergeben wird, wird doch aufgerufen wenn entweder die Verbindung aufgebaut wurde oder wenn der Verbindungsaufbau gescheitert ist und eine Fehlermeldung an den Callback übergeben wird.

Entsprechend dachte ich dass der Callback, der bei DevIo_OpenDev übergeben wird, ebenfalls aufgerufen werden soll, wenn entweder die Verbindung erfolgreich aufgebaut wurde, oder wenn es nicht geklappt hat. Nach dem Aufruf von DevIo_Opendev selbst ist aber noch alles offen.

Habe ich bis hier schon etwas falsch verstanden?

Gruss 
    Stefan


rudolfkoenig

Nein, aber wir diskutieren ueber den Fal, wo FHEM versucht die Verbindung weiterhin (periodisch) zu oeffnen. Aus Modulsicht ist der Vorgang nicht abgeschlossen, also weder gescheitert, noch gelungen.

Falls der Code dein  Modbus_OpenCB aufrufen wuerde, obwohl in einer Minute der zweite Open-Versuch stattfindet, denn waere BUSY_OPENDEV zu unrecht entfernt.

StefanStrobel

ok, dann haben wir offenbar aneinander vorbei geredet.
So lange Fhem das Modul / Gerät auf der readyfnlist hat, wird die _Ready-Funktion immer wieder aufgerufen. Eventuell sogar im Sekundentakt. Das wollte ich nicht anzweifeln und daran wollte ich mit einem BUSY_OPENDEV auch nichts ändern.

Es geht mir um folgenden Fall:
Es wird nur eine TCP Verbindung vom Modul zu einem Modbus-Gerät benötigt.
die Verbindung geht verloren, das Gerät kommt auf die readyfnlist.
_Ready ruft DevIo_OpenDev mit einem Callback auf um die Verbindung nonblocking wieder herzustellen.
Fhem wartet jetzt quasi parallel auf das Herstellen der Verbindung und das kann ein paar Sekunden dauern.
In der Zwischenzeit wird die _Ready-Funktion schon wieder aufgerufen.
_Ready ruft DevIo_OpenDev nochmals auf, obwohl der erste Call noch gar nicht fertig bearbeitet wurde und noch kein Timeout gegriffen hat.

Damit das nicht vorkommt würde ich in der _Ready-Funktion erst dann wieder DevIo_OpenDev aufrufen, wenn der letzte Versuch tatsächlich schon gescheitert ist.
Deshalb wollte ich vor dem Aufruf von DevIo_OpenDev ein BUSY-Flag setzen und dieses erst dann wieder löschen wenn der aktuelle Verbindungsversuch sich zurückgemeldet hat. Entweder mit Fehler / Timeout oder mit Success. Im Fehlerfall wird dann beim nächsten Aufruf von _Ready ein neuer Versuch gestartet. Aus Modulsicht bleibt in der Zeit das Device auf disconnected und logisch quasi im Zustand "Reconnect-Versuch". Nur die überschneidenden Aufrufe von DevIo_Opendev aus der _Ready-Funktion wollte ich so verhindern.

Daher mein Wunsch, dass das Modul in allen Fällen ein Rückmeldung per Callback bekommt, damit das temporäre BUSY-Flag gelöscht werden kann und so der nächste Verbindungsversuch gestartet werden kann.

Gruss
    Stefan