Generelle Frage zu BlockingCall()

Begonnen von Sailor, 28 Oktober 2014, 16:13:01

Vorheriges Thema - Nächstes Thema

Sailor

Moin zusamen

Bevor ich mich in eine Sackgasse programmiere, habe ich habe zwei generelle Frage zum Modul BlockingCall().

Ich habe mir das WIKI http://www.fhemwiki.de/wiki/Blocking_Call durchgelesen und bin auf folgende 2 Fragen gestossen:

a)
Wenn ich aus der km200_define eine Funktion mit BlockingCall aufrufe, sagen wir mit
BlockingCall(SubA, $hash);
SubA ruft seinerseits SubB auf welche seinerseits SubC und SubD aufruft.
Alle Subs befinden sich innerhalb des Moduls 73_km200.pm

Sind dann alle SubsA-D ebenfalls durch BlockingCall von SubA geforkt?

b)
Mit der Einschränkung
ZitatVeränderungen an internen Variablen von FHEM (Device Hashes, Timers, usw.) werden innerhalb eines BlockingCalls durchgeführt und sind dort auch sichtbar, haben aber keinerlei Einfluss auf den eigentlichen FHEM Hauptprozess und alle Definitionen. Solche Veränderungen müssen an die finishFn delegiert werden (z.B. durch zusätzliche Rückgabewerte), da es sich um einen Fork-Prozess handelt, der sich nach Abschluss des BlockingCall selbst zerstört.

Schließt das auch jene Veränderungen ein, die ich während des Ablaufs von den SubsA-D an den "$hash" zurückgebe?

Danke für die Hilfe

Gruß
    Sailor
******************************
Man wird immer besser...

rudolfkoenig

a) ja, egal wo die subs definiert sind.
b) du kannst alle Variablen beliebig aendern, das "Haupt-FHEM" kriegt nur das mit, was du explizit zuruecklieferst.

BlockingCall ist eine Notloesung, und hat diverse Probleme.
Wenn moeglich (und meist ist es moeglich), sollte man es vermeiden.
Skizzier das Problem bitte, ich versuche dann Alternativen vorzuschlagen.

Sailor

Hallo Rudi,

also ich weiß nicht ganz wie ich es skizzieren soll.

Mein Projekt und das Modul findest Du unter: http://forum.fhem.de/index.php/topic,25540.135.html

Ich habe 3 Subroutinen innerhalb dieses Moduls:

km200_CompleteDataInit
km200_CompleteDynData
km200_CompleteStatData


Alle drei Module laden im Endeffekt Werte aus einem per Netzwerk verbundenen Kommunikationsmodul mittels JSON.
Während dieses Vorgangs, der beim einmaligen Initial-Download mittels "km200_CompleteDataInit" bis zu 20s dauern kann, ist fhem komplett blockiert und reagiert erst wieder, wenn der download abgeschlossen ist.

Das ist für den normalen Gebrauch von fhem nicht aktzeptabel.

Die Subroutinen "km200_CompleteDynData" und "km200_CompleteStatData" sind darüber hinaus noch mit einem InternalTimer versehen.
Das klappt bisher sehr gut aber blockiert das System mit den dafür vorgesehenen Intervallen komplett.

Reicht das als "Skizze" oder brauchst du noch ein paar Infos?

Danke für Deine Hilfe! ;)

Gruß
   Sailor

******************************
Man wird immer besser...

justme1968

ist das ein http abruf für die daten? dann kannst du die nonblocking HttpUtils verwenden ansonsten per readFn und selectlist. beides ist nicht blockierend und kommt ohne fork aus.

beispiele für die HttpUtils variante findest du z.b. im HTTPMOD oder netatmo modul, für selectlist unterwandert in telnet und mailcheck.

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

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

rudolfkoenig

Daten vom Netzwerk zu laden kann man auch (und in FHEM sollte man) nicht-blockierend gestalten.

Hoffentlich die meisten (mwn alle von mir gebauten) FHEM-Module, die mit der "Aussenwelt" zu tun haben, arbeiten nicht-blockierend: das FileDescriptor (Netzwerk/USB/etc) wird ins globale %selectlist eingetragen (normalerweise durch DevIo_OpenDev), und das bewrikt, dass das moduleigene ReadFn vom Framework aufgerufen wird, falls was zu lesen gibt. ReadFn darf nur ein sysread (oder DevIo_SimpleRead) absetzen, weil das wg. dem select garantiert ohne blockieren funktioniert. Die neuen Daten werden zu den bisher gelesenen hinzugefuegt, und auf Vollstaendigkeit geprueft. Falls Vollstaendig, dann wird die Weiterverarbeitung angestossen, sonst per return die Kontrolle abgegeben, und darauf gewartet, das mehr Daten kommen (== ReadFn wieder aufgerufen wird).

Fuer HTTP gibts als FHEM-Hilfsfunktion HttpUtils_NonblockingGet, die arbeitet intern genauso, und ruft, falls fertig, eine Routine auf, die man per Parameter spezifizieren muss. Ist einfacher zu handhaben, und vermeidet auch das Blockieren falls das Zeilsystem ausgeschaltet ist.

Da in FHEM kein Threading moeglich ist (was wiederum andere Probleme verursachen wuerde), muss man etwas umdenken, und die Loesung nicht-blockierend gestalten. Wird nach ein bisschen Uebung selbstverstaendlich.

BlockingCall loest das Blockieren-Problem durch forken (== es wird ein zweiter Prozess erzeugt), und die Kommunikation zwischen Haupt-Fhem und geforkter FHEM erfolgt via TCP/IP bzw. das telnet Modul. Das ist weder schnell, noch unproblematisch, weil alle Verbindungen (Filedeskriptoren) ploetzlich verdoppelt werden, und die beiden Prozesse unabhaengig voneinander weiterexistieren koennen.

Sailor

Zitat von: justme1968 am 29 Oktober 2014, 09:34:12
ist das ein http abruf für die daten? dann kannst du die nonblocking HttpUtils verwenden ansonsten per readFn und selectlist. beides ist nicht blockierend und kommt ohne fork aus.

Hallo Andre

ja es is ein http-Aufruf... Anbei der Code der Routine "ganz tief unten"


my $ua = LWP::UserAgent->new;
my $options = HTTP::Headers->new
(
"Accept"     => "application/json",
"User-Agent" => "TeleHeater/2.2.3"
);
$ua->default_headers($options);
$ua->agent('TeleHeater/2.2.3');

my $response = $ua->get( 'http://' . $km200_gateway_host . $REST_URL );   


Ich werde mich mal in rudolfs Tipp "HttpUtils_NonblockingGet" einlesen.

Zitat von: rudolfkoenig am 29 Oktober 2014, 10:05:27
Fuer HTTP gibts als FHEM-Hilfsfunktion HttpUtils_NonblockingGet, die arbeitet intern genauso, und ruft, falls fertig, eine Routine auf, die man per Parameter spezifizieren muss. Ist einfacher zu handhaben, und vermeidet auch das Blockieren falls das Zeilsystem ausgeschaltet ist.

Wobei es mir allerdings lieber wäre, nicht nur die eigentliche HTTP-Abfrage sondern den gesamten Baum aus (SubA ruft SubB ruft SubC auf) zu entforken, da dieser als Poll alle 90 Sekunden aufgerufen wird.

Wer Lust auf Kopfschmerzen hat, darf sich den Code gerne mal anschauen!  ;D

Ich werde berichten!

Danke!!!  ;)

Gruss
    Sailor
******************************
Man wird immer besser...

Sailor

Hallo zusammen

ich hoffe ich habe alles richtig verstanden, dass aus dem Code


sub km200_GetData($)
{
my ($hash, $def)            = @_;

my $REST_URL                = $hash->{temp}{service};
my $km200_gateway_host      = $hash->{URL} ;
my $name                    = $hash->{NAME} ;

my $ua = LWP::UserAgent->new;
my $options = HTTP::Headers->new
(
"Accept"     => "application/json",
"User-Agent" => "TeleHeater/2.2.3"
);
$ua->default_headers($options);
$ua->agent('TeleHeater/2.2.3');

my $response = $ua->get( 'http://' . $km200_gateway_host . $REST_URL );   

if ($response->decoded_content =~ /Sorry, the requested file does not exist on this server/) {
Log3 $name, 2, $name . " : ERROR: No proper Communication with Gateway";
return "ERROR";
}

my $data;
if ($response->is_success)
{     
$hash->{temp}{decodedcontent} = $response->decoded_content;
$data = decode_json(km200_Decrypt($hash));
}

else
{
Log3 $name, 4, $name. " : km200 - Data not available on km200 for http://" . $km200_gateway_host . $REST_URL. " : " .$response->status_line;
return "SERVICE NOT AVAILABLE";
}


nun der folgende Code wird


sub MyCallbackFunction($$$)
{
my $param                      = @_[0];
my $err                        = $_[1];
my $data                       = $_[2];
my $hash                       = $param->{hash};

$hash->{temp}{decodedcontent} = $data;
$hash->{temp}{response} = $param->{code};
}

#***************************


sub km200_GetData($)
{
my ($hash, $def)            = @_;

my $REST_URL                = $hash->{temp}{service};
my $km200_gateway_host      = $hash->{URL} ;
my $name                    = $hash->{NAME} ;


my %param;
my $response;

$param->{url}      = "http://" . $km200_gateway_host . $REST_URL;
$param->{timeout}  = 5;
$param->{loglevel} = 5;
$param->{hideurl}  = 1;
$param->{method}   = GET;
$param->{header}   = "User-Agent: TeleHeater/2.2.3\r\nAccept: application/json";
$param->{callback} = "\&MyCallbackFunction";
$param->{hash}     = $hash ;


HttpUtils_NonblockingGet($param);

if ($hash->{temp}{decoded_content} =~ /Sorry, the requested file does not exist on this server/) {
Log3 $name, 2, $name . " : ERROR: No proper Communication with Gateway";
return "ERROR";
}

my $data;
if ($hash->{temp}{response})
{     
#$hash->{temp}{decodedcontent} = $hash->{temp}{decodedcontent};
$data = decode_json(km200_Decrypt($hash));
}

else
{
Log3 $name, 4, $name. " : km200 - Data not available on km200 for http://" . $km200_gateway_host . $REST_URL. " : " .$response->status_line;
return "SERVICE NOT AVAILABLE";
}


Sieht Jemand generelle Denkfehler?

Das Modul wird langsam aber sicher zu einer echten Herausforderung!  :o

Gruß
   Sailor
******************************
Man wird immer besser...

justme1968

ich glaube nicht ganz.

nach dem HttpUtils_NonblockingGet aufruf kannst du die daten nicht gleich dekodieren weil sie noch nicht nicht da sind sonder asynchron ankommen.

die Logik ist eher so:

- HttpUtils_NonblockingGet aufruf. dann einfach return und nichts weiter tun
- in der callback funktion gibt es zwei möglichkeiten:
  dein device schickt die daten schnell genug bzw es sind nur so wenige das sie auf ein mal ankomen
    -> daten decodieren und nächsten timer für den HttpUtils_NonblockingGet starten

  dein device schickt die daten langsam bzw es sind so viele das sie nicht bei einem callback aufruf komplett sind
    -> du nimmst die daten entgegen, schaust ob dein json komplett ist
      wenn nein -> daten merken und beim nächsten callback aufruf die dann
                          neuen daten an die gemerkten anhängen.
      wenn ja -> verarbeiten wie oben

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

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

rudolfkoenig

ZitatSieht Jemand generelle Denkfehler?

Ja, du hast dich vom blockierenden Gedanken nicht verabschiedet, und hoffst, dass das durch irgendwelche Tricks doch moeglich ist.
HttpUtils_NonblockingGet ist, wie der Name sagt, nicht blockierend. D.h. der Aufruf kommt sofort zurueck, und die Daten sind dann noch nicht fertig. Man kann nicht auf  $hash->{temp}{response} zugreifen, da dies erst wenige ms spaeter gefuellt wird.
Ein ein sleep hilft nicht dabei, auf diese Daten zu warten.

Du solltest das Pollen gedanklich in folgende Schritte umbauen:
- dem Geraet sagen, dass es Daten liefern soll (via HttpUtils_NonblockingGet)
- wenn das Geraet die Daten irgendwann geliefert hat, so tun, als ob er das freiwillig gemacht haette, und diese Daten analysieren, readings erstellen, notifies bauen, etc, alles in dem MyCallbackFunction, der sinnvollerweise "km200_ParseAnswer" oder so heissen sollte.
- Diese beiden Aufgaben nicht als eine Einheit sehen.

@ andre: da HttpUtils_NonblockingGet ein HTTP Header mit Laenge erwartet, wird die Callback nur einmal mit kompletten Daten aufgerufen. Oder man kann HttpUtils_NonblockingGet hier nicht verwenden.

justme1968

@rudi: das macht es einfach weil fall 2 dann hier erst mal weg fällt. gilt aber für die variante über selectlist weiterhin.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

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

Markus Bloch

Developer für Module: YAMAHA_AVR, YAMAHA_BD, FB_CALLMONITOR, FB_CALLLIST, PRESENCE, Pushsafer, LGTV_IP12, version

aktives Mitglied des FHEM e.V. (Technik)

Sailor

Hallo zusammen

OK, habe mich intensiv in das Thema des "forken" eingelesen aber ich komme nicht wirklich von der Stelle.

a) Kann mir Jemand ein gutes, aber einfaches Beispiel für "HttpUtils_NonblockingGet" nennen, wo Werte an den parent $hash zurückgegeben werden.

b) Wissend, dass meine Subroutine km200_GetData($) durch eine übergeordnete Routine 148 mal aufgerufen bzw. 148 Anfragen an das gleiche Gerät stellt frage ich mich, ob ich mir mit 148 geforkten Einzelprozessen, die alle gleichzeitig versuchen das KM200 Gerät abzufragen, nicht ins eigene  Knie schieße.

Im Grunde kann die übergeordnete Routine ja ruhig alles nacheinander machen. Sie soll nur nicht den fhem-Prozess blockieren.

"Programmieren kann Kopfschmerzen verursachen. Zu Risiken und Nebenwirkungen lesen sie Ihren "Programmers Guide und fragen Sie den Entwickler ihres Vertrauens."

Gruss
    Sailor
******************************
Man wird immer besser...

Markus Bloch

Schau dir mal die Yamaha Module in FHEM an. Beide Module verwenden HttpUtils_NonBlocking. Ansonsten kann ich gleich mal etwas beispielhaftes machen.
Developer für Module: YAMAHA_AVR, YAMAHA_BD, FB_CALLMONITOR, FB_CALLLIST, PRESENCE, Pushsafer, LGTV_IP12, version

aktives Mitglied des FHEM e.V. (Technik)

justme1968

das non blockig get hat nichts mit forken zu tun.

wie rudi schon gesagt hat: verabschiede dich von dem gedanken das du alles sequentiell selber kontrollierst.

mit dem aufruf von non blockig get sagst du dem device im prinzip das es daten senden soll.

danach tust du nichts mehr. weder warten noch sonst etwas.

die daten werden von fhem im hintergrund empfangen. ohne das du etwas tun musst. und ohne das etwas blockiert.

irgendwann später wenn die daten da sind ruft fhem deinen callback auf. dann kannst du sie verarbeiten.

das anfordern und das verarbeiten ist entkoppelt.

gruß
  andre

ein beispiel findest du z.b. im netatmo modul.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

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

Sailor

Zitat von: Markus Bloch am 31 Oktober 2014, 15:58:42
Ansonsten kann ich gleich mal etwas beispielhaftes machen.

Hallo Markus,

das wäre echt klasse.

Ein Beispiel "get" mit anschließendem Callback wo die aus "get" empfangenen Daten in ein Reading geschrieben werden wäre genau was ich brauche.

Ich erkläre mich auch bereit dies ins Wiki zu stellen. (Sobald ich dein Beispiel verstanden habe.   ;))

Dann schau ich mal was passiert, sobald ich 148 Anfragen gleichzeitig stelle... Armes KM200  ;D

Gruss
    Sailor
******************************
Man wird immer besser...