Thorsten Maus
Thorsten Maus

Reputation: 37

Playframework non-blocking Action

Came across a problem I did not find an answer yet.

Running on playframework 2 with Scala.

Was required to write an Action method that performs multiple Future calls. My question: 1) Is the attached code non-blocking and hence looking the way it should be ? 2) Is there a guarantee that both DAO results are caught at any given time ?

def index = Action.async {

  val t2:Future[Tuple2[List[PlayerCol],List[CreatureCol]]] = for {
    p <- PlayerDAO.findAll()
    c <- CreatureDAO.findAlive()
  }yield(p,c)

  t2.map(t => Ok(views.html.index(t._1, t._2)))
}

Thanks for your feedback.

Upvotes: 0

Views: 195

Answers (1)

Michael Zajac
Michael Zajac

Reputation: 55569

Is the attached code non-blocking and hence looking the way it should be ?

That depends on a few things. First, I'm going to assume that PlayerDAO.findAll() and CreatureDAO.findAlive() return Future[List[PlayerCol]] and Future[List[CreatureCol]] respectively. What matters most is what these functions are actually calling themselves. Are they making JDBC calls, or using an asynchronous DB driver?

If the answer is JDBC (or some other synchronous db driver), then you're still blocking, and there's no way to make it fully "non-blocking". Why? Because JDBC calls block their current thread, and wrapping them in a Future won't fix that. In this situation, the most you can do is have them block a different ExecutionContext than the one Play is using to handle requests. This is generally a good idea, because if you have several db requests running concurrently, they can block Play's internal thread pool used for handling HTTP requests, and suddenly your server will have to wait to handle other requests (even if they don't require database calls).

For more on different ExecutionContexts see the thread pools documentation and this answer.

If you're answer is an asynchronous database driver like reactive mongo (there's also scalike-jdbc, and maybe some others), then you're in good shape, and I probably made you read a little more than you had to. In that scenario your index controller function would be fully non-blocking.

Is there a guarantee that both DAO results are caught at any given time ?

I'm not quite sure what you mean by this. In your current code, you're actually making these calls in sequence. CreatureDAO.findAlive() isn't executed until PlayerDAO.findAll() has returned. Since they are not dependent on each other, it seems like this isn't intentional. To make them run in parallel, you should instantiate the Futures before mapping them in a for-comprehension:

def index = Action.async {
    val players: Future[List[PlayerCol]] = PlayerDAO.findAll()
    val creatures: Future[List[CreatureCol]] = CreatureDAO.findAlive()

    val t2: Future[(List[PlayerCol], List[CreatureCol])] = for {
        p <- players
        c <- creatures
    } yield (p, c)

    t2.map(t => Ok(views.html.index(t._1, t._2)))
}

The only thing you can guarantee about having both results being completed is that yield isn't executed until the Futures have completed (or never, if they failed), and likewise the body of t2.map(...) isn't executed until t2 has been completed.


Further reading:

Are there any benefits in using non-async actions in Play Framework 2.2?

Understanding the Difference Between Non-Blocking Web Service Calls vs Non-Blocking JDBC

Upvotes: 6

Related Questions