Play Framework async controller blocks subsequent calls for the same controller

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
  }

}

Just curious, are you running in production mode?
https://www.playframework.com/documentation/2.6.x/Deploying#Running-a-production-server-in-place

You explicitly say that the scheduleOnce block should run on the ActorSystem’s dispatcher, so that part is as expected.

You should use the execution context named “database-io”, you are actually passing the ActorSystem’s dispatcher, the same that is used to render, thus blocking the route

implicit val dbCtx: ExecutionContext = actorSystem.dispatchers.lookup("database-io")


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!")
    }(dbCtx)
    promise.future
  }

Note that if its the only ExecutionContext in this scope you dont need to pass it to the Promise, if its not the only one then its better to not have it as implicit and use it directly as demonstrated

Try using curl instead of the browser. I also run into this exact issue some time ago when testing from Chrome, and it turned out that the browser itself does not send any subsequent requests to the same endpoint until it receives at least one successful response. In other words, you code might not even receive the second request until it responds to the first one, because the browser doesn’t send it.

Here I found my old question on StackOverflow, might be useful: https://stackoverflow.com/questions/46467447/controller-method-called-synchronously

Thanks all for the replies, I think these must be the issues, I’ll try them when I have some time this weekend. :slight_smile:

Edit: Yep, that was it, both issues solved, thanks again. I had to pass the execution context explicitly and use curl instead of the browser…