Endlosschleifen debuggen

Ab und zu passiert es mir, dass ich versehentlich ereignisgesteuerte Skripte derart verknüpfe, dass es zu Endlosschleifen kommen kann. Besonders gern passiert das, wenn man Ereignisse „bei Variablenaktualisierung“ definiert, weshalb das wohl auch nach Möglichkeit zu vermeiden ist, außer in begründeten Einzelfällen.

Aber selbst wenn man darauf verzichtet, entstehen solche Endlosschleifen mitunter. Das tückische dabei ist, dass man häufig nicht Unbedingt von seinem „Glück“ merkt, bis es irgendwann wahnsinnige Verzögerungen bei der Abarbeitung anderer Ereignisse gibt. Dann guckt man in die Thread-Ansicht, oder auch zu den Meldungen, und bemerkt dass sich irgendein Skript und irgendwelche Variablen ständig gegenseitig setzen, möglicherweise schon seit einer ganzen Weile. Leider kann es bei komplexeren Verknüpfungen dann sein, dass sich nicht eindeutig erkennen lässt, warum dies geschieht. Man kann dann versuchen, Ereignisse zu deaktivieren, Skriptbefehle auszukommentieren etc., aber häufig reagiert das System in diesem Stadium nur noch sporadisch und stark verzögert, so dass eine gezielte Fehlersuche durch Ausschluss kaum mehr möglich ist.

Also beendet man irgendwann einfach den IPS-Dienst und startet ihn neu - und oft ist es dann so, dass das Problem nicht gleich wieder auftritt, sondern auch erst wieder „nach einer Weile“. Es kann sich um Minuten handeln, oder auch um Tage, bis die entsprechenden Voraussetzungen wieder eintreten. Das macht es oft schwer, solche Fehler zu lokalisieren.

Daher wollte ich mal fragen, ob ihr „Tips und Tricks“ wisst, Erfahrungswerte habt, wie sich solche Probleme am besten bekämpfen lassen.

Grundsätzlich halte ich mich an folgende Regeln:

vermeiden von:

  • vielen Skripte die ständig gestartet werden. (z.B. sekündlich…)
  • Instanzen die <10sek Takt etwas aktualisieren
  • Variablen die sehr oft aktualisiert werden

Fehlersuche durch:

  • eigenes Loggingscript oder Fremdmodul wie Brownsons IPSLogger
  • geringe bzw. Standard max. execution time
  • Schalter für wichtige Scripte mittels derer ich diese für eine bestimmte Zeit an-/abschalten kann

Auch die Listenansicht mit Sortierung nach Aktualisierung kann hier helfen Fehlerquellen zu finden.

Ich simuliere gerne. Jedes Script wird bevor es scharf geschaltet wird, getestet. Ist halt je nach Umfang ein bischen aufwändig, hat sich aber bewährt.

Fehlerlogging ist aber auch aktiv. Egal was kommen sollte, ist der Übeltäter schnell verhaftet. :smiley:

Kommt bei mir meist nur in der Testphase vor (knock on wood) - siehe RWN’s post.

Da bau ich - wenn ich verknüpfte Scripte habe, was man ja ein Stück weit im Voraus weiß - kleine „Sicherungszeilen“ ein, wie z.B.:


if( file_exists( __FILE__ . ".STOP" ) || file_exists( "D:/STOP.STOP" ) ) return;

oder


if( file_exists( __FILE__ . ".WAIT" ) ) IPS_SLEEP( 1000 );

Letzteres reicht schon mal, um „aufgeschwungene“ Sachen wieder zu beruhigen und erstes - besonders der zweite Teil - hilft, IPS einzubremsen, falls nix mehr hilft.

Drauf gekommen bin ich damals, als ich ne Batch-Steuerung in IPS realisiert habe, bei der sich ein Script selbst aufruft und jeweils per Include einen Stapel abarbeitet.

Für fertige Scripte habe ich bei vielen ohnehin eine Ein- und Ausschaltfunktion, das wird aber über ein Framework gehandelt, welches ich mir gebaut hab. Da kann ich ggf. auch per Socket einen Schuss setzen (Falls die Maschine gar nicht mehr reagiert oder ich gar nicht da bin).

jwka

Ok ich habe die „Übeltäter“ jetzt möglicherweise gefunden. Es ist gar nicht so unknifflig. Also:

-ich habe eine Variable namens Soll-Temperatur, Typ Float. Diese enthält (wer hätt’s gedacht?) die Soll-Temeratur für eine Raumheizung.

-eine weitere Variable namens „Heizungsmodus“, welche ich zum komfortablen Setzen einer Temperatur übers Webfront benutze.

wenn Heizungsmodus=1 gesetzt wird, wird die Solltemperatur auf 21°C gesetzt, bei 0 auf 17.5°C und bei 2 auf 23°C.

Nun habe ich ein Skript, das auf beide Variablen reagiert und diese miteinander synchron hält. Heißt, wenn man Heizungsmodus setzt wird Soll-Temperatur entsprechend gesetzt, wenn man aber die Solltemperatur „händisch“ einstellt (bspw über Fernbedienung oder Webfront) und dabei eine der Standardtemperaturen trifft (z.B. 21°) dann soll Heizungsmodus auf den entsprechenden Wert (hier 1) gesetzt werden. Ist die eingestellte Temperatur keine Standardtemperatur, so setze ich Heizungsmodus auf -1, so dass im Webfront keiner der im Profil eingetragenen Werte aufleuchtet.

Nun kann es aber offenbar irgendwie vorkommen, dass sich zwei solcher Setz-Vorgänge derart ins Gehege kommen, dass sie sich immer wieder gegenseitig setzen. Endlos. Mir ist nicht klar wie das kommen kann, ich nehme an, es liegt irgendwie am Multithreading.

Ich könnte nun einfach eine Semaphore setzen, die ein Wiederaufrufen des Skripts verhindert, aber das würde zu Problemen führen bspw bei Abwesenheit: Dann werden nämlich mehrere Instanzen des Skripts hintereinander aufgerufen, für alle Räume (immer mit Heizungsmodus=0 für den jeweiligen Raum).

Mir wäre ja wesentlich wohler wenn ich zunächst einmal wirklich verstehen würde, warum es bei dieser Konstellation zu Endlosschleifen kommt? Die Trigger sind ja nun immer „bei Änderung“.

Hast Dir doch die Antwortselbst gegeben:

"Die Trigger sind ja nun immer „bei Änderung“

Dein Script (oder auch zwei) ändern „über kreuz“. Und rufen damit immer wieder sich selbst oder das jeweils andere auf. Da es dann wieder ne Aktualisierung gibt, geht’s grad von Vorne los.

Eine Möglichkeit wäre, dass Du die „LastExecute“ Zeit auswertets und sagst, dass da mindestens zwei Sekunden zwischen sein müssen, sonst ende (nicht die Variable setzen).

Im Normalbetrieb wird sowas immer ausreuchen, weil kein User binnen zwei Sekunden mehrfach ne Temperatur umsetzt. Und wenn doch mal: Dann muss er eben nochmals hinlangen.

jwka

Ja, das wird es wohl sein. Mich wunder nurt, dass dieser Zustand für so lange Zeit anhält. Müsste das nicht eigentlich „instabil“ sein und sobald mal eine Skript-Instanz eine Sekunde hängt hört das Spiel von selbst auf?

Nicht dass die Sache damit gelöst wäre, nur als Verständnisfrage.

Ein User nicht, aber das Goodbye-Skript schon (es ist für alle Räume das selbe Skript). Aber das mit dem Timestamp ist eine interessante Idee, man könnte ja stattdessen die Timestamps der Variablen untersuchen.

„Wenn du Variable schon innerhalb der letzten 2 Sekunden geändert wurdest, ändere ich dich nicht nochmal.“

Ja, das wird es wohl sein. Mich wunder nurt, dass dieser Zustand für so lange Zeit anhält. Müsste das nicht eigentlich „instabil“ sein und sobald mal eine Skript-Instanz eine Sekunde hängt hört das Spiel von selbst auf?

Das wäre eine ziemliches Armutszeugnis wenn IPS mal eben die Ausführung eines Events überspringen würde nur weil gerade „viel los ist“.

Ich denke ja nicht an überspringen, mehr an „aus dem Takt geraten.“ Die beiden Skriptaufrufe bilden ja eine Art virtuellen „Schwingkreis“ wenn man so will. Der dürfte doch nur so lange bestehen bleiben, wie die beiden Aufrufe exakt gegenläufiges Timing haben oder?

Möglicherweise ist das auch Quark und ich bemerke es nicht weil ich zu viel Eventlog geguckt habe… :eek:

Ich meine hier mal gelesen zu haben das IPS da stur eine Warteschlange abarbeitet.

@Sokkerheld:

klar, ist „Abfrage der letzten Aktualisierung der Var“ vielleicht die beste Lösung. Wenn aber Dein Script nicht gerade ein Child der Variable ist, musst Du ne ID mehr pflegen, um darauf zuzugreifen.

Daher mein Ansatz mit „Script-Lastrun“: Da kann man mit $IPS_SELF arbeiten.

Anyhow. Du hast da das Prinzip verstanden.

Und zum Thema „Dass IPS das aus dem Tritt kommt“: Das wird sicher auch irgendwann passieren, vernmutlich aber nach ganz arg langer Zeit erst, nämlich dann, wenn IPS die Warteschlange oder der Log_Buffer überläuft, wozu nur Paresy Stellung nehmen kann.

jwka

Sorry dass ich das Thema wieder hoch hole, aber bei mir ist es immer noch (leider) sehr aktuell. Es kommt an den verschiedensten Stellen immer mal wieder vor, dass sich zwei Variablen, die voneinander abhängen, gegenseitig die Änderungsskripte triggern, was dann endlos weitergeht und schnell zum Absturz bzw. erheblichen Lags führt.

Es ist kein Bug in IPS, sondern ein Problem der Herangehensweise. Warum wähle ich dann eine Herangehensweise, könnte man dann natürlich fragen, bei der sowas passieren kann?

Um es etwas besser verständlich zu machen, was ich da tue und warum, hier ein Beispiel:

Ich habe in einem Raum mehrere Variablen für das Licht. Eine namens Lichtstimmung, die die Index-Nummer der aktuellen Lichtstimmung enthält. Wenn man diese Variable ändert, sollen die Dimmer im Raum alle die jeweils für diesen Dimmer in dieser Stimmung korrekten Wert anfahren.

Jeder Dimmer hat nun wieder auch eine Variable, die den aktuellen Wert in % enthält. Diese werden wie gesagt vom Skript gesetzt sobald man die Variable für Lichtstimmung ändert, man kann sie aber auch einzeln setzen.

Soweit, so simpel.

Nun will ich aber gerne, dass wenn man alle Lampenvariablen einzeln, z.B. im Webfront, auf 0 setzt, dann auch die entsprechende Lichtstimmung in der Lichtstimmungs-Variablen (hier 0 für alles aus) gesetzt wird. Und dort beißt sich die Katze in den Schwanz.

Im Prinzip ist es gar kein Problem, denn wenn ich die letzte Lampe manuell auf 0 gestzt habe, daraufhin die Lichtstimmung z.B. von 1 auf 0 umspringt und er dort nochmals alle Lampen auf 0 setzt, dann werden deren onChange-Events ja gar nicht mehr gefeuert, weil sich ja nichts geändert habe. Wo liegt also das Problem?

Das Problem entsteht, wenn aus irgendeinem Grund zwei unterschiedliche Werte mehr oder midner zeitgleich gesetzt werden, die sich gegenseitig bedingen. Typisches Beispiel ist, dass jemand unkoordiniert auf der Fernbedienung herumdrückt und plötzlich zwei unterschiedliche Lichtszenen unmittelbar hintereinander aufruft.

In IPS scheint es eine Art Queue zu geben mit den Variablenänderungen, die dann durchgegangen wird um die entsprechenden Ereignisse auszulösen. Wenn zwei Ereignisaufrufe dabei sind, die sich auf Umwegen immer wieder gegenseitig auslösen (bspw Szene AUS schaltet Lampe A aus, aber bevor die Ereignisprozedur für Lampe A aufgerufen wird, kommt Szene EIN, welche wiederum Lampe A auf 80% setzt. Dann werden die Ereignisse für Lampe A durchgegangen, zunächst das Ereignis dass sie AUS geht, welches den Aufruf von Szene AUS zur Folge hat und bevor auf diese reagiert werden kann, direkt danach Lampe Änderugn auf 80%, woraufhin die Szene EIN gestzt wird… und wieder von vorne… sehr schnell, sehr oft hintereinander…)

Beim Licht ließ sich das dadurch verhindern, dass man eine Lampe einfach nicht mehrmals pro 2 Sekunden setzen kann, ansonsten wird der Schaltbefehl ignoriert (Ereignisprozedur bricht sofort ab).

Bei Lautstärkewerten kann das aber z.B. schwierig werden, denn diese darf man natürlich auch mehrfach schnell hintereinander verändern (Stichwort Fernbedienung gedrückt halten). Lautstärkewerte sind bei mir auch teilweise recht komplex verknüpft (2 Audio-Kanäle mit Masterregler-variablen, die zu 8 Raumreglervariablen äquivalent sind und mit diesen synchronisiert werden, wenn diese auf den entsprechenden Kanal eingestellt sind, ansonsten unabhängig). Es gibt Probleme wenn man schnell zweimal hintereinander die Lautstärkewerte ändert.

Ich kann aber hier nicht einfach sagen „wenn schonmal geändert in den letzten 2 Sekunden, dann return“, denn es gibt hier ja viele Fälle in denen so häufige Aufrufe legitim sind… knifflig.

Hat jemand eine gute Idee, wie man mit sowas umgehen kann?

Und bitte, Tipps wie „keep it simple“ und „mach sowas einfach nicht“ helfen mir in dieser Sache nicht. Ich weiß selbst, dass ich es mir leichter machen könnte indem ich auf Funktionalität verzichte. Es ist auch nicht so dass meine Skripte nicht 99% gut funktionieren würden, es geht nur um diese eine blöde Situation wenn sich da zwei Änderungen gegenseitig auslösen.

Dafür suche ich händeringend nach einer Lösung, am liebsten wäre mir eine generische Prozedur die man oben in ein Skript schreiben kann, zumindest als „Fallschirm“.

Auch würde mich interessieren ob schon andere über ähnliche Probleme gestolpert sind.

Wenn ich es beim überfliegen Deiner recht langen Ausführung richtig sehe würde ich den Versender der Änderung einbeziehen, also WF oder Fernbed. also Variable.

Nur als Rettungsfallschirm - warum fragst Du nicht ab wann das Skript zum letzen mal ausgeführt wurde?

Ist doch unwahrscheinlich, dass Du eine Aktion innerhalb weniger Millisekunden mehrfach manuell ausführst.

Das kuriert zwar nicht die Ursache aber eventuell lindert es das Symptom. Der Königsweg wäre es aber natürlich solche Schleifen von Haus aus zu vermeiden. Auch wie von Ferengi-Master angeregt den Absender des Triggers auszuwerten macht Sinn.

Ich hatte sowas ähnliches mit meinem Repeater für FS20 Funksignale.

Was ich bei solchen Sachen mache, ist dass ich:

1.) Scripten ne Alias-ID vergebe und diese zusammen mit dem Wert der letzen Auslösung ( mircrotime( true) ) wegschreibe. Beim nächsten Aufruf vergleiche ich (auf ID oder Gruppen-ID-Basis) eine mindestens abzuwartende Zeit.

2.) Bei Verknüpften Vorgängen baue ich Semaphoren, mit denen ich ganze Prozessketten ausschalten kann. So verhindere ich z.B. bei meinem FS20 Repeater bei einem Ablauf von Macros, dass Endlosschleifen entstehen. Ich schalte dann die Repeat-Funktion einfach aus und der letzte Macro-Befehl schaltet die Basisfunktion wieder ein.

Vielleicht hilft Dir das auch?

jwka