@qooxdoo/framework
Version:
The JS Framework for Coders
521 lines (409 loc) • 12.5 kB
JavaScript
/* ************************************************************************
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;
}
});