Play Framework async controller blocks subsequent calls for the same controller

Viewed 48

My goal is to do some database queries from the async controller, then return the answer.

I'm playing with the example project, for now just simulating the DB queries with a sleep, but what I noticed is that whatever I do, the REST interface won't even start the sleep of the second query until the first one finishes. E.g.: If I call the REST interface from one tab in the browser, then 1 second later again from an another tab, I'd expect that the second one gets the reply too in 10 seconds, but actually it's 19.

Also it doesn't seem to use the "database-io" pool either:

1: application-akka.actor.default-dispatcher-2

2: application-akka.actor.default-dispatcher-5

My code:

@Singleton
class AsyncController @Inject()(cc: ControllerComponents, actorSystem: ActorSystem) extends AbstractController(cc) {

  implicit val executionContext = actorSystem.dispatchers.lookup("database-io")

  def message = Action.async {
    getFutureMessage().map { msg => Ok(msg) }
  }

  private def getFutureMessage(): Future[String] = {
    val defaultThreadPool = Thread.currentThread().getName;
    println(s"""1: $defaultThreadPool""")

    val promise: Promise[String] = Promise[String]()
    actorSystem.scheduler.scheduleOnce(0 second) {
      val blockingPool = Thread.currentThread().getName;
      println(s"""2: $blockingPool""")

      Thread.sleep(10000)
      promise.success("Hi!")
    }(actorSystem.dispatcher)
    promise.future
  }

}
2 Answers

It could be two reasons for this behavior:

  1. You use the development mode (1 thread), or your product configuration is configured only for one thread.
  2. The browser blocks the second request until receiving the response from the first. This phrase: "If I call the REST interface from one tab in the browser." Try to do the same from different browsers.

You need to avoid blocking code. Basically:

  1. You can a method that returns the Future.

  2. You map into it.

  3. You recover any failure the Future result might bring.

Lets say I have:

def userAge (userId: String): Future[Int] = ???

Then you map into it:

userAge.map{
  age => ??? //everything is ok
}.recover{ case e: Throwable => ??? //Do something when it fails 

Note that if you have more than one call the other map becomes flatMap because you want Future[...] something and not Future[Future[...]].

Related