Permalink
Browse files

Merge pull request #1309 from lift/asc_issue_956

AJAX request deduplication.
  • Loading branch information...
2 parents 1980baa + 2d0709a commit aae6082bcea0420b9d2cbc87a7cc85e4b74a6c30 David Pollak committed Aug 27, 2012
@@ -400,6 +400,12 @@ trait LiftCometActor extends TypedActor[Any, Any] with ForwardableActor[Any, Any
}
/**
+ * Asynchronous message send. Send-and-receive eventually. Returns a Future for the reply message.
+ */
+ def !<(msg: Any): LAFuture[Any]
+
+
+ /**
* Override in sub-class to customise timeout for the render()-method for the specific comet
*/
def cometRenderTimeout = LiftRules.cometRenderTimeout

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -500,6 +500,16 @@ private final case class PostPageFunctions(renderVersion: String,
}
/**
+ * existingResponse is Empty if we have no response for this request
+ * yet. pendingActors is a list of actors who want to be notified when
+ * this response is received.
+ */
+private[http] final case class AjaxRequestInfo(requestVersion:Int,
+ existingResponse:Box[Box[LiftResponse]],
+ pendingActors:List[LiftActor],
+ lastSeen: Long)
+
+/**
* The LiftSession class containg the session state information
*/
class LiftSession(private[http] val _contextPath: String, val uniqueId: String,
@@ -558,6 +568,16 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String,
private var postPageFunctions: Map[String, PostPageFunctions] = Map()
/**
+ * A list of AJAX requests that may or may not be pending for this
+ * session. There is up to one entry per RenderVersion.
+ */
+ private val ajaxRequests = scala.collection.mutable.Map[String,AjaxRequestInfo]()
+
+ private[http] def withAjaxRequests[T](fn: (scala.collection.mutable.Map[String, AjaxRequestInfo]) => T): T = {
+ ajaxRequests.synchronized { fn(ajaxRequests) }
+ }
+
+ /**
* The synchronization lock for the postPageFunctions
*/
private val postPageLock = new Object
@@ -675,7 +695,6 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String,
* Executes the user's functions based on the query parameters
*/
def runParams(state: Req): List[Any] = {
-
val toRun = {
// get all the commands, sorted by owner,
(state.uploadedFiles.map(_.name) ::: state.paramNames).distinct.
@@ -708,13 +727,28 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String,
val f = toRun.filter(_.owner == w)
w match {
// if it's going to a CometActor, batch up the commands
- case Full(id) if asyncById.contains(id) => asyncById.get(id).toList.flatMap(a =>
- a.!?(a.cometProcessingTimeout, ActionMessageSet(f.map(i => buildFunc(i)), state)) match {
+ case Full(id) if asyncById.contains(id) => asyncById.get(id).toList.flatMap(a => {
+ val future =
+ a.!<(ActionMessageSet(f.map(i => buildFunc(i)), state))
+
+ def processResult(result: Any): List[Any] = result match {
case Full(li: List[_]) => li
case li: List[_] => li
- case Empty => Full(a.cometProcessingTimeoutHandler())
+ // We return the future so it can, from AJAX requests, be
+ // satisfied and update the pending ajax request map.
+ case Empty =>
+ val processingFuture = new LAFuture[Any]
+ // Wait for and process the future on a separate thread.
+ Schedule.schedule(() => {
+ processingFuture.satisfy(processResult(future.get))
+ }, 0 seconds)
+ List((a.cometProcessingTimeoutHandler, processingFuture))
case other => Nil
- })
+ }
+
+ processResult(future.get(a.cometProcessingTimeout))
+ })
+
case _ => f.map(i => buildFunc(i).apply())
}
}
@@ -836,6 +870,15 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String,
}
}
+ withAjaxRequests { currentAjaxRequests =>
+ for {
+ (version, requestInfo) <- currentAjaxRequests
+ if (now - requestInfo.lastSeen) > LiftRules.unusedFunctionsLifeTime
+ } {
+ currentAjaxRequests -= version
+ }
+ }
+
synchronized {
messageCallback.foreach {
case (k, f) =>
@@ -963,6 +1006,13 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String,
} postPageFunctions += (ownerName -> funcInfo.updateLastSeen)
}
+ withAjaxRequests { currentAjaxRequests =>
+ currentAjaxRequests.get(ownerName).foreach {
+ case info: AjaxRequestInfo =>
+ currentAjaxRequests += (ownerName -> info.copy(lastSeen = time))
+ }
+ }
+
synchronized {
(0 /: messageCallback)((l, v) => l + (v._2.owner match {
case Full(owner) if (owner == ownerName) =>
@@ -112,24 +112,24 @@ trait JSArtifacts {
}
/**
- * The companion module for AjaxInfo that provides
+ * The companion module for AjaxInfodd that provides
* different construction schemes
*/
object AjaxInfo {
def apply(data: JsExp, post: Boolean) =
- new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, "script", Empty, Empty)
+ new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, "script", Empty, Empty, true)
def apply(data: JsExp,
dataType: String,
post: Boolean) =
- new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, dataType, Empty, Empty)
+ new AjaxInfo(data, if (post) "POST" else "GET", 1000, false, dataType, Empty, Empty, true)
def apply(data: JsExp) =
- new AjaxInfo(data, "POST", 1000, false, "script", Empty, Empty)
+ new AjaxInfo(data, "POST", 1000, false, "script", Empty, Empty, true)
def apply(data: JsExp,
dataType: String) =
- new AjaxInfo(data, "POST", 1000, false, dataType, Empty, Empty)
+ new AjaxInfo(data, "POST", 1000, false, dataType, Empty, Empty, true)
def apply(data: JsExp,
post: Boolean,
@@ -142,13 +142,30 @@ object AjaxInfo {
false,
"script",
Full(successFunc),
- Full(failFunc))
+ Full(failFunc),
+ true)
+
+ def apply(data: JsExp,
+ post: Boolean,
+ timeout: Long,
+ successFunc: String,
+ failFunc: String,
+ includeVersion: Boolean) =
+ new AjaxInfo(data,
+ if (post) "POST" else "GET",
+ timeout,
+ false,
+ "script",
+ Full(successFunc),
+ Full(failFunc),
+ includeVersion)
}
/**
* Represents the meta data of an Ajax request.
*/
case class AjaxInfo(data: JsExp, action: String, timeout: Long,
cache: Boolean, dataType: String,
- successFunc: Box[String], failFunc: Box[String])
+ successFunc: Box[String], failFunc: Box[String],
+ includeVersion: Boolean)
@@ -107,8 +107,7 @@ object ScriptRenderer {
"POST",
LiftRules.ajaxPostTimeout,
false, "script",
- Full("liftAjax.lift_successRegisterGC"), Full("liftAjax.lift_failRegisterGC"))) +
- """
+ Full("liftAjax.lift_successRegisterGC"), Full("liftAjax.lift_failRegisterGC"), false)) + """
},
@@ -132,6 +131,7 @@ object ScriptRenderer {
aboutToSend.onSuccess(data);
}
liftAjax.lift_doCycleQueueCnt++;
+ liftAjax.lift_ajaxVersion++;
liftAjax.lift_doAjaxCycle();
};
@@ -145,6 +145,7 @@ object ScriptRenderer {
queue.push(aboutToSend);
liftAjax.lift_ajaxQueueSort();
} else {
+ liftAjax.lift_ajaxVersion++;
if (aboutToSend.onFailure) {
aboutToSend.onFailure();
} else {
@@ -180,6 +181,18 @@ object ScriptRenderer {
setTimeout("liftAjax.lift_doAjaxCycle();", 200);
},
+ lift_ajaxVersion: 0,
+
+ addPageNameAndVersion: function(url) {
+ return """ + {
+ if (LiftRules.enableLiftGC) {
+ "url.replace('" + LiftRules.ajaxPath + "', '" + LiftRules.ajaxPath + "/'+lift_page+('-'+liftAjax.lift_ajaxVersion%36).toString(36));"
+ } else {
+ "url;"
+ }
+ } + """
+ },
+
addPageName: function(url) {
return """ + {
if (LiftRules.enableLiftGC) {
@@ -196,7 +209,7 @@ object ScriptRenderer {
"POST",
LiftRules.ajaxPostTimeout,
false, "script",
- Full("onSuccess"), Full("onFailure"))) +
+ Full("onSuccess"), Full("onFailure"), true)) +
"""
},
@@ -206,7 +219,7 @@ object ScriptRenderer {
"POST",
LiftRules.ajaxPostTimeout,
false, "json",
- Full("onSuccess"), Full("onFailure"))) +
+ Full("onSuccess"), Full("onFailure"), true)) +
"""
}
};
@@ -279,7 +292,7 @@ object ScriptRenderer {
false,
"script",
Full("liftComet.lift_handlerSuccessFunc"),
- Full("liftComet.lift_handlerFailureFunc"))) +
+ Full("liftComet.lift_handlerFailureFunc"), false)) +
"""
}
}
@@ -148,7 +148,7 @@ object ExtCoreArtifacts extends JSArtifacts {
}
private def toJson(info: AjaxInfo, server: String, path: String => JsExp): String =
- (("url : liftAjax.addPageName(" + path(server).toJsCmd + ")" ) ::
+ (("url : liftAjax.addPageNameAndVersion(" + path(server).toJsCmd + ")" ) ::
"params : " + info.data.toJsCmd ::
("method : " + info.action.encJs) ::
("dataType : " + info.dataType.encJs) ::
@@ -95,9 +95,15 @@ trait JQueryArtifacts extends JSArtifacts {
* attributes described by data parameter
*/
def ajax(data: AjaxInfo): String = {
+ val versionIncluder =
+ if (data.includeVersion)
+ "liftAjax.addPageNameAndVersion"
+ else
+ "liftAjax.addPageName"
+
"jQuery.ajax(" + toJson(data, S.contextPath,
prefix =>
- JsRaw("liftAjax.addPageName(" + S.encodeURL(prefix + "/" + LiftRules.ajaxPath + "/").encJs + ")")) + ");"
+ JsRaw(versionIncluder + "(" + S.encodeURL(prefix + "/" + LiftRules.ajaxPath + "/").encJs + ")")) + ");"
}
/**
@@ -126,9 +126,15 @@ object YUIArtifacts extends JSArtifacts {
* attributes described by data parameter
*/
def ajax(data: AjaxInfo): String = {
+ val versionIncluder =
+ if (data.includeVersion)
+ "liftAjax.addPageNameAndVersion"
+ else
+ "liftAjax.addPageName"
+
val url = S.encodeURL(S.contextPath + "/" + LiftRules.ajaxPath + "/")
- "url = YAHOO.lift.buildURI(liftAjax.addPageName(" + url.encJs + ") , " + data.data.toJsCmd + ");" +
+ "url = YAHOO.lift.buildURI(" + versionIncluder + "(" + url.encJs + ") , " + data.data.toJsCmd + ");" +
"YAHOO.util.Connect.asyncRequest(" + data.action.encJs + ", url, " + toJson(data) + ");"
}

0 comments on commit aae6082

Please sign in to comment.