ioBroker.vis icon indicating copy to clipboard operation
ioBroker.vis copied to clipboard

Mem-Leak bei Nutzung von Variablen-Bindings in String Widget

Open cratoo opened this issue 2 years ago • 15 comments

Describe the bug
Wenn man im vis ein Javascript binding nutzt scheint das zu einem Mem-Leak zu führen.

To Reproduce

  1. String-Widget auf einem View platzieren
  2. Im "HTML anhängen" Feld ein Binding einfügen: Z.B. {v:0_userdata.0.some.path.value;(v/1000).toFixed(1)}
  3. Im Desktop Chrome kann man dem Speicher-Verbrauch des vis-Tabs beim Wachsen zusehen. (v.a. wenn man viele der Bindings nutzt)
  4. Ersetzt man in meinem Fall das String Widget durch das Number-Widget und macht die Umrechnung ohne Binding bleibt der Ram-Verbrauch konstant.

Expected behavior
Speicher-Verbrauch sollte konstant bleiben.

Versions:

  • Adapter version: 1.4.5
  • JS-Controller version: 3.3.2
  • Node version: 12.22.5
  • Operating system: Docker

cratoo avatar May 23 '22 09:05 cratoo

+1 on that. See linked Bug-report

seb2010 avatar Jul 19 '22 15:07 seb2010

@Apollon77, ist hier ein Fix in Aussicht?

seb2010 avatar Aug 04 '22 05:08 seb2010

Bluefox arbeitet an vis 2.0 ... denke damit sollte man das Thema nochmal betrachten ob es noch auftritt ... Sowas zu finden ist nicht gerade einfach

Apollon77 avatar Aug 04 '22 05:08 Apollon77

Also ich kann hier auf dem kompletten Holzweg sein, aber hier mal meine Analyse: Ich sehe sehr viele detached Elements im Heap-Snapshot, bei denen es sich um die DIVElements der Widgets mit variablen Bindings handelt.

Wenn per Laufzeit ein Variablen-Binding aktualisiert wird, wird in der vis.js die Funktion reRenderWidget und dabei dann renderWidget aufgerufen. Wenn dann diese Funktionen das $widget mit replaceWith autauscht, entstehen verwaiste Elemente. Ich denke das sollte nicht so sein. Ggf. bereinigt jQuery hier doch nicht ganz korrekt das Element von bestehenden Referenzen und die Garbage-Collection funktioniert nicht richtig.

In der reRenderWidget-Funktion entstehen bei Aufruf von renderWidget bspw. 3 (je nach Komplexität des Widgets) neue detached Elements:

  • einmal das Widget-Div mit der ID "w00???_removed" (class "vis-widget vis-tpl-basic-HTML")
  • dann vermutlich auch der body des DIVs mit der class "vis-widget-body"
  • das Dritte ist ein "<span>@@!!@@</span>" Objekt Ich glaube auch, dass bspw. die DIVs für Signal-Icons, welche in einem Widget mit Variablen-Binding hängen mit verwaisen. Also im Grunde alle Child-Elemente des Widgets... Daher variiert die Anzahl der verwaisten Elemente mit der Komplexität des Widgets.

Ich bin aber auch kein Experte in der Nutzung der Dev-Tools oder der Javascript Programmierung... Wenn ich etwas spezielles herausfinden soll, sag Bescheid.

seb2010 avatar Aug 05 '22 08:08 seb2010

wie kann ich die vis.js Datei "uploaden"? Ich versuche hier ein bisschen am Code rumzuspielen...

seb2010 avatar Aug 06 '22 07:08 seb2010

Das kommt ja stark drauf an, wie bei dir iobroker läuft. Bei mir ist z.B. das data Verzeichnis in einen Container eingebunden und ich könnte die Datei in diesem Verzeichnis bearbeiten. Ein "Upload" ist da glaube ich kaum vorgesehen, ausser du gehst über einen Update des vis Adapters, aber da dürfte der Aufwand von bearbeiten, adapter bauen und aktualisieren zu gross sein.

cratoo avatar Aug 06 '22 09:08 cratoo

Ich hab iobroker auf nem Pi image laufen. Also totaler Standard. Wenn ich unter /opt/iobroker/iobroker-data/files/vis/js dann aber die vis.js anpasse, müsste ich die irgendwie erst "uploaden" (wohin auch immer), so dass die auch aus dem Web/LAN erreichbar ist. Mit der index.html klappt das per iobroker upload aber nicht mit der vis.js...

seb2010 avatar Aug 06 '22 10:08 seb2010

Meines Verständnisses nach kannst du die da vor Ort anpassen und und ein reload des vis müsste die Änderung dann laden. Da Verzeichnis ist ja der Ort von wo das vis geladen wird. Du kannst ja mal absichtlich einen Syntax Fehler einbauen und den reload machen. Dann müsste das vis kaputt sein.

cratoo avatar Aug 06 '22 10:08 cratoo

Ich habe jetzt mal ein bisschen herumprobiert. Ich hatte die Widget-Aktualisierung in der "vis.js" im verdacht, aber alle Versuche hier anzusetzen haben nichts gebracht. Ob ggf. Probleme mit CanJS bestehen kann ich nicht sagen.

Ich habe dann nochmal ein bisschen weiter mit den Dev-Tools gespielt und es wurde dann bei Speicherspikes oft auf die Funktion "htmlRefresh" in der "index.html" für DetachedHTMLDIVElements verwiesen.

Kann mir einer sagen wofür die Funktion "htmlRefresh" unter "vis.binds.basic" in der "index.html" Datei steht. Wenn ich die Funktion auskommentiere (also den Funktions-Code, nicht die Funktion selber, da sie ja aufgerufen wird), scheint es keinen Memory-Leak zu geben. Meine große Vis, die nach Minuten GB-Größe angenommen hat, bleibt jetzt bei ~180MB in EDGE. Zuvor hatte ich nach einiger Zeit 10.000de Detached Elements, jetzt mit auskommentiertem Funktions-Body weniger als 100 über eine lange Zeit. Ich sehe keine Nachteile in der Vis.

@cratoo: kannst du das mal verifizieren?

update: so langsam schließt sich der Kreis. Für die Aktualisierung eines Widgets wird ein neues Can-Objekt (was das ist weiß ich bisher auch noch nicht) erzeugt. Bei der Initialisierung des Objekt wird diese htmlRefresh-Funktion aufgerufen.

seb2010 avatar Aug 11 '22 11:08 seb2010

Den Mem-Leak kann ich bei mir ja nicht mehr nachstellen, da ich die Bindings rausgeworfen und durch passendere Widgets ersetzt habe. das htmlRefresh hab ich bei mir mal auskommentiert und sehe im ersten Moment auch keinen Nachteil. Ich lass es so mal laufen, aber irgendeinen Zweck wird es haben.

cratoo avatar Aug 11 '22 11:08 cratoo

Kannst du dir nicht schnell ne View bauen, die das reproduziert?

seb2010 avatar Aug 11 '22 11:08 seb2010

Hab ich jetzt mal versucht, aber ich bekomm meine ursprüngliche Funktion gar nicht mehr reproduziert. Wenn ich das Binding, wie von mir oben angegeben, in das HTML anhängen packe, steht im vis die ganze Zeit nur 0.0, obwohl der Wert anders sein müsste. Sowohl wenn ich htmlRefresh auskommentiere, als auch wenn es drin ist.

cratoo avatar Aug 11 '22 12:08 cratoo

Es scheint auch nicht an der $this.html(html); Zeile allein zu liegen. Wenn nur die auskommentiert ist, leaked es wieder. Ich weiß zwar nicht genau wie es funktioniert, aber ich denke die setInterval-Funktion wird bei dem Austausch/Zerstören des Objekts per JQuery nicht mit bereinigt, es bleibt dann ein Detached-Object und die Garbage-Collection räumt es nicht auf. @Apollon77: hast du hier vielleicht die Lösung, welche auch den eigentlich Sinn der Funktion erhalten kann? (welcher auch immer das ist)

seb2010 avatar Aug 11 '22 18:08 seb2010

die Funktion von htmlRefresh ist bei einem HTML-Widget eben genau die Aktualisierung des Widgets mit der im Vis-Editor unter "Updatezeit(ms):" eingestellten Refreshzeit. Ich hatte die aus irgendeinem Grund auf 2000ms stehen. Wenn sie auf 0 steht, wird die Funktion gar nicht aufgerufen. Macht mich jetzt wieder skeptisch, ob das wirklich das "Problem" war.

Ich vermute, es ist die Kombination aus "Updatezeit(ms):" eines Widgets >0 --> htmlRefresh-Funktion wird mit setInterval genutzt und einem Variablen-Binding im Widget. Durch letzteres werden JQuery-Befehle genutzt um das Widget zu ersetzen. Das setInterval verhindert vermutlich dabei die korrekte Garbage-Collection, dahier noch eine unaufgeräumte Referenz bestehen bleibt.

seb2010 avatar Aug 11 '22 21:08 seb2010

Ich kann hier garnicht helfen, aber @GermanBluefox ist der richtige und denke die Infos die Ihr hier sammelt helfen Ihm da ungemein

Apollon77 avatar Aug 12 '22 07:08 Apollon77