Tarun Kumar
Tarun Kumar

Reputation: 518

How async Action api works in play framework 2.2.x for scala?

I was trying to create async api. But the response shows sequential execution. Steps done: Open the url in two tabs of chrome. And hit them one after other quickly. url ex:- localhost:9000/getStar.

But the execution log is like :-

    [info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

[success] Compiled in 107ms
[info] application - Application has started
[info] play - Application started (Dev)
[info] application - Async started ************************** :tarun
[info] application - Success Async call  :1
[info] application - Success Async call  :2
[info] application - Success Async call  :3
[info] application - Success Async call  :4
[info] application - Success Async call  :5
[info] application - Success Async call  :6
[info] application - Success Async call  :7
[info] application - Success Async call  :8
[info] application - Success Async call  :9
[info] application - Async finished ************************** :tarun
[info] application - Async started ************************** :tarun1
[info] application - Success Async call  :1
[info] application - Success Async call  :2
[info] application - Success Async call  :3
[info] application - Success Async call  :4
[info] application - Success Async call  :5
[info] application - Success Async call  :6
[info] application - Success Async call  :7
[info] application - Success Async call  :8
[info] application - Success Async call  :9
[info] application - Async finished ************************** :tarun1

The code for this is :

package controllers

import play.Logger
import play.api.libs.json.Json
import play.api.mvc._


import scala.concurrent.Future

object StarController extends Controller {
  import play.api.libs.concurrent.Execution.Implicits.defaultContext


  def getStarAsync(name : String) = Action.async{
    val futureResult = Future{
      Logger.info("Async started ************************** :" + name)
      val a = 0;
      for( a <- 1 until 10) {
        Thread.sleep(1000)
        Logger.info("Success Async call  :" + a.toString)
      }
      Logger.info("Async finished ************************** :" + name)
      Map("success" -> Json.toJson(true), "msg" -> Json.toJson("Success Async by :" + name), "code" -> Json.toJson(200))
    }

    futureResult.map{ result =>
      Ok(Json.toJson(result))
    }
  }

}

Can anyone please help me understand , why the execution of the was sequential even with async call ?

Upvotes: 1

Views: 558

Answers (3)

summerwind
summerwind

Reputation: 181

I did a lot of experiments and found one thing. Maybe it sounds crazy, but Play handles simultaneous requests sequentially only if they are made from the same browser to the same route. If I make requests via curl or from different browsers or even from one browser but to different routes, then they are handled asynchronously. Not sure what kind of protection Play does in such a way, but this protection exists and it's a fact.

Upvotes: 1

Odomontois
Odomontois

Reputation: 16328

Just to clarify m-z's answer. This is example how you could handle some asynchronous collections in your code

def getStarAsyncOld(name: String) = Action.async {
  val futureResult = Future {
    Logger.info("Async started ************************** :" + name)
  } flatMap (_ => Future.sequence(for (a <- 1 until 10) yield Future {
    Thread.sleep(1000)
    Logger.info("Success Async call  :" + a.toString)
  })) map { _ =>
    Logger.info("Async finished ************************** :" + name)
    Map("success" -> Json.toJson(true), "msg" -> Json.toJson("Success Async by :" + name), "code" -> Json.toJson(200))
  }

  futureResult.map { result =>
    Ok(Json.toJson(result))
  }
}

or absolutely the same using for:

def getStarAsync(name: String) = Action.async {
  for {
    _ <- Future(Logger.info("Async started ************************** :" + name))
    _ <- Future.sequence(for (a <- 1 until 10) yield Future {
      Thread.sleep(1000)
      Logger.info("Success Async call  :" + a.toString)
    })
    _ = Logger.info("Async finished ************************** :" + name)
    result = Map("success" -> Json.toJson(true), "msg" -> Json.toJson("Success Async by :" + name), "code" -> Json.toJson(200))
  } yield Ok(Json.toJson(result))
}

Upvotes: 0

Michael Zajac
Michael Zajac

Reputation: 55569

Action.async doesn't magically make the controller method asynchronous. The only thing it is different about it is that it expects a Future[Result] instead of a Result. That's it. Controllers are otherwise asynchronous as they can be by nature (i.e. a normal Action gets wrapped in a Future anyway). The thing here is that Thread.sleep(1000) blocks it's thread, and is not the least bit asynchronous.

The other thing is that in dev mode (i.e. activator run), the play server uses a single thread to serve requests, so it can properly handle reload/compile, evolutions, etc. So what's happening is that you're just blocking that thread with synchronous calls. You should see different results using activator start, but even so, there's no point in using Action.async here unless you're going to delegate that blocking to a different thread pool.

Further reading.

Upvotes: 8

Related Questions