Implemented request attachment: If a request with the same
user, path and input data is already being evaluated, the request is attached to that evaluation instead of starting a new one. This should fix the very common double- or multiple click problem, among others.
This commit is contained in:
parent
9fa2d5154f
commit
69d9b49ce3
4 changed files with 94 additions and 14 deletions
|
@ -17,8 +17,11 @@ public class RequestTrans implements Serializable {
|
|||
public String path;
|
||||
public String session;
|
||||
private Hashtable values;
|
||||
|
||||
// this is used to hold the EcmaScript form data object
|
||||
public transient Object data;
|
||||
// when was execution started on this request?
|
||||
public transient long startTime;
|
||||
|
||||
public RequestTrans () {
|
||||
super ();
|
||||
|
@ -49,4 +52,24 @@ public class RequestTrans implements Serializable {
|
|||
return values;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
return session == null ? super.hashCode () : session.hashCode ();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A request is considered equal to another one if it has the same user, path,
|
||||
* and request data. This is used to evaluate multiple simultanous requests only once
|
||||
*/
|
||||
public boolean equals (Object what) {
|
||||
try {
|
||||
RequestTrans other = (RequestTrans) what;
|
||||
return (session.equals (other.session) &&
|
||||
path.equalsIgnoreCase (other.path) &&
|
||||
values.equals (other.getReqData ()));
|
||||
} catch (Exception x) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -142,12 +142,28 @@ public class ResponseTrans implements Serializable {
|
|||
|
||||
|
||||
/**
|
||||
* This has to be called after writin to this response has finished and before it is shipped back to the
|
||||
* This has to be called after writing to this response has finished and before it is shipped back to the
|
||||
* web server. Transforms the string buffer into a char array to minimize size.
|
||||
*/
|
||||
public void close () {
|
||||
if (buffer != null)
|
||||
public synchronized void close () {
|
||||
if (buffer != null) {
|
||||
response = new String (buffer).toCharArray();
|
||||
buffer = null; // make sure this is done only once, even with more requsts attached
|
||||
} else {
|
||||
response = new char[0];
|
||||
}
|
||||
notifyAll ();
|
||||
}
|
||||
|
||||
/**
|
||||
* If we just attached to evaluation we call this instead of close because only the primary thread
|
||||
* is responsible for closing the result
|
||||
*/
|
||||
public synchronized void waitForClose () {
|
||||
try {
|
||||
if (response == null)
|
||||
wait (10000l);
|
||||
} catch (InterruptedException ix) {}
|
||||
}
|
||||
|
||||
public String getContentString () {
|
||||
|
|
|
@ -53,6 +53,9 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, Runn
|
|||
Thread worker;
|
||||
long requestTimeout = 60000; // 60 seconds for request timeout.
|
||||
ThreadGroup threadgroup;
|
||||
|
||||
// Map of requesttrans -> active requestevaluators
|
||||
Hashtable activeRequests;
|
||||
|
||||
protected String templateExtension, scriptExtension, actionExtension, skinExtension;
|
||||
|
||||
|
@ -124,6 +127,7 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, Runn
|
|||
freeThreads.push (ev);
|
||||
allThreads.addElement (ev);
|
||||
}
|
||||
activeRequests = new Hashtable ();
|
||||
|
||||
typemgr = new TypeManager (this);
|
||||
typemgr.check ();
|
||||
|
@ -229,9 +233,23 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, Runn
|
|||
|
||||
ResponseTrans res = null;
|
||||
RequestEvaluator ev = null;
|
||||
// are we responsible for releasing the evaluator and closing the result?
|
||||
boolean primaryRequest = false;
|
||||
try {
|
||||
ev = getEvaluator ();
|
||||
res = ev.invoke (req, u);
|
||||
// first look if a request with same user/path/data is already being executed.
|
||||
// if so, attach the request to its output instead of starting a new evaluation
|
||||
// this helps to cleanly solve "doubleclick" kind of users
|
||||
ev = (RequestEvaluator) activeRequests.get (req);
|
||||
if (ev != null) {
|
||||
res = ev.attachRequest (req);
|
||||
}
|
||||
if (res == null) {
|
||||
primaryRequest = true;
|
||||
// if attachRequest returns null this means we came too late
|
||||
// and the other request was finished in the meantime
|
||||
ev = getEvaluator ();
|
||||
res = ev.invoke (req, u);
|
||||
}
|
||||
} catch (ApplicationStoppedException stopped) {
|
||||
// let the servlet know that this application has gone to heaven
|
||||
throw stopped;
|
||||
|
@ -240,10 +258,14 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, Runn
|
|||
res = new ResponseTrans ();
|
||||
res.write ("Error in application: <b>" + x.getMessage () + "</b>");
|
||||
} finally {
|
||||
releaseEvaluator (ev);
|
||||
if (primaryRequest) {
|
||||
releaseEvaluator (ev);
|
||||
res.close (); // this needs to be done before sending it back
|
||||
} else {
|
||||
res.waitForClose ();
|
||||
}
|
||||
}
|
||||
|
||||
res.close (); // this needs to be done before sending it back
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -533,18 +533,37 @@ public class RequestEvaluator implements Runnable {
|
|||
this.user = user;
|
||||
this.res = new ResponseTrans ();
|
||||
|
||||
checkThread ();
|
||||
wait (app.requestTimeout);
|
||||
if (reqtype != NONE) {
|
||||
IServer.getLogger().log ("Stopping Thread for Request "+app.getName()+"/"+req.path);
|
||||
stopThread ();
|
||||
res.reset ();
|
||||
res.write ("<b>Error in application '"+app.getName()+"':</b> <br><br><pre>Request timed out.</pre>");
|
||||
try {
|
||||
app.activeRequests.put (req, this);
|
||||
|
||||
checkThread ();
|
||||
wait (app.requestTimeout);
|
||||
if (reqtype != NONE) {
|
||||
IServer.getLogger().log ("Stopping Thread for Request "+app.getName()+"/"+req.path);
|
||||
stopThread ();
|
||||
res.reset ();
|
||||
res.write ("<b>Error in application '"+app.getName()+"':</b> <br><br><pre>Request timed out.</pre>");
|
||||
}
|
||||
} finally {
|
||||
app.activeRequests.remove (req);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks if the Evaluator is already executing an equal request. If so, attach to it and
|
||||
* wait for it to complete. Otherwise return null, so the application knows it has to run the request.
|
||||
*/
|
||||
public synchronized ResponseTrans attachRequest (RequestTrans req) throws InterruptedException {
|
||||
if (this.req == null || !this.req.equals (req) || reqtype == NONE)
|
||||
return null;
|
||||
// we already know our response object
|
||||
ResponseTrans r = res;
|
||||
wait (app.requestTimeout);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
public synchronized Object invokeXmlRpc (String method, Vector args) throws Exception {
|
||||
this.reqtype = XMLRPC;
|
||||
|
|
Loading…
Add table
Reference in a new issue