Wie kann ein synchroner AJAX-Aufruf zu einem Speicherleck führen?

Lesezeit: 11 Minuten

Benutzer-Avatar
Johnbabu Koppolu

Ich verstehe diesen allgemeinen Rat gegen die Verwendung von synchronen Ajax-Aufrufen, da die synchronen Aufrufe das UI-Rendering blockieren.

Der andere allgemein angegebene Grund sind Probleme mit Speicherlecks synchron AJAX.

Von dem MDN Dokumente –

Hinweis: Sie sollten keine synchronen XMLHttpRequests verwenden, da es aufgrund der inhärent asynchronen Natur von Netzwerken verschiedene Möglichkeiten gibt, wie Speicher und Ereignisse bei der Verwendung synchroner Anforderungen lecken können. Die einzige Ausnahme ist, dass synchrone Anforderungen innerhalb von Workers gut funktionieren.

Wie können synchrone Aufrufe zu Speicherlecks führen?

Ich suche ein praktisches Beispiel. Literaturhinweise zu diesem Thema wären toll.

  • @Shmiddty – Ich habe vorerst wie Tage gegoogelt. Ja, ich habe die Seite gesehen, auf die Sie sich beziehen, aber die Erklärung scheint sich auf ältere Versionen von IE zu beziehen. (IE < 9)

    – Johnbabu Koppolu

    16. Januar 2013 um 18:21 Uhr


  • @Shmiddty – es ist IE-spezifisch und spricht nicht von “synchronen” AJAX-Anfragen.

    – Johnbabu Koppolu

    16. Januar 2013 um 18:30 Uhr

  • Ist es möglich, dass die Notiz: ist auch spezifisch für IE?

    – Kevin B

    16. Januar 2013 um 18:39 Uhr

  • @ Kevin B – genau, ich konnte keine endgültigen Antworten finden. Hinweis ist spezifisch für ‘synchrone’ Anrufe.

    – Johnbabu Koppolu

    17. Januar 2013 um 4:20 Uhr


  • Wenn überhaupt, hatte Async mehr Leckpotenzial durch Schließen der Variablen seines Funktions-Callbacks …

    – dandavis

    21. August 2013 um 21:52 Uhr

Benutzer-Avatar
Esailija

Wenn XHR korrekt implementiert ist pro spezdann wird es nicht auslaufen:

Ein XMLHttpRequest-Objekt darf nicht von der Garbage Collection erfasst werden, wenn sein Zustand OPENED ist und das send()-Flag gesetzt ist, sein Zustand HEADERS_RECEIVED ist oder sein Zustand LOADING ist und eine der folgenden Bedingungen erfüllt ist:

Es hat einen oder mehrere Ereignis-Listener registriert, deren Typ readystatechange, progress, abort, error, load, timeout oder loadend ist.

Das Flag „Upload abgeschlossen“ ist nicht gesetzt, und für das zugeordnete XMLHttpRequestUpload-Objekt sind ein oder mehrere Ereignis-Listener registriert, deren Typ „Fortschritt“, „Abbruch“, „Fehler“, „Laden“, „Zeitüberschreitung“ oder „Ladenende“ ist.

Wenn ein XMLHttpRequest-Objekt von der Garbage Collection erfasst wird, während seine Verbindung noch offen ist, muss der Benutzeragent jede Instanz des von diesem Objekt geöffneten Abrufalgorithmus abbrechen, alle für sie in die Warteschlange gestellten Aufgaben und alle weiteren Daten, die für sie aus dem Netzwerk empfangen wurden, verwerfen.

Also nachdem du getroffen hast .send() Das XHR-Objekt (und alles, auf das es verweist) wird immun gegen GC. Jedoch, Jeder Fehler oder Erfolg versetzt den XHR in den DONE-Zustand und es wird wieder Gegenstand von GC. Es spielt überhaupt keine Rolle, ob das XHR-Objekt synchron oder asynchron ist. Im Falle einer langen Synchronisierungsanforderung spielt dies keine Rolle, da Sie nur an der Sendeanweisung hängen bleiben, bis der Server antwortet.

Allerdings gem diese Folie Es wurde zumindest in Chrome/Chromium im Jahr 2012 nicht korrekt implementiert. Per Spezifikation wäre kein Anruf erforderlich .abort() da der DONE-Zustand bedeutet, dass das XHR-Objekt normalerweise bereits GCd sein sollte.

Ich kann nicht den geringsten Beweis finden, um die MDN-Aussage zu untermauern, und ich habe den Autor über Twitter kontaktiert.

  • “Jeder Fehler oder Erfolg versetzt den XHR in den DONE-Zustand” – macht es Ihnen etwas aus, einen Verweis darauf hinzuzufügen? Können wir auch eine Leistung oder Geige reproduzieren, die ein tatsächliches Speicherleck in Sync-Ajax in einigen seltsamen Situationen veranschaulicht? (Mit Abbruch oder was auch immer)

    – Benjamin Grünbaum

    17. August 2013 um 18:00 Uhr

  • @BenjaminGruenbaum die Spezifikation, die oben verlinkt wurde? 😛 Siehe hier im Fehlerfall, Schritt 4 ändert den Status auf DONE. w3.org/TR/XMLHttpRequest/#infrastructure-for-the-send()-Methode

    – Esailija

    17. August 2013 um 18:35 Uhr


  • “Wenn XHR gemäß den Spezifikationen korrekt implementiert ist, wird es nicht durchsickern” – Worum geht es? in der Praxis ? Vielleicht ist dies ein IE-Problem?

    – Benjamin Grünbaum

    18. August 2013 um 8:06 Uhr

  • @BenjaminGruenbaum Ich beziehe mich auf das Üben mit der Folie. Immer noch nichts mit synchronem xhr zu tun, da Sie in readyState DONE genauso wahrscheinlich nicht abort aufrufen würden wie bei Verwendung von sync xhr. Die IE-Bugs sind alt und nicht spezifisch für Synchronous.

    – Esailija

    18. August 2013 um 10:58 Uhr

  • @Esailija Vielleicht möchten Sie diese Antwort aktualisieren, um zu berücksichtigen, dass die Dokumente diese Notiz nicht mehr anzeigen

    – Camilo Terevinto

    2. Januar 2018 um 10:11 Uhr

Ich denke, dass Speicherlecks hauptsächlich auftreten, weil der Garbage Collector seine Arbeit nicht erledigen kann. Dh du hast einen Verweis auf etwas und der GC kann ihn nicht löschen. Ich habe ein einfaches Beispiel geschrieben:

var getDataSync = function(url) {
    console.log("getDataSync");
    var request = new XMLHttpRequest();
    request.open('GET', url, false);  // `false` makes the request synchronous
    try {
        request.send(null);
        if(request.status === 200) {
            return request.responseText;
        } else {
            return "";
        }
    } catch(e) {
        console.log("!ERROR");
    }
}

var getDataAsync = function(url, callback) {
    console.log("getDataAsync");
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onload = function (e) {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                callback(xhr.responseText);
            } else {
                callback("");
            }
        }
    };
    xhr.onerror = function (e) {
        callback("");
    };
    xhr.send(null);
}

var requestsMade = 0
var requests = 1;
var url = "http://missing-url";
for(var i=0; i<requests; i++, requestsMade++) {
    getDataSync(url);
    // getDataAsync(url);
}

Abgesehen davon, dass die synchrone Funktion viele Dinge blockiert, gibt es einen weiteren großen Unterschied. Die Fehlerbehandlung. Wenn du benutzt getDataSync und entfernen Sie den Try-Catch-Block und aktualisieren Sie die Seite, Sie werden sehen, dass ein Fehler ausgelöst wird. Das liegt daran, dass die URL nicht existiert, aber die Frage ist jetzt, wie der Garbage Collector funktioniert, wenn ein Fehler geworfen wird. Löscht es alle mit dem Fehler verbundenen Objekte, behält es das Fehlerobjekt oder so ähnlich. Ich würde mich freuen, wenn jemand mehr darüber weiß und hier schreibt.

  • Ihr Beispiel beweist oder widerlegt nicht die Behauptung, dass synchrones XHR Speicher verliert. Ich habe Ihre Antwort herabgestuft, sodass neue (aufschlussreiche) Antworten höher angezeigt werden als diese spekulative Antwort.

    – Rob W

    17. August 2013 um 13:06 Uhr

  • Meine Antwort war eher eine Annahme, dass das Speicherleck auf den Fehler zurückzuführen sein könnte, der während des Sync-XHR-Aufrufs ausgelöst wurde. Wie auch immer, ich stimme zu, dass ich den Beweis nicht erbracht habe.

    – Krasimir

    17. August 2013 um 13:26 Uhr


  • Jeder Fehler versetzt xhr in den DONE-Zustand, was per Spezifikation bedeutet, dass die GC-Immunität des Objekts aufgehoben werden muss. Das XHR-Objekt ist nur immun gegen GC, während eine Anfrage läuft.

    – Esailija

    17. August 2013 um 14:41 Uhr


Wenn der synchrone Aufruf unterbrochen wird (dh durch ein Benutzerereignis, das das XMLHttpRequest-Objekt wiederverwendet), bevor er abgeschlossen ist, kann die ausstehende Netzwerkabfrage hängen bleiben und kann nicht bereinigt werden.

Dies liegt daran, dass die Rückgabe nicht abgeschlossen werden kann, wenn das Objekt, das die Anfrage initiiert hat, nicht existiert, wenn die Anfrage zurückkehrt, aber (wenn der Browser nicht perfekt ist) im Speicher verbleibt. Sie können dies einfach mit setTimeout veranlassen, um das Anforderungsobjekt zu löschen, nachdem die Anforderung gestellt wurde, aber bevor sie zurückkehrt.

Ich erinnere mich, dass ich ungefähr 2009 im IE ein großes Problem damit hatte, aber ich würde hoffen, dass moderne Browser nicht dafür anfällig sind. Sicherlich verhindern moderne Bibliotheken (z. B. JQuery) die Situationen, in denen dies auftreten könnte, und ermöglichen es, Anfragen zu stellen, ohne darüber nachdenken zu müssen.

  • Nachdem du getroffen hast .send() Bei einer synchronen Anfrage wird die Steuerung nicht zu Ihnen zurückkehren, bis ein Fehler oder Erfolg auftritt. Die xhr-Ereignisse werden auch nicht ausgelöst, wenn die Anfrage synchron ist. Alle externen Ereignisse werden nicht behandelt, bis die Ausführung nachgibt. Sie haben Recht, dass es wahrscheinlich Probleme geben würde, wenn die Spezifikation nicht wie angegeben implementiert wird. Aber das ist überhaupt nicht die Schuld von synchronen Anfragen.

    – Esailija

    22. August 2013 um 13:17 Uhr


  • Sie können dies einfach mit setTimeout veranlassen, um das Anforderungsobjekt zu löschen, nachdem die Anforderung gestellt wurde, aber bevor sie zurückkehrt. Dies ist der gleiche Fehler wie in der anderen Antwort. Wenn Sie eine Zeitüberschreitung vornehmen, wird die Zeitüberschreitung erst behandelt, nachdem die Synchronisierungsanforderung bereits aufgelöst wurde. Die Spezifikation ist so geschrieben, dass, wenn Ihre Anfrage synchron ist, alles eingefroren wird, wenn sich das Objekt im Immun-to-GC-Zustand befindet.

    – Esailija

    22. August 2013 um 13:26 Uhr


  • @Esailija Du hast absolut Recht. Trotzdem, wie ich schon sagte (und ich weiß nicht, ob das immer noch stimmt), implementieren (ed) viele Browser Javascript so, dass vom Benutzer ausgelöste Ereignisse (dh “onClick”, die Zeitüberschreitungen enthalten können oder nicht) unterbrechen würden der aktuell ausgeführte Thread, der die Steuerung an den Ereignishandler übergibt, bis er aufgelöst wird. Dies führte zu erheblichen Problemen, wenn eine Funktion, die mit einer globalen Variablen arbeitete, durch ein Ereignis unterbrochen wurde, das auch diese Variable modifizierte.

    – Benuvogel

    22. August 2013 um 13:51 Uhr

  • “Wenn ich mich richtig erinnere … war dies die einzige Erklärung, die ich finden konnte, die zu dem beobachteten Verhalten passte.” — eine verblasste Erinnerung an eine empirische Erklärung eines schlecht verstandenen Phänomens (an die man sich nicht selbst erinnert) ist ein ziemlich schwacher Beweis. Aber zumindest wissen wir, welchen Browser wir auf Thread-Unterbrechung testen müssen. Aber wenn IE6 das getan hätte, wäre ich mir ziemlich sicher, dass ich es inzwischen gewusst hätte.

    – John Dvorak

    22. August 2013 um 14:32 Uhr


  • @JanDvorak: Sie wissen sicher, dass Javascript garantiert Single-Threaded ist? – nein 🙂 Allerdings glaube ich nicht, dass dies für sjax gilt

    – Bergi

    22. August 2013 um 20:19 Uhr

Benutzer-Avatar
Dmitri Kaigorodow

Synchronisieren Sie die Threadausführung des XHR-Blocks und alle Objekte im Funktionsausführungsstapel dieses Threads von GC.

Z.B:

function (b) { 
  var a = <big data>;
  <work with> a and b
  sync XHR
}

Die Variablen a und b werden hier blockiert (und auch der gesamte Stack). Wenn also GC zu arbeiten begonnen hat und Sync XHR den Stack blockiert hat, werden alle Stack-Variablen als “survived GC” markiert und vom frühen Heap auf den persistenteren verschoben. Und ein Ton von Objekten, die nicht einmal die einzelne GC überleben sollten, wird viele Garbage Collections leben, und sogar Referenzen von diesen Objekten werden GC überleben.

Informationen zu Claims Stack Blocks GC und zu Objekten, die als langlebige Objekte gekennzeichnet sind: siehe Abschnitt Conservative Garbage Collection in
Unser Weg zurück zur Präzision. Auch “markierte” Objekte werden GCed nach der übliche Haufen ist GCed, und normalerweise nur wenn noch mehr Speicher freigegeben werden muss (da das Sammeln von markierten und gefegten Objekten mehr Zeit in Anspruch nimmt).

UPDATE: Ist es wirklich ein Leck, nicht nur eine ineffektive Lösung für einen frühen Haufen? Es gibt mehrere Dinge zu beachten.

  • Wie lange werden diese Objekte gesperrt, nachdem die Anfrage abgeschlossen ist?
  • Sync XHR kann Stack für unbegrenzte Zeit blockieren, XHR hat keine Timeout-Eigenschaft (in allen Nicht-IE-Browsern), Netzwerkprobleme sind nicht selten.
  • Wie viele UI-Elemente sind gesperrt? Wenn es 20 MB Speicher für nur 1 Sekunde blockiert == 200.000 Lead in 2 Minuten. Betrachten Sie viele Hintergrundregisterkarten.
  • Betrachten Sie den Fall, wenn eine einzelne Synchronisierung den Ton der Ressourcen blockiert und der Browser in die Auslagerungsdatei wechselt
  • Wenn ein anderes Ereignis versucht, DOM in zu ändern, kann es durch die Synchronisierung von XHR blockiert werden, ein anderer Thread wird blockiert (und auch der gesamte Stack).
  • Wenn der Benutzer die Aktionen wiederholt, die zur Synchronisierung von XHR führen, wird die ganz Browserfenster wird gesperrt. Browser verwenden max=2 Thread, um Fensterereignisse zu verarbeiten.
  • Auch ohne Blockierung verbraucht dies viele Betriebssystem- und Browser-interne Ressourcen: Thread, kritische Abschnittsressourcen, UI-Ressourcen, DOM … Stellen Sie sich vor, Sie können (aufgrund von Speicherproblemen) 10 Registerkarten mit Websites öffnen, die Sync-XHR verwenden, und 100 Registerkarten mit Websites die asynchrones XHR verwenden. Ist das nicht Speicherleck.

Speicherlecks bei synchronen AJAX-Anfragen werden häufig verursacht durch:

  • Verwendung von setInterval/setTimout, was Zirkularaufrufe verursacht.
  • XmlHttpRequest – Wenn der Verweis entfernt wird, wird xhr unzugänglich

Memory Leak tritt auf, wenn der Browser aus irgendeinem Grund keinen Speicher von Objekten freigibt, die nicht mehr benötigt werden.

Dies kann aufgrund von Browserfehlern, Problemen mit Browsererweiterungen und viel seltener aufgrund unserer Fehler in der Codearchitektur geschehen.

Hier ist ein Beispiel für ein Speicherleck, das verursacht wird, wenn setInterval in einem neuen Kontext ausgeführt wird:

var
Context  = process.binding('evals').Context,
Script   = process.binding('evals').Script,
total    = 5000,
result   = null;

process.nextTick(function memory() {
  var mem = process.memoryUsage();
  console.log('rss:', Math.round(((mem.rss/1024)/1024)) + "MB");
  setTimeout(memory, 100);
});

console.log("STARTING");
process.nextTick(function run() {
  var context = new Context();

  context.setInterval = setInterval;

  Script.runInContext('setInterval(function() {}, 0);',
                      context, 'test.js');
  total--;
  if (total) {
    process.nextTick(run);
  } else {
    console.log("COMPLETE");
  }
});

  • gute Antwort. Können Sie explizit erklären, warum Ihr Code für Laienprogrammierer ein Leck verursacht?

    – d’alar’cop

    16. August 2013 um 17:01 Uhr


  • Das ist nett und sagt interessante Dinge über Speicherlecks und JavaScript aus – aber ich sehe nichts, was mit Sync-Ajax zu tun hat (insbesondere im Vergleich zu Async-Ajax, das den Kontext dieser Frage darstellt).

    – Benjamin Grünbaum

    16. August 2013 um 17:42 Uhr

  • gute Antwort. Können Sie explizit erklären, warum Ihr Code für Laienprogrammierer ein Leck verursacht?

    – d’alar’cop

    16. August 2013 um 17:01 Uhr


  • Das ist nett und sagt interessante Dinge über Speicherlecks und JavaScript aus – aber ich sehe nichts, was mit Sync-Ajax zu tun hat (insbesondere im Vergleich zu Async-Ajax, das den Kontext dieser Frage darstellt).

    – Benjamin Grünbaum

    16. August 2013 um 17:42 Uhr

1206260cookie-checkWie kann ein synchroner AJAX-Aufruf zu einem Speicherleck führen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy