Pregunta ¿Cómo la llamada síncrona AJAX podría causar pérdida de memoria?


entiendo esta consejos generales sobre el uso de llamadas ajax sincrónicas, porque las llamadas síncronas bloquean la representación de la IU.

La otra razón generalmente dada es la pérdida de memoria conlleva sincrónico AJAX.

Desde el MDN documentos -

Nota: No debe usar XMLHttpRequests síncrono porque, debido a   la naturaleza inherentemente asíncrona de las redes, hay varios   formas en que la memoria y los eventos pueden tener fugas al usar solicitudes sincrónicas. los   La única excepción es que las solicitudes sincrónicas funcionan bien dentro de los trabajadores.

¿Cómo las llamadas sincrónicas podrían causar pérdidas de memoria?

Estoy buscando un ejemplo práctico. Cualquier referencia a cualquier literatura sobre este tema sería genial.


32
2018-01-16 18:15


origen


Respuestas:


Si XHR se implementa correctamente por especificación, entonces no tendrá fugas:

Un objeto XMLHttpRequest no debe ser recolectado como basura si su estado es   ABIERTO y se establece el indicador de envío (), su estado es HEADERS_RECEIVED, o   su estado es LOADING, y uno de los siguientes es verdadero:

Tiene uno o más detectores de eventos registrados cuyo tipo es   readystatechange, progress, abort, error, load, timeout o loadend.

El indicador de carga completa está desarmado y el indicador asociado   El objeto XMLHttpRequestUpload tiene uno o más detectores de eventos registrados   cuyo tipo es progress, abort, error, load, timeout o loadend.

Si un objeto XMLHttpRequest es basura recolectada mientras su conexión   todavía está abierto, el agente de usuario debe cancelar cualquier instancia de la búsqueda   algoritmo abierto por este objeto, descartando cualquier tarea en cola para ellos,   y desechando cualquier información adicional recibida de la red por ellos.

Entonces, después de golpear .send() el objeto XHR (y todo lo que haga referencia) se vuelve inmune a GC. Sin embargo, cualquier error o éxito pondrá el XHR en estado DONADO y vuelve a estar sujeto a GC. No importaría en absoluto si el objeto XHR es de sincronización o asíncrono. En el caso de una solicitud de sincronización larga nuevamente, no importa porque simplemente estarías atrapado en la instrucción de envío hasta que el servidor responda.

Sin embargo, según esta diapositiva no se implementó correctamente al menos en Chrome / Chromium en 2012. Según la especificación, no habría necesidad de llamar .abort() ya que el estado HECHO significa que el objeto XHR ya debería ser normalmente GCd.

No puedo encontrar la más mínima evidencia para respaldar la declaración de MDN y me he contactado con el autor a través de Twitter.


13
2017-08-17 14:51



Creo que las pérdidas de memoria ocurren principalmente porque el recolector de basura no puede hacer su trabajo. Es decir. tiene una referencia a algo y el GC no puede eliminarlo. Escribí un ejemplo simple:

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);
}

Excepto por el hecho de que la función sincrónica bloquea muchas cosas, hay otra gran diferencia. El manejo del error Si utiliza getDataSync y elimine el bloque try-catch y actualice la página, verá que se produce un error. Eso es porque la url no existe, pero la pregunta ahora es cómo funciona el recolector de basura cuando se produce un error. ¿Borra todos los objetos conectados con el error? ¿Mantiene el objeto de error o algo así? Me alegrará que alguien sepa más sobre eso y escriba aquí.


3
2017-08-17 12:31



Si la llamada síncrona se interrumpe (es decir, por un evento del usuario que reutiliza el objeto XMLHttpRequest) antes de que se complete, la consulta de red pendiente puede dejarse colgando, sin poder ser recolectada.

Esto se debe a que, si el objeto que inició la solicitud no existe cuando la solicitud retorna, la devolución no puede completarse, pero (si el navegador es imperfecto) permanece en la memoria. Puede hacer esto fácilmente usando setTimeout para eliminar el objeto de solicitud después de que se haya realizado la solicitud pero antes de que vuelva.

Recuerdo que tuve un gran problema con esto en IE, allá por 2009, pero espero que los navegadores modernos no sean susceptibles. Ciertamente, las bibliotecas modernas (es decir, JQuery) evitan las situaciones en las que podría ocurrir, permitiendo que las solicitudes se realicen sin tener que pensar en ello.


3
2017-08-22 13:11



Sincronice la ejecución del hilo del bloque XHR y todos los objetos en la pila de ejecución de funciones de este hilo desde GC.

P.ej.:

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

Las variables a y b están bloqueadas aquí (y toda la pila también). Entonces, si GC comenzó a funcionar, entonces la sincronización XHR ha bloqueado la pila, todas las variables de la pila se marcarán como "GC sobrevivido" y se moverán de la pila inicial a la más persistente. Y un tono de objetos que no debería sobrevivir incluso el GC único vivirá muchas colecciones de basura e incluso las referencias de estos objetos sobrevivirán al GC.

Acerca de las solicitudes de bloques de pila GC, y ese objeto marcado como objetos de larga duración: consulte la sección Colección de basura conservadora en Agarrando nuestro camino de regreso a la precisión. Además, los objetos "marcados" GCed después el montón habitual es GCed, y por lo general solo si todavía hay necesidad de liberar más memoria (ya que recolectar objs marcados y barridos toma más tiempo).

ACTUALIZAR: ¿Es realmente una fuga, no solo una solución ineficaz de montón temprano? Hay varias cosas a considerar.

  • ¿Cuánto tiempo estarán bloqueados estos objetos después de que se complete la solicitud?
  • Sync XHR puede bloquear la pila durante un tiempo ilimitado, XHR no tiene propiedades de tiempo de espera (en todos los navegadores que no sean IE), los problemas de red no son raros.
  • ¿Cuántos elementos de UI están bloqueados? Si bloquea 20M de memoria por solo 1 seg == 200k de plomo en 2 minutos. Considera muchas pestañas de fondo.
  • Considere el caso cuando la sincronización simple bloquea el tono de los recursos y el navegador va a archivo de intercambio
  • Cuando otro evento intenta alterar DOM puede ser bloqueado por sincronización XHR, otro hilo está bloqueado (y todo su stack también)
  • Si el usuario repite las acciones que conducen a la sincronización XHR, el todo la ventana del navegador estará bloqueada. Los navegadores usan max = 2 hilos para manejar eventos de ventana.
  • Incluso sin bloquear esto consume muchos recursos internos de SO y navegador: hilo, recursos de sección crítica, recursos de IU, DOM ... Imagine que puede abrir (debido a problemas de memoria) 10 pestañas con sitios que usan XHR de sincronización y 100 pestañas con sitios que usan async XHR. ¿No es esta pérdida de memoria?

3
2017-08-22 07:00



Las pérdidas de memoria que utilizan solicitudes AJAX sincrónicas a menudo son causadas por:

  • usando setInterval / setTimout causando llamadas circulares.
  • XmlHttpRequest: cuando se elimina la referencia, xhr se vuelve inaccesible

La pérdida de memoria ocurre cuando el navegador, por algún motivo, no libera la memoria de los objetos que ya no se necesitan.

Esto puede ocurrir debido a errores del navegador, problemas de extensiones del navegador y, mucho más raramente, nuestros errores en la arquitectura del código.

Aquí hay un ejemplo de una pérdida de memoria que causa cuando se ejecuta setInterval en un nuevo contexto:

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");
  }
});

0
2017-08-16 16:34