¿Cancelar solicitudes pendientes de AJAX en la aplicación PHP?

Tengo problemas para cancelar mis solicitudes de XHR cuando navego entre páginas. Tengo una página que tiene 8 solicitudes que son despedidas. Los cancelo al hacer clic en un enlace fuera de la página actual. La página se detiene mientras espera en el siguiente documento para cargar. Las solicitudes de XHR aparecen como canceladas en las herramientas de desarrollador, pero el nuevo documento se detiene como si estuviera esperando que regresen.

Aquí puede ver que la página está detenida aunque todas las demás solicitudes se cancelaron. La nueva página es la única solicitud pendiente …

enter image description here

Y aquí se puede ver que una vez que la página finalmente hizo el salto, el TTFB es 52.52s. Si espero que las llamadas vuelvan antes de hacer clic, el salto es instantáneo.

enter image description here

Aquí están los encabezados de las nuevas páginas una vez que finalmente se carga si eso ayuda … enter image description here

Utilizo el siguiente código de frankenstein para gestionar solicitudes de XHR. Tengo una función cancelAll hacia la parte inferior que cancela las solicitudes …

XHRManager = { Requests: [], pendingRequests: [], addNextRequest: function (r) { var timeout = 0; if (trace.isDevelopment()) { timeout = 350; } setTimeout(function () { if (r.url == XHRManager.pendingRequests[0].url && r.start == XHRManager.pendingRequests[0].start) { XHRManager.pendingRequests.splice(0, 1); } else { $( XHRManager.pendingRequests).each(function (ii, dd) { if (dd.url == r.url && dd.start == r.start) { XHRManager.pendingRequests.splice(ii, 1); } }); } XHRManager.startNextRequest(); if (trace.findLocalStorage()) { XHRManager.showTrace = true; trace.show(); } }, timeout); }, requests: [], intervals: [], requestsInt: 0, firstRun: true, delay: 500, globalTimeout: 5000, showTrace: false, startNextRequest: function () { $( XHRManager.pendingRequests).each(function (i, d) { if (d.start) { } if (i == 0) { if (trace.domWatcher.constructor == Function) { trace.domWatcher(d.requestNumber); } trace.log("Request #" + d.requestNumber + " started"); d.requestType(d); } }); if ( XHRManager.pendingRequests.length == 0) { if (trace.isDevelopment()) { trace.show(); } } }, AddToPendingRequests: function (url, params, cb, type, errCB) { var rI = XHRManager.requestsInt; XHRManager.requestsInt++; var req = {url: url, params: params, cb: cb, requestNumber: rI, requestType: type}; if (errCB) { req.errCB = errCB; } XHRManager.pendingRequests.push(req); // if(trace.findLocalStorage()){ // trace.show(); // } if (rI == 0 || XHRManager.pendingRequests.length == 1) { XHRManager.startNextRequest(); } }, writeVals: function (url, params, data, start, cb, requestNumber) { if ($("meta[content='development']").length > 0) { try { var response = {}; response.requestNumber = requestNumber; if (data.sql != "" && data.sql != undefined) { response.sql = data.sql; } if (data.debug) { if (data.debug.sql != "" && data.debug.sql != undefined) { response.sql = data.debug.sql; } } if (data.data != "" && data.data != undefined) { response.data = data.data; } else { if (data != "" || data != undefined) { response.data = data; } } if (url != "" && url != undefined) { response.url = url; } if (params != "" && params != undefined) { response.params = params; } if (cb) { response.cb = cb.toString(); } else { response.cb = ""; } response.requestStats = {}; response.requestStats.start = start; response.requestStats.end = Date(); response.requestStats.totalTime = ((new Date(response.requestStats.end)).getTime() - (new Date(start)).getTime()) / 1000 + " sec(s)"; XHRManager.Requests.push(response); } catch (e) { trace.log(e); } } }, _create: function (r) { var xm = XHRManager; var start = Date(); var req = $.get(r.url, r.params, r.cb) .done(function (data) { XHRManager.writeVals(r.url, r.params, data, start, r.cb, r.requestNumber); if (trace.isDevelopment() && trace.isOn()) { XHRManager.addNextRequest(r); } }); xm.requests.push(req); }, _createAjax: function (r) { var xm = XHRManager; var start = Date(); if (r.type == "PUT" || r.type == "DELETE") { var req = $.ajax({ type: r.type, xhrFields: { withCredentials: true }, url: r.url, data: r.params, success: function (data) { XHRManager.writeVals(r.url, r.params, r.data, r.start, r.cb, r.requestNumber); r.cb(data); if (trace.isDevelopment() && trace.isOn()) { XHRManager.addNextRequest(r); } }, error: r.errCB }); xm.requests.push(req); } else { var req = $.ajax({ type: r.type, xhrFields: { withCredentials: true }, dataType: 'json', json: 'json', url: r.url, data: r.params, success: function (data) { XHRManager.writeVals(r.url, r.params, data, start, r.cb, r.requestNumber); r.cb(data); if (trace.isDevelopment() && trace.isOn()) { XHRManager.addNextRequest(r); } }, error: r.errCB }); xm.requests.push(req); } }, _createJSON: function (r) { var start = Date(); var xm = XHRManager; var req = $.getJSON(r.url, r.params, r.cb) .done(function (data) { XHRManager.writeVals(r.url, r.params, data, start, r.cb, r.requestNumber); if (trace.isDevelopment() && trace.isOn()) { XHRManager.addNextRequest(r); } }); xm.requests.push(req); }, create: function (url, params, cb) { if (trace.isDevelopment() && trace.isOn()) { XHRManager.AddToPendingRequests(url, params, cb, XHRManager._create); } else { var r = {}; r.url = url; r.params = params; r.cb = cb; XHRManager._create(r); } }, createAjax: function (url, params, type, cb, errCB) { if (trace.isDevelopment() && trace.isOn()) { XHRManager.AddToPendingRequests(url, params, cb, XHRManager._createAjax, errCB); } else { var r = {}; r.url = url; r.params = params; r.cb = cb; r.type = type; r.errCB = errCB; XHRManager._createAjax(r); } }, createJSON: function (url, params, cb) { if (trace.isDevelopment() && trace.isOn()) { XHRManager.AddToPendingRequests(url, params, cb, XHRManager._createJSON); } else { var r = {}; r.url = url; r.params = params; r.cb = cb; XHRManager._createJSON(r); } }, remove: function (xhr) { var xm = XHRManager; var index = xm.requests.indexOf(xhr); if (index > -1) { xm.requests.splice(index, 1); } index = xm.intervals.indexOf(xhr.interval); if (index > -1) { xm.intervals.splice(index, 1); } }, cancelAll: function () { var xm = XHRManager; $(xm.requests).each(function () { var t = this; t.abort(); }); $(xm.intervals).each(function () { var t = this; clearInterval(t); }); xm.requests = []; xm.intervals = []; } }; 

El sitio usa jQuery, PHP, Zend Framework 2 y SQL, Apache. ¿Qué me estoy perdiendo?

Cadena causal probable

  1. el servidor no se da cuenta de que las solicitudes de XHR están canceladas, por lo que los procesos PHP correspondientes siguen ejecutándose
  2. estos procesos PHP usan sesiones e impiden el acceso simultáneo a esta sesión hasta que terminan

Soluciones posibles

Al abordar cualquiera de los dos puntos anteriores se rompe la cadena y se puede solucionar el problema:

  1. (a) ignore_user_abort es FALSE de forma predeterminada, pero podría estar utilizando una configuración no estándar. Cambie esta configuración a FALSE en su php.ini o ignore_user_abort(false) en las secuencias de comandos que manejan estas solicitudes interrumpibles.

Drawback: el script simplemente termina. Cualquier trabajo en progreso se descarta, posiblemente dejando el sistema en un estado sucio.

  1. (b) Por defecto, PHP no detectará que el usuario haya abortado la conexión hasta que se intente enviar información al cliente. Haga echo algo periódicamente durante el transcurso de su script de larga duración.

Drawback: estos datos ficticios pueden dañar el resultado normal de su script. Y aquí también, la secuencia de comandos puede dejar el sistema en un estado sucio.

  1. Una sesión de PHP se almacena como un archivo en el servidor. En session_start() , el script abre el archivo de sesión en modo de escritura, efectivamente adquiriendo un locking exclusivo en él. Las solicitudes posteriores que usan la misma sesión se ponen en espera hasta que se libera el locking. Esto sucede cuando el script finaliza, a menos que cierre la sesión de forma explícita. Llame a session_write_close() o session_abort() tan pronto como sea posible.

Drawback: cuando se cierra, la sesión ya no se puede escribir (a menos que se vuelva a abrir la sesión , pero este es un hack algo poco elegante). Además, el script se sigue ejecutando, posiblemente desperdiciando recursos.

Definitivamente recomiendo la última opción.

¿Está almacenando su solicitud de Ajax en una variable? Si no, eso es lo que debe hacer para cancelar por completo una solicitud

 var xhr = $.ajax({ type: "POST", url: "anyScript.php", data: "data1=0&data2=1", success: function(msg){ //Success Function } }); //here you abort the request xhr.abort() 

Supongo que lo ha hecho, pero verifique todos los archivos de registro (php y apache).

También prueba esto:

php.ini

 upload_max_filesize = 256M post_max_size = 256M 

.htaccess

 php_value upload_max_filesize 256M php_value post_max_size 256M 

Otra cosa que me molesta es esta parte.

 $(xm.requests).each(function () { var t = this; t.abort(); }); $(xm.intervals).each(function () { var t = this; clearInterval(t); }); 

Intente pasar argumentos a la callback y abortarlos. He visto casos, donde la asignación de this a una variable dentro de $ .each loop realmente apunta a un objeto diferente o a la ventana global.

 $(xm.requests).each(function (index, value) { value.abort(); }); $(xm.intervals).each(function (index, value) { clearInterval(value); });