Hello Lift people,
I've been using Lift in a project over the last six months and really enjoyed working with it! Thanks for the great work you've done on it.
Today I've run into a problem that I still couldn't figure out a solution for. A quick glance at the situation:
I'm using Lift as a backend for a Cordova HTML5 app and a JSON based REST API to bring the two together. Among others, the app user can retrieve a list of messages and a list of appointments, which are represented by rather simple MongoRecord classes.
My RestDispatcher directs the requests through these methods:
object RestDispatcher extends RestHelper with Logger {
serve {
case req if req.requestType.options_? => OkResponse()
// login functionality
case ("api" :: "login" :: Nil) JsonPost (data -> _) =>
/* ... */
case ("api" :: _) _ _ if !User.logged_in? => UnauthorizedResponse("")
case ("api" :: path) JsonGet _ =>
trace(s"GET request to $path")
get(path)
/* ... */
}
def get(path: List[String]) = path match {
/* ... */
case ("messages" :: Nil) =>
trace("messages requested")
JArray(Message.forCurrentUser.map(_.export))
case ("appointments" :: Nil) =>
trace("appointments requested")
JArray(Appointment.forCurrentUser.map(_.export))
/* ... */
}
}
This structure worked very well over the last six months. Now I've recently added the appointments route, and at app startup, requests to both routes are made to fetch some initial data. This is where the problem occurs: On every second or third server restart, the first set of requests from the app causes a complete server freeze. On the others it works just fine. This happens both on my development system and the production deployment.
One really odd thing striked me during the debugging: When I replace the lines in the RestDispatcher such that they simply return an empty JArray, everything works fine. But if I replace the accessor methods on the model objects such that they directly return empty lists (on the Message and Appointment objects: def forCurrentUser: List[Message] = { trace("forCurrentUser called") ; Nil }), the freeze occurs once again. The log messages from the RestDispatcher (messages/appointments requested) both show up in the log, the messages from the respective model objects (forCurrentUser called) do not. It breaks right before "leaving" the RestDispatcher in both cases.
At first, I thought that this issue can be solved by increasing the maxConcurrentRequests rule (although it is already set to 2 by default), but this didn't change anything. Also, there have always been other situations where multiple requests are made simultaneously, and it didn't ever cause any problems so far. I've tried it with both Lift 2.6.2 and 3.0-RC1, still no difference. I'm running the code through the sbt task jetty:start from earldouglas' xsbt-web-plugin.
When I look at the process through jconsole, I can see that after the freeze happened, it keeps on spawning worker threads (although no further requests are made). All these threads are in WAITING state, suspending on some AbstractQueuedSynchronizer$ConditionObject. I've tried to figure out where Lift uses these but couldn't quite find the right pieces of code. The login test you see in the RestDispatcher (User.logged_in?) is based on a SessionVar, and the first thing I thought about was a concurrency problem / deadlock situation. However, as far as I see it, all SessionVar.get calls are synchronized by default, aren't they?
Can you maybe give me some hints on what else I might try to figure out what's going wrong here? Thanks a lot!
Stefan