Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What effect does using Action.async have, since Play uses Netty which is non-blocking

Since Netty is a non-blocking server, what effect does changing an action to using .async?

def index = Action { ... }

versus

def index = Action.async { ... }

I understand that with .async you will get a Future[SimpleResult]. But since Netty is non-blocking, will Play do something similar under the covers anyway?

What effect will this have on throughput/scalability? Is this a hard question to answer where it depends on other factors?

The reason I am asking is, I have my own custom Action and I wanted to reset the cookie timeout for every page request so I am doing this which is a async call:

object MyAction extends ActionBuilder[abc123] {
  def invokeBlock[A](request: Request[A], block: (abc123[A]) => Future[SimpleResult]) = {
    ...
    val result: Future[SimpleResult] = block(new abc123(..., result))
    result.map(_.withCookies(...))
  }
}

The take away from the above snippet is I am using a Future[SimpleResult], is this similar to calling Action.async but this is inside of my Action itself?

I want to understand what effect this will have on my application design. It seems like just for the ability to set my cookie on a per request basis I have changed from blocking to non-blocking. But I am confused since Netty is non-blocking, maybe I haven't really changed anything in reality as it was already async?

Or have I simply created another async call embedded in another one?

Hoping someone can clarify this with some details and how or what effect this will have in performance/throughput.

like image 219
Blankman Avatar asked Jun 11 '14 14:06

Blankman


2 Answers

def index = Action { ... } is non-blocking you are right.

The purpose of Action.async is simply to make it easier to work with Futures in your actions.

For example:

def index = Action.async {
  val allOptionsFuture: Future[List[UserOption]] = optionService.findAll()
  allOptionFuture map {
    options =>
      Ok(views.html.main(options))
  }
}

Here my service returns a Future, and to avoid dealing with extracting the result I just map it to a Future[SimpleResult] and Action.async takes care of the rest.

If my service was returning List[UserOption] directly I could just use Action.apply, but under the hood it would still be non-blocking.

If you look at Action source code, you can even see that apply eventually calls async: https://github.com/playframework/playframework/blob/2.3.x/framework/src/play/src/main/scala/play/api/mvc/Action.scala#L432

like image 171
vptheron Avatar answered Nov 03 '22 21:11

vptheron


I happened to come across this question, I like the answer from @vptheron, and I also want to share something I read from book "Reactive Web Applications", which, I think, is also great.

The Action.async builder expects to be given a function of type Request => Future[Result]. Actions declared in this fashion are not much different from plain Action { request => ... } calls, the only difference is that Play knows that Action.async actions are already asynchronous, so it doesn’t wrap their contents in a future block.

That’s right — Play will by default schedule any Action body to be executed asynchronously against its default web worker pool by wrapping the execution in a future. The only difference between Action and Action.async is that in the second case, we’re taking care of providing an asynchronous computation.

It also presented one sample:

def listFiles = Action { implicit request =>
  val files = new java.io.File(".").listFiles
  Ok(files.map(_.getName).mkString(", "))
}

which is problematic, given its use of the blocking java.io.File API.

Here the java.io.File API is performing a blocking I/O operation, which means that one of the few threads of Play's web worker pool will be hijacked while the OS figures out the list of files in the execution directory. This is the kind of situation you should avoid at all costs, because it means that the worker pool may run out of threads.

-

The reactive audit tool, available at https://github.com/octo-online/reactive-audit, aims to point out blocking calls in a project.

Hope it helps, too.

like image 2
shyan1 Avatar answered Nov 03 '22 19:11

shyan1