UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

521 lines (409 loc) 12.5 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de 2006 Derrell Lipman License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Andreas Ecker (ecker) * Derrell Lipman (derrell) ************************************************************************ */ /** * Handles scheduling of requests to be sent to a server. * * This class is a singleton and is used by qx.io.remote.Request to schedule its * requests. It should not be used directly. * * NOTE: Instances of this class must be disposed of after use * * @internal */ qx.Class.define("qx.io.remote.RequestQueue", { type : "singleton", extend : qx.core.Object, implement : [ qx.core.IDisposable ], /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct : function() { this.base(arguments); this.__queue = []; this.__active = []; this.__totalRequests = 0; // timeout handling this.__timer = new qx.event.Timer(500); this.__timer.addListener("interval", this._oninterval, this); }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties : { /** * Indicates whether queue is enabled or not. */ enabled : { init : true, check : "Boolean", apply : "_applyEnabled" }, /** * The maximum number of total requests. */ maxTotalRequests : { check : "Integer", nullable : true }, /** * Maximum number of parallel requests. */ maxConcurrentRequests : { check : "Integer", init : qx.core.Environment.get("io.maxrequests") }, /** * Default timeout for remote requests in milliseconds. */ defaultTimeout : { check : "Integer", init : 5000 } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { __queue : null, __active : null, __totalRequests : null, __timer : null, /* --------------------------------------------------------------------------- QUEUE HANDLING --------------------------------------------------------------------------- */ /** * Get a list of queued requests * * @return {qx.io.remote.Request[]} The list of queued requests */ getRequestQueue : function() { return this.__queue; }, /** * Get a list of active queued requests, each one wrapped in an instance of * {@link qx.io.remote.Exchange} * * @return {qx.io.remote.Exchange[]} The list of active queued requests, each one * wrapped in an instance of {@link qx.io.remote.Exchange} */ getActiveQueue : function() { return this.__active; }, /** * Generates debug output */ _debug : function() { if (qx.core.Environment.get("qx.debug")) { if (qx.core.Environment.get("qx.debug.io.remote")) { // Debug output var vText = this.__active.length + "/" + (this.__queue.length + this.__active.length); this.debug("Progress: " + vText); window.status = "Request-Queue Progress: " + vText; } } }, /** * Checks the queue if any request is left to send and uses the transport * layer to send the open requests. * This method calls itself until every request in the queue is send. * */ _check : function() { // Debug output this._debug(); // Check queues and stop timer if not needed anymore if (this.__active.length == 0 && this.__queue.length == 0) { this.__timer.stop(); } // Checking if enabled if (!this.getEnabled()) { return; } // Checking active queue fill if ( this.__queue.length == 0 ||(this.__queue[0].isAsynchronous() && this.__active.length >= this.getMaxConcurrentRequests())) { return; } // Checking number of total requests if (this.getMaxTotalRequests() != null && this.__totalRequests >= this.getMaxTotalRequests()) { return; } var vRequest = this.__queue.shift(); var vTransport = new qx.io.remote.Exchange(vRequest); // Increment counter this.__totalRequests++; // Add to active queue this.__active.push(vTransport); // Debug output this._debug(); // Establish event connection between qx.io.remote.Exchange and me. vTransport.addListener("sending", this._onsending, this); vTransport.addListener("receiving", this._onreceiving, this); vTransport.addListener("completed", this._oncompleted, this); vTransport.addListener("aborted", this._oncompleted, this); vTransport.addListener("timeout", this._oncompleted, this); vTransport.addListener("failed", this._oncompleted, this); // Store send timestamp vTransport._start = (new Date).valueOf(); // Send vTransport.send(); // Retry if (this.__queue.length > 0) { this._check(); } }, /** * Removes a transport object from the active queue and disposes the * transport object in order stop the request. * * @param vTransport {qx.io.remote.Exchange} Transport object */ _remove : function(vTransport) { // Remove from active transports qx.lang.Array.remove(this.__active, vTransport); // Dispose transport object vTransport.dispose(); // Check again this._check(); }, /* --------------------------------------------------------------------------- EVENT HANDLING --------------------------------------------------------------------------- */ __activeCount : 0, /** * Listens for the "sending" event of the transport object and increases * the counter for active requests. * * @param e {qx.event.type.Event} event object */ _onsending : function(e) { if (qx.core.Environment.get("qx.debug")) { if (qx.core.Environment.get("qx.debug.io.remote")) { this.__activeCount++; e.getTarget()._counted = true; this.debug("ActiveCount: " + this.__activeCount); } } e.getTarget().getRequest()._onsending(e); }, /** * Listens for the "receiving" event of the transport object and delegate * the event to the current request object. * * @param e {qx.event.type.Event} event object */ _onreceiving : function(e) { e.getTarget().getRequest()._onreceiving(e); }, /** * Listens for the "completed" event of the transport object and decreases * the counter for active requests. * * @param e {qx.event.type.Event} event object */ _oncompleted : function(e) { if (qx.core.Environment.get("qx.debug")) { if (qx.core.Environment.get("qx.debug.io.remote")) { if (e.getTarget()._counted) { this.__activeCount--; this.debug("ActiveCount: " + this.__activeCount); } } } // delegate the event to the handler method of the request depending // on the current type of the event ( completed|aborted|timeout|failed ) var request = e.getTarget().getRequest(); var requestHandler = "_on" + e.getType(); // remove the request from the queue, // keep local reference, see [BUG #4422] this._remove(e.getTarget()); // It's possible that the request handler can fail, possibly due to // being sent garbage data. We want to prevent that from crashing // the program, but instead display an error. try { if (request[requestHandler]) { request[requestHandler](e); } } catch(ex) { this.error("Request " + request + " handler " + requestHandler + " threw an error: ", ex); // Issue an "aborted" event so the application gets notified. // If that too fails, or if there's no "aborted" handler, ignore it. try { if (request["_onaborted"]) { var event = qx.event.Registration.createEvent("aborted", qx.event.type.Event); request["_onaborted"](event); } } catch(ex1) { } } }, /* --------------------------------------------------------------------------- TIMEOUT HANDLING --------------------------------------------------------------------------- */ /** * Listens for the "interval" event of the transport object and checks * if the active requests are timed out. * * @param e {qx.event.type.Event} event object */ _oninterval : function(e) { var vActive = this.__active; if (vActive.length == 0) { this.__timer.stop(); return; } var vCurrent = (new Date).valueOf(); var vTransport; var vRequest; var vDefaultTimeout = this.getDefaultTimeout(); var vTimeout; var vTime; for (var i=vActive.length-1; i>=0; i--) { vTransport = vActive[i]; vRequest = vTransport.getRequest(); if (vRequest.isAsynchronous()) { vTimeout = vRequest.getTimeout(); // if timer is disabled... if (vTimeout == 0) { // then ignore it. continue; } if (vTimeout == null) { vTimeout = vDefaultTimeout; } vTime = vCurrent - vTransport._start; if (vTime > vTimeout) { this.warn("Timeout: transport " + vTransport.toHashCode()); this.warn(vTime + "ms > " + vTimeout + "ms"); vTransport.timeout(); } } } }, /* --------------------------------------------------------------------------- MODIFIERS --------------------------------------------------------------------------- */ // property apply _applyEnabled : function(value, old) { if (value) { this._check(); } this.__timer.setEnabled(value); }, /* --------------------------------------------------------------------------- CORE METHODS --------------------------------------------------------------------------- */ /** * Add the request to the pending requests queue. * * @param vRequest {var} The request */ add : function(vRequest) { vRequest.setState("queued"); if (vRequest.isAsynchronous()) { this.__queue.push(vRequest); } else { this.__queue.unshift(vRequest); } this._check(); if (this.getEnabled()) { this.__timer.start(); } }, /** * Remove the request from the pending requests queue. * * The underlying transport of the request is forced into the aborted * state ("aborted") and listeners of the "aborted" * signal are notified about the event. If the request isn't in the * pending requests queue, this method is a noop. * * @param vRequest {var} The request */ abort : function(vRequest) { var vTransport = vRequest.getTransport(); if (vTransport) { vTransport.abort(); } else if (this.__queue.includes(vRequest)) { qx.lang.Array.remove(this.__queue, vRequest); } } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct : function() { this._disposeArray("__active"); this._disposeObjects("__timer"); this.__queue = null; } });