async by default

2.125 visualizzazioni
Passa al primo messaggio da leggere

James Roper

da leggere,
22 gen 2013, 07:48:0922/01/13
a play-fram...@googlegroups.com
This topic of async by default keeps on coming up all over the place, and so I thought I'd post this email to the dev mailing list so that we can refer people to it in future.  Here's the thing, Play actions are async by default.  The confusion comes from the fact that we use the word "async" in the code of actions that involve futures, and not in actions that don't, implying that actions that involve futures are asynchronous, and actions that don't are not.  So let me explain what I mean by in Play actions being async by default.  Here is an action that you might think is not async:

def index = Action {
  Ok(views.html.index("Your new application is ready."))
}

Truth is, it is async.  Note that the "{ Ok(...) }" part of the code is not the method body of index.  It is an anonymous function that is being passed to the Action object apply method, which is creating an object of type Action.  Remember that!  The code of this action that you wrote is not the method body, but an anonymous function.  Action is a class with two attributes.  One of them is a body parser, and a body parser is just a function that takes a request header and returns an iteratee that consumes byte arrays and produces a body of a particular type, we'll call it A.  The second attribute is a function that takes a request with a body of type A, and returns a result.  This is where our anonymous function ends up.

Now an action is actually just an implementation of the EssentialAction trait.  An EssentialAction is a function that takes a request header, and returns an Iteratee that consumes byte arrays and produces a Result.  Action implements this by first applying the body parser to the RequestHeader, which gives it an iteratee to produce the body, then mapping this to be a request with a body, and then mapping this using the anonymous function from the action implementation.  If you haven't followed me, don't worry.  The point of explaining that so to show this, the following code does almost exactly the same thing as the first implementation of the action:

def index = new EssentialAction {
  def apply(rh: RequestHeader) = Iteratee.ignore[Array[Byte]].map { u =>
    Ok(views.html.index("Your new application is ready"))
  }
}

There are a few slight differences, in the code above, we are ignoring the body, where as the first implementation uses the default body parser, which will actually parse the body if one is available to be parsed, even though our code ignores it.  Though, in the case of a GET request, there will be no body, so parsing it is irrelevant.  The other difference is that the first one will have an intermediate Request[AnyContent] object created, which gets passed to the function passed to Action, and as you can see in our code, is promptly ignored.  We're skipping that step here too.

So, apart from the minor differences, they are essentially identical, not just in the end result, but in the way they work internally.  And, the point I want to make here, is that the second implementation shows very clearly that it is asynchronous.  We asynchronously parse the body, then we don't wait for the result, instead we pass our anonymous function to the map method, the same way as you map futures asynchronously.  And that's why I wanted you to remember that the core of our action implementation was an anonymous function, it's just a function that gets passed to an asynchronous map method.  There's nothing synchronous about it, it's plain old good old asynchronous code.  So what's the go with async?  Well let's have a look at an async action:

def index = Action {
  Async {
    WS.url("http://playframework.org").get().map { resp =>
      if (resp.status == 200) Ok("Play is up!") else Ok("Play is down :(")
    }
  }
}

Implementing this is an EssentialAction could look like this:

def index = new EssentialAction {
  def apply(rh: RequestHeader) = Iteratee.ignore[Array[Byte]].mapM { u =>
    WS.url("http://playframework.org").get().map { resp =>
      if (resp.status == 200) Ok("Play is up!") else Ok("Play is down :(")
    }
  }
}

As you can see there's no sign of "async" here, because nothing special is needed, it's already async.  The only difference is instead of calling map, I've called mapM.  The "M" is a convention used in functional programming (eg haskell uses it) to say this is a monadic operation, it takes a function that returns a container type.  If I were to convert the Iteratee to a Future (it is possible to do this), I would actually be calling flatMap instead of mapM, both methods have essentially the same signature.

However, this implementation is slightly different from what actually happens, because what actually happens in the first implementation is that we return an AsyncResult, the Async method is just a convenience method for creating an AsyncResult.  AsyncResult is not a very good name, it implies that other results are not asynchronous.  The existence of AsyncResult I think comes from a combination of how code has evolved from when it was first written (EssentialAction is actually a relatively new thing), and the convenience methods we've used for building EssentialAction (this is the Action class and companion object, it also happens to make action composition really easy).  When using the Action convenience methods, you don't have a choice as to whether mapM is called, or map is called, you just pass the function.  As it happens, map is called, which is why you can't just return a Future[Result].  Instead, you wrap it in an AsyncResult, and later on Play inspects whether the Result is a PlainResult or an AsyncResult, and if it's an AysncResult, it recurses using the wrapped Futures map function to eventually get to a PlainResult, and handles that.

So, let's be clear, things are async by default, but the naming of the helper methods confuses people, making them think there are two types of actions, synchronous and asynchronous.  Should we do something about this?  I don't know.  Here are some options:

* Unfortunately, supporting returning either Result or Future[Result] is not an option, due to type erasure.  However, we could define an implicit conversion that implicitly converts Future[Result] to AysncResult, and so fake it.  However, this still means we've got this "AsyncResult" that composing actions and filters need to deal with.
* Change the return type of Action to Future[Result].  But then what should actions that don't need futures do?  Wrap everything in Future.successful()?  That seems like a lot of unnecessary boiler plate to me, especially for unimplemented actions, actions that just redirect, etc.   An implicit conversion may be able to be used to convert Result to Future[Result].
* Change actions so they receive a Future[Request[A]].  This is option is a fair amount of extra boilerplate, but it does make it completely explicit that all actions are asynchronous, since to handle an action, you will simply map the request.  It doesn't however solve the problem for actions that ignore the request.
* Allow you to call Action.map { } and Action.mapM { } instead of just Action { }.  The first returns Result, the second returns a Future[Result].  Problem here is it's a bit confusing to newcomers, what does mapM mean?  Monads?  I thought that was just snobby language Haskell developers used to put everyone else off?

So anyway, these are my thoughts on async by default.

Cheers,

James

--
James Roper
Software Engineer

Typesafe - The software stack for applications that scale
Twitter: @jroper

James Ward

da leggere,
22 gen 2013, 08:36:3522/01/13
a James Roper, play-fram...@googlegroups.com
Wow. Thanks for that detailed explanation! That is really great stuff
to know.

-James
> --
>
>


Viktor Klang

da leggere,
22 gen 2013, 09:27:4122/01/13
a James Ward, James Roper, play-fram...@googlegroups.com
On Tue, Jan 22, 2013 at 2:36 PM, James Ward <james...@typesafe.com> wrote:
Wow.  Thanks for that detailed explanation!  That is really great stuff
to know.

Yeah, great writeup Roper!

Did you try Result => Future[Result] implicit conversion, (combined with making Action return a Future[Result]) and if so what did you think of that?

Cheers,
 
--





--
Viktor Klang
Director of Engineering

TypesafeThe software stack for applications that scale
Twitter: @viktorklang

Julien Richard-Foy

da leggere,
22 gen 2013, 09:56:5922/01/13
a Viktor Klang, James Ward, James Roper, play-fram...@googlegroups.com
Thanks James for trying to make things better!

Another option, similar to Action.map and Action.mapM, could be to use
Action and ActionM (or ActionF? or AsyncAction?) to return
respectively a Result and a Future[Result].

Pascal Voitot Dev

da leggere,
22 gen 2013, 10:05:4322/01/13
a James Roper, play-fram...@googlegroups.com
>>I resend my mail because I sent it just to James R. ;)

Great explanation.

AFAIK, EssentialAction was introduced by Sadek as a "refinement" of the concept of Action. Before EssentialAction, we had only trait Action[A] extends (Request[A] => Result) introducing type A at the lowest level of the API.
This type A was a bit superfluous at the pure action level. When you deal with an action, it's essentially a header, an array of byte for the body and after action is performed, you get a result... type A don't appear in the result and is just useful at the controller level... EssentialAction was born and Action is now built on top of EssentialAction as you explained...

Your comment about async is interesting because I hadn't even thought people would mistake this... For me, everything has always been async and Async{} is just a facility to be able to flatten the Future my action might return.
I don't know if we should change anything... maybe some clear explanation in doc would be enough.
Anyway, I'm biased as I've never been mistaken about this...

Pascal

--
 
 

Pascal Voitot Dev

da leggere,
22 gen 2013, 10:06:4422/01/13
a Julien Richard-Foy, Viktor Klang, James Ward, James Roper, play-fram...@googlegroups.com
Just be careful about ActionM because I've discovered lots of people don't really understand the M(onadic) stuff and often ask "why xxxM and not an explicit name????"
 
Pascal

--



Alex Jarvis

da leggere,
22 gen 2013, 10:19:0922/01/13
a Pascal Voitot Dev, play-fram...@googlegroups.com
As an end-user of the framework, I was initially under the impression that the default Actions were not asynchronous until reading James's description.

Although recently I've learnt how actions are handled by akka actors via a dispatcher and so figured they were of some asynchronous value, I did not fully understand this implementation detail and how Pascal puts it "Async{} is just a facility to be able to flatten the Future my action might return.".

Maybe then, if what you guys are trying to do is create a more semantic API where you don't want people to ask this question, simply renaming the Async{} function to something that has 'flatten' in it, or similar might do the trick.

That's just my opinion anyway, so please let me know what you think. The other option would be to do it automatically via implicit conversion which would solve the issue of writing the (albeit small) boilerplate and then you can preach that all actions are async (as they are) and avoid any confusion.

Regards,
Alex

--
 
 

Guillaume Bort

da leggere,
22 gen 2013, 10:59:4322/01/13
a Alex Jarvis, Pascal Voitot Dev, play-fram...@googlegroups.com
I think that the ideal situation would be to change the signature to have all Action returning a Future[Result]. 

Now because of the current limitations with type erasure that only solution would be the Result to Future[Result] implicit conversion. I don't think it is too dangerous, but it has to be evaluated more deeply.


--
 
 



--
Guillaume Bort, http://guillaume.bort.fr

Julien Richard-Foy

da leggere,
22 gen 2013, 11:04:0322/01/13
a Guillaume Bort, Alex Jarvis, Pascal Voitot Dev, play-fram...@googlegroups.com
> Now because of the current limitations with type erasure

Which problem are you referring to?

Guillaume Bort

da leggere,
22 gen 2013, 11:42:2422/01/13
a Julien Richard-Foy, Alex Jarvis, Pascal Voitot Dev, play-fram...@googlegroups.com
We can't have

apply(f: RequestHeader => Result)

and

apply(f: RequestHeader => Future[Result])

Because both are erased as apply(Function1)


On Tue, Jan 22, 2013 at 5:04 PM, Julien Richard-Foy <j...@zenexity.com> wrote:
> Now because of the current limitations with type erasure

Which problem are you referring to?



Julien Richard-Foy

da leggere,
22 gen 2013, 11:44:5922/01/13
a Guillaume Bort, Alex Jarvis, Pascal Voitot Dev, play-fram...@googlegroups.com
Do we need that EssentialAction extends Function1[…]?

Julien Richard-Foy

da leggere,
22 gen 2013, 11:47:1522/01/13
a Guillaume Bort, Alex Jarvis, Pascal Voitot Dev, play-fram...@googlegroups.com
Oops, my comment was stupid.
Ok, so if we want to not overload apply, the only choice is to define
an implicit conversion from Result to Future[Result]?

Guillaume Bort

da leggere,
22 gen 2013, 11:50:2622/01/13
a Julien Richard-Foy, Alex Jarvis, Pascal Voitot Dev, play-fram...@googlegroups.com
Yes this is the only possible solution that would allow for:

Action {
   Ok("Hello!")
}

And:

Action {
   somethingInTheFuture.map { x => 
     Ok("Hello " + x) 
  }
}

Now we can also solve this at the naming level as proposed by James. I think that Action.mapM would be fine.

Viktor Klang

da leggere,
22 gen 2013, 12:06:5622/01/13
a Guillaume Bort, Julien Richard-Foy, Alex Jarvis, Pascal Voitot Dev, play-fram...@googlegroups.com
On Tue, Jan 22, 2013 at 5:50 PM, Guillaume Bort <guillau...@gmail.com> wrote:
Yes this is the only possible solution that would allow for:

Action {
   Ok("Hello!")
}

And:

Action {
   somethingInTheFuture.map { x => 
     Ok("Hello " + x) 
  }
}

Now we can also solve this at the naming level as proposed by James. I think that Action.mapM would be fine.

In my experience one of the biggest problem with choice is that it needs education to be performed accurately, if one does not know what to choose, it is easy to pick the wrong thing.

Cheers,
 


On Tue, Jan 22, 2013 at 5:47 PM, Julien Richard-Foy <j...@zenexity.com> wrote:
Oops, my comment was stupid.
Ok, so if we want to not overload apply, the only choice is to define
an implicit conversion from Result to Future[Result]?

On Tue, Jan 22, 2013 at 5:44 PM, Julien Richard-Foy <j...@zenexity.com> wrote:
> Do we need that EssentialAction extends Function1[…]?
>
> On Tue, Jan 22, 2013 at 5:42 PM, Guillaume Bort
> <guillau...@gmail.com> wrote:
>> We can't have
>>
>> apply(f: RequestHeader => Result)
>>
>> and
>>
>> apply(f: RequestHeader => Future[Result])
>>
>> Because both are erased as apply(Function1)
>>
>>
>> On Tue, Jan 22, 2013 at 5:04 PM, Julien Richard-Foy <j...@zenexity.com>
>> wrote:
>>>
>>> > Now because of the current limitations with type erasure
>>>
>>> Which problem are you referring to?
>>
>>
>>
>>
>> --
>> Guillaume Bort, http://guillaume.bort.fr



--
Guillaume Bort, http://guillaume.bort.fr

--
 
 



--
Viktor Klang
Director of Engineering
TypesafeThe software stack for applications that scale
Twitter: @viktorklang

Scott Clasen

da leggere,
22 gen 2013, 20:33:2522/01/13
a play-fram...@googlegroups.com
We were doing some benchmarking against a sinatra app at heroku, and noticed seeming scalability differences between these 2 types of code...


def benchmark1() = Action {
   DB.withConnection{
      do some stuff
   }
   Ok
}

def benchmark2() = Action {
   Async { Akka.future { DB.withConnection {
     //Do same stuff
   }
   Ok
   }}
}


Benchmark 2 seems to perform far better under load....at least on the constrained resource of a heroku dyno.

So I think the async by default is more of people are worried that if they write an action like benchmark1  they are going to "block".

Is wrapping DB.with... in Akka.future{ } an OK thing to do?  Perhaps that should happen internally and the DB stuff should return Futures?

James Ward

da leggere,
22 gen 2013, 21:39:3222/01/13
a play-fram...@googlegroups.com
Were these tested with Play 2.0 or 2.1?

-James
> --
>
>


Scott Clasen

da leggere,
22 gen 2013, 21:47:2522/01/13
a play-fram...@googlegroups.com
It was 2.0.4

James Roper

da leggere,
22 gen 2013, 21:47:0622/01/13
a Scott Clasen, play-fram...@googlegroups.com
The thing that I didn't talk about is how does an action be synchronous.  If you block, then your action is doing synchronous IO.  Where you block, and more specifically, which execution context you block in, will greatly impact the performance and scalability of your app.

So here's some code:

def benchmark1 = Action {
  DB.withConnection{
    // do some stuff
  }
  Ok
}

This synchronous io action should have almost identical performance characteristics to this synchronous io action:

import play.api.libs.concurrent.Execution.Implicits._

def benchmark1 = Action {
  Async {
   Future {
     DB.withConnection{
       // do some stuff
     }
     Ok
   }
  }
}

Note how the above action is using the async method, but it's still doing synchronous IO.  It's a synchronous action, you can't magically turn synchronous IO into asynchronous by wrapping it in an AsyncResult (again, this is why I don't like the name of that result).  Now the reason why it has exactly the same performance characteristics is because the execution context it's using is the Play default execution context.  So the code outside the Future call is being executed by threads from the same pool as code inside. We could also use Future.successful, and then it would be executed synchronously by the same thread.

By default Plays default execution context is tuned for async work, you get one thread in the thread pool per processor, this is why blocking is very bad.  But, there's nothing stopping you from tuning this to be 200, like a traditional thread pool used by servlet containers.  Then your performance should be much better.

In the case where you dispatch the synchronous io code to the Akka execution context, well that's a different execution context, and depending on how you have that tuned, it may have more threads available than the default Play execution context, in which case you may see better performance.

If we were to change action so that you had to return Future[Result], then if we get our implicit conversion right (eg, we implicitly convert the action function to be a function that returns Future[Result], not to convert the result to be a Future[Result]), then we can require that a user always selects (by default implicitly brought in by the Controller mix in) an execution context for their action to be executed in.  They can then easily override this on a per controller class basis, or on a per action method basis, just with import statements, choosing one execution context for DB queries, another for other things, etc.  This will mean you don't need to explicitly dispatch to another execution context either via Akka or via Future.


--
 
 

Scott Clasen

da leggere,
22 gen 2013, 22:05:0722/01/13
a play-fram...@googlegroups.com
Great thanks for the info! I think that is one thing missing from the docs, I have to think tons of play apps are simple crud with a (blocking) db, and would be great if there were some advice given on how to scale play in this case.

Is a seperate execution context wrapped around db calls appropriate? What knobs should people turn, and why?

Should play adopt Havoc P's suggestion from a blog post while back of apps having 2 thread pools one for 'blocking'/io intensive and 1 for CPU intensive? (I'm probably summarizing that incorrectly but hopefully you get the idea)

This is one area that a little codifying of best practices in docs or code could go along way IMO.


Mushtaq Ahmed

da leggere,
22 gen 2013, 23:54:4722/01/13
a play-fram...@googlegroups.com
This is an excellent thread with a lot of useful info. +1 for adding this to the docs.

Scott Clasen

da leggere,
23 gen 2013, 00:33:3423/01/13
a play-fram...@googlegroups.com

Guillaume Bort

da leggere,
23 gen 2013, 05:57:0323/01/13
a James Roper, Scott Clasen, play-fram...@googlegroups.com

On Wed, Jan 23, 2013 at 3:47 AM, James Roper <james...@typesafe.com> wrote:
If we were to change action so that you had to return Future[Result], then if we get our implicit conversion right (eg, we implicitly convert the action function to be a function that returns Future[Result], not to convert the result to be a Future[Result]), then we can require that a user always selects (by default implicitly brought in by the Controller mix in) an execution context for their action to be executed in.

Transforming a `Result` into an already successful `Future[Result]` is not a blocking operation and doesn't require an ExecutionContext (perhaps it does?).

If you return a Future[Result] in your Action, it means that you have run some logic on an external ExecutionContext, and it's your responsibility to choose which one.

James Roper

da leggere,
23 gen 2013, 06:05:1023/01/13
a Guillaume Bort, Scott Clasen, play-fram...@googlegroups.com
On Wed, Jan 23, 2013 at 9:57 PM, Guillaume Bort <guillau...@gmail.com> wrote:

On Wed, Jan 23, 2013 at 3:47 AM, James Roper <james...@typesafe.com> wrote:
If we were to change action so that you had to return Future[Result], then if we get our implicit conversion right (eg, we implicitly convert the action function to be a function that returns Future[Result], not to convert the result to be a Future[Result]), then we can require that a user always selects (by default implicitly brought in by the Controller mix in) an execution context for their action to be executed in.

Transforming a `Result` into an already successful `Future[Result]` is not a blocking operation and doesn't require an ExecutionContext (perhaps it does?).

That's right, but the other option is to implement an implicit conversion that looks like this:

implicit def convert[A](action: Request[A] => Result)(implicit executor: ExecutionContext): Request[A] => Future[Result] = {
  (r: Request[A]) => Future(action(r))(executor)
}
 
I don't know if that will work, but it would be nice if it did.


If you return a Future[Result] in your Action, it means that you have run some logic on an external ExecutionContext, and it's your responsibility to choose which one.


--
Guillaume Bort, http://guillaume.bort.fr

Sadek Drobi

da leggere,
23 gen 2013, 08:11:3323/01/13
a James Roper, Guillaume Bort, Scott Clasen, play-fram...@googlegroups.com
I must admit that I am strongly against introducing an implicit conversion to solve a problem like this. If it is the async that bothers then why not rename it to , say, Action.eventually{} or something like that. The nice thing about the API is that it involves no magic, I don't guess it is necessary to add it here.

For the execution Context, I don't guess we should add an implicit parameter for ExecutionContext. If you need to set a different ExecutionContext for parts of your app then you can simply define your own builder method, DbAction{} that will wrap your code in the appropriate Future with the appropriate ExecutionContext.

I guess this way we keep the API simple and we remove the ambiguity around async.


--
 
 

Julien Richard-Foy

da leggere,
23 gen 2013, 08:26:3723/01/13
a Sadek Drobi, James Roper, Guillaume Bort, Scott Clasen, play-fram...@googlegroups.com
Yet another solution, not involving an implicit conversion and
removing the Async(…) thing would be to define the `apply` method of
the `Action` object with the following signature:

def apply[A, B : PlayResult](parser: BodyParser[A])(block:
Request[A] => B): Action[A]

And then define implicit instances for PlayResult[Result] and
PlayResult[Future[Result]].

Sadek Drobi

da leggere,
23 gen 2013, 08:30:0123/01/13
a James Roper, Guillaume Bort, Scott Clasen, play-fram...@googlegroups.com
Also Async is not that bad. Actually if you don't use Async then your code WILL be blocking the execution context and the code you provide is synchronous. Async means that your provided code is asynchronous. I guess explained this way it does have the write name.

Sadek Drobi

da leggere,
23 gen 2013, 08:30:5123/01/13
a Julien Richard-Foy, James Roper, Guillaume Bort, Scott Clasen, play-fram...@googlegroups.com
Yeah, I thought of this, but again it is involving trickery for syntax, something I really dislike because it complicates the API.

rintcius

da leggere,
24 gen 2013, 01:23:3024/01/13
a play-fram...@googlegroups.com
Regarding:


* Unfortunately, supporting returning either Result or Future[Result] is not an option, due to type erasure.  However, we could define an implicit conversion that implicitly converts Future[Result] to AysncResult, and so fake it.  However, this still means we've got this "AsyncResult" that composing actions and filters need to deal with.


Maybe the magnet pattern is an option to look at? http://spray.io/blog/2012-12-13-the-magnet-pattern/
One of the things it solves is (quote):

“Collisions” caused by type erasure

Julien Richard-Foy

da leggere,
24 gen 2013, 07:30:2424/01/13
a Sadek Drobi, James Roper, Guillaume Bort, Scott Clasen, play-fram...@googlegroups.com
On Wed, Jan 23, 2013 at 2:30 PM, Sadek Drobi <s...@zenexity.com> wrote:
> Yeah, I thought of this, but again it is involving trickery for syntax,
> something I really dislike because it complicates the API.

I agree. However I think that’s the most acceptable tradeoff, compared
to other solutions. And we can even define an intelligible error
message if the implicit PlayResult value is not found.

Otherwise an Action.mapM(…) thing (or maybe just Action.async(…)) could be fine.

I have another thought (not completely related, though): do you think
it is a good idea that the default behavior consists in parsing the
request body? Shouldn’t we just ignore it unless we want to use it?

Then we would have the following signatures in the `Action` object:

def apply(block: RequestHeader => Result): EssentialAction

def apply[A](parser: BodyParser[A])(block: Request[A] => Result): Action[A]


Another question: why are forms not directly handled by body parsers?
I imagine something like:

val login = Action(loginForm) { req =>
req.body.fold {
case (user, pwd) => Ok
case errors => BadRequest
}
}

James Roper

da leggere,
24 gen 2013, 22:23:4224/01/13
a Julien Richard-Foy, Sadek Drobi, Guillaume Bort, Scott Clasen, play-fram...@googlegroups.com
I have another thought (not completely related, though): do you think
it is a good idea that the default behavior consists in parsing the
request body? Shouldn’t we just ignore it unless we want to use it?

Then we would have the following signatures in the `Action` object:

  def apply(block: RequestHeader => Result): EssentialAction

  def apply[A](parser: BodyParser[A])(block: Request[A] => Result): Action[A]

In the case of GET requests, there's no body, so it makes no difference whether there is a parser configured or not.  In the case of POST and PUT requests, there is usually a body, and in 99% of cases if there is a body you want to do something with it.  So I think having the any content body parser there by default makes a lot of sense.  Also note that regardless of whether you want to handle the body or not, you must consume it, since very few clients ever try to read a response until they've finished writing the body.  If you don't read the body, you risk getting into a deadlock where the client is getting pushback on the network because you aren't consuming it, and so it isn't trying to read the response yet, and you're trying to send a response but getting pushback from the network because the client isn't consuming it.  
 
Another question: why are forms not directly handled by body parsers?
I imagine something like:

val login = Action(loginForm) { req =>
  req.body.fold {
    case (user, pwd) => Ok
    case errors => BadRequest
  }
}

I think this makes a lot of sense.  There are a few ways we could support this:

* Create a form binding body parser, so your code would look something like Action(form(loginForm))
* Create an implicit conversion that convers a form to a body parser, so your code would look just like the above
* Make Form implement BodyParser, so your code would look just like the above

I don't think we should overload Action to accept a Form, because I don't think our core classes should depend on the form API (we might decide to pull it out into its own module, and provide other ways of binding forms, etc).

Sadek Drobi

da leggere,
25 gen 2013, 02:36:2325/01/13
a James Roper, Julien Richard-Foy, Guillaume Bort, Scott Clasen, play-fram...@googlegroups.com
On Fri, Jan 25, 2013 at 4:23 AM, James Roper <james...@typesafe.com> wrote:
I have another thought (not completely related, though): do you think
it is a good idea that the default behavior consists in parsing the
request body? Shouldn’t we just ignore it unless we want to use it?

Then we would have the following signatures in the `Action` object:

  def apply(block: RequestHeader => Result): EssentialAction

  def apply[A](parser: BodyParser[A])(block: Request[A] => Result): Action[A]

In the case of GET requests, there's no body, so it makes no difference whether there is a parser configured or not.  In the case of POST and PUT requests, there is usually a body, and in 99% of cases if there is a body you want to do something with it.  So I think having the any content body parser there by default makes a lot of sense.  Also note that regardless of whether you want to handle the body or not, you must consume it, since very few clients ever try to read a response until they've finished writing the body.  If you don't read the body, you risk getting into a deadlock where the client is getting pushback on the network because you aren't consuming it, and so it isn't trying to read the response yet, and you're trying to send a response but getting pushback from the network because the client isn't consuming it.  
 
Another question: why are forms not directly handled by body parsers?
I imagine something like:

val login = Action(loginForm) { req =>
  req.body.fold {
    case (user, pwd) => Ok
    case errors => BadRequest
  }
}

I think this makes a lot of sense.  There are a few ways we could support this:

* Create a form binding body parser, so your code would look something like Action(form(loginForm))
 
Or loginForm.bodyParser
 
* Create an implicit conversion that convers a form to a body parser, so your code would look just like the above

We should resist much more to implicit conversions,
 
* Make Form implement BodyParser, so your code would look just like the above

.bodyParser is better in that case
 

I don't think we should overload Action to accept a Form, because I don't think our core classes should depend on the form API (we might decide to pull it out into its own module, and provide other ways of binding forms, etc).

I agree.

Julien Richard-Foy

da leggere,
25 gen 2013, 03:36:5725/01/13
a James Roper, Sadek Drobi, Guillaume Bort, Scott Clasen, play-fram...@googlegroups.com
On Fri, Jan 25, 2013 at 4:23 AM, James Roper <james...@typesafe.com> wrote:
>> Another question: why are forms not directly handled by body parsers?
>> I imagine something like:
>>
>> val login = Action(loginForm) { req =>
>> req.body.fold {
>> case (user, pwd) => Ok
>> case errors => BadRequest
>> }
>> }
>
>
> I think this makes a lot of sense. There are a few ways we could support
> this:

Yes I know it’s not hard to create a body parser but I was wondering
if this idea scales for all kind of forms. E.g. iirc in the current
API we don’t handle file uploads in Form objects.

Guillaume Bort

da leggere,
25 gen 2013, 05:08:1425/01/13
a Julien Richard-Foy, James Roper, Sadek Drobi, Scott Clasen, play-fram...@googlegroups.com
We can still create a body parser that handle both a form and set of file.

But if you ask me, multipart/form-data is too old school and not really used anymore.

From the UX point of view it doesn't make sense to push plain form data and file uploading in the same request. If you have to redisplay the form because of functional errors, you loose the file. It's way better to handle the file upload asynchronously in a separate request.

Matt

da leggere,
26 gen 2013, 13:45:3026/01/13
a play-fram...@googlegroups.com, Sadek Drobi, James Roper, Guillaume Bort, Scott Clasen
I think there's a tremendous amount to be said for having a single way of doing things.  My sense is for Play to really thrive and be a dominant web framework, it will need to attract people from other languages.  Having multiple ways of doing things and/or requiring implicits for even basic operations would seem like a turn-off and just adds to the sense that Scala is complex.

There's a lot to be said for an interface that's just:

Request => Future[Response]

Easy to grok.  Easy to read.

Just my $0.02

M

Viktor Klang

da leggere,
26 gen 2013, 13:56:1126/01/13
a Matt, play-fram...@googlegroups.com, Sadek Drobi, James Roper, Guillaume Bort, Scott Clasen
From the top of my head:

I have similar experiences, when one has more than one way to do it, then you have introduced choice, and in order to be comfortable that you've made the right choice, you need to understand the implications of both, and how they interact with the rest of the code.
So, in the case where one offers choice, there needs to exist clear guidelines for when they are favorable. I.e. the value of the choice needs to be higher than the cost of it.

Cheers,


--
 
 



--
Viktor Klang
Director of Engineering
TypesafeThe software stack for applications that scale
Twitter: @viktorklang

Guillaume Bort

da leggere,
27 gen 2013, 05:57:3827/01/13
a Viktor Klang, Matt, play-fram...@googlegroups.com, Sadek Drobi, James Roper, Scott Clasen
Well we are not really taking about 2 ways to do the same thing.

We all agree that the Action signature should be Request => Future[Result]. 

We are just trying to define the best way to deal with situations where you get a Result directly instead of a Future[Result]. This is a very common situation and create a Future.success(result) seems too artificial.

Even if we know that everything is async in play, new users coming to play looking for a simple Java or Scala MVC framework, shouldn't have to deal with asynchronous result and Future at first if they don't need it.

So there is 2 subjects in the initial thread started by James:

- Changing the Action signature to => Future[Result], so removing the artificial AsyncResult, and simplifying the Actions composition.
- Finding the best way to provide a shortcut in ActionBuilder, that allow to construct an Action that return a Result directly.


Sadek Drobi

da leggere,
27 gen 2013, 06:43:4827/01/13
a Guillaume Bort, Viktor Klang, Matt, play-fram...@googlegroups.com, James Roper, Scott Clasen
On Sun, Jan 27, 2013 at 11:57 AM, Guillaume Bort <guillau...@gmail.com> wrote:
Well we are not really taking about 2 ways to do the same thing.

We all agree that the Action signature should be Request => Future[Result]. 

We are just trying to define the best way to deal with situations where you get a Result directly instead of a Future[Result]. This is a very common situation and create a Future.success(result) seems too artificial.

Even if we know that everything is async in play, new users coming to play looking for a simple Java or Scala MVC framework, shouldn't have to deal with asynchronous result and Future at first if they don't need it.

So there is 2 subjects in the initial thread started by James:

- Changing the Action signature to => Future[Result], so removing the artificial AsyncResult, and simplifying the Actions composition.

AsyncResult is not artificial. It means what it says. If you don't use Async then you ARE blocking. Use Async to not block. Any action that uses simple Result and does blocking IO will be blocking execution of other actions, thus should use Async (or user should use larger thread pools).

The initial post of this thread is misleading. It talks about the fact that Actions won't block netty, but that's too low level. The fact that Play protects its internal thread pools is unrelated. One action can be blocking the other actions if it is not Async. If you just use CPU then it is OK to return simple Result otherwise return Async.

Also advanced users could define their own API, such like DBAction which will be using its own ExecutionContext, that is the beauty of Play, it has hybrid model (blocking and non blocking) and provides means of execution management.

James Roper

da leggere,
27 gen 2013, 07:50:5827/01/13
a Sadek Drobi, Guillaume Bort, Viktor Klang, Matt, play-fram...@googlegroups.com, Scott Clasen
AsyncResult is not artificial. It means what it says. If you don't use Async then you ARE blocking. Use Async to not block.

But that's misleading.  Consider the following code:

import play.api.libs.concurrent.Execution.Implicits._

def myAction = Action {
  Async {
    Future {
      return Ok(someBlockingIoCall())
    }
  }
}

Is this blocking or not?  It uses Async, according to your statement, it's not blocking.  But in actual fact, there is no difference whatsoever, in terms of which thread pool does what, between that code, and this code:

def myAction = Action {
  return Ok(someBlockingIoCall())
}

And this is what I don't like about async, it misleads people to think that just because they use it, it means their code is async.  But, whether they use the Async keyword or not has no bearing on whether their action is async or not.

The other difference between the two code blocks is the first is harder to compose, since to do anything with the result of the first, you need to unwrap it.  We provide some helper methods on AsyncResult, such as a transform method, but if you want to catch an exception for example (which you probably will want to do for the very common use case of reporting metrics), then the only way you can do that is by recursively unwrapping the AsyncResult and attaching on onFailure handler to each Future.  If it was just a Future[Result], then you wouldn't need to do this, and we wouldn't need to provide helper methods.  We could add more helper methods, in the end we'll just end up reimplementing the Future interface.


--
James Roper
Software Engineer
Typesafe - The software stack for applications that scale
Twitter: @jroper

Sadek Drobi

da leggere,
27 gen 2013, 09:55:4427/01/13
a James Roper, Guillaume Bort, Viktor Klang, Matt, play-fram...@googlegroups.com, Scott Clasen


Sent from my iPhone

On 27 janv. 2013, at 13:51, James Roper <james...@typesafe.com> wrote:


AsyncResult is not artificial. It means what it says. If you don't use Async then you ARE blocking. Use Async to not block.

But that's misleading.  Consider the following code:

import play.api.libs.concurrent.Execution.Implicits._

def myAction = Action {
  Async {
    Future {
      return Ok(someBlockingIoCall())
    }
  }
}

Is this blocking or not?  It uses Async, according to your statement, it's not blocking.  But in actual fact, there is no difference whatsoever, in terms of which thread pool does what, between that code, and this code:

def myAction = Action {
  return Ok(someBlockingIoCall())
}

And this is what I don't like about async,

This is cheating, what if you should not import this execution context? Things become more logical don't they?


it misleads people to think that just because they use it, it means their code is async.  


But, whether they use the Async keyword or not has no bearing on whether their action is async or not.

It has if you don't intentionally use that execution context. 

Scott Clasen

da leggere,
27 gen 2013, 12:07:3927/01/13
a Sadek Drobi, Guillaume Bort, Viktor Klang, Matt, play-fram...@googlegroups.com, James Roper
I would argue that the DBAction Sadek mentions should be part of play (or at least a reference impl in the play-plugins repo) and not something advanced users should have to create.

Sent from my iPhone

Luis Ángel Vicente Sánchez

da leggere,
27 gen 2013, 12:27:5627/01/13
a play-fram...@googlegroups.com
My 2 cents as a play user.

To me Async is just a explicit conversion from Future[Result] to AsyncResult; you can remove Async "keyword" and add a implicit conversion in a trait that you should mixin to explicitly import that implicit conversion. But... that´s a different issue.

The problem I see with Async { .... } is that it´s sometimes used with this kind of construct,

Async {
   object.someMethodThatReturnAFuture map {
      temp =>
         val resu =maybeTimeConsumingOperation()
         Ok(views.html.some(resu)
   }
}

then you have to provide an implicit execution context or import play global execution context.

If you don't know that play actions are indeed asynchronous  and that are executed using that context, you will use it consuming resources that should be used to execute actions instead of business logic.

So from the point of view of an user new to scala / akka / play framework this looks like... "Oh... if I use async I have to provide and execution context, so... async is used if I want asynchronous actions".

Kind regards,

Luis


El martes, 22 de enero de 2013 12:48:09 UTC, james...@typesafe.com escribió:
This topic of async by default keeps on coming up all over the place, and so I thought I'd post this email to the dev mailing list so that we can refer people to it in future.  Here's the thing, Play actions are async by default.  The confusion comes from the fact that we use the word "async" in the code of actions that involve futures, and not in actions that don't, implying that actions that involve futures are asynchronous, and actions that don't are not.  So let me explain what I mean by in Play actions being async by default.  Here is an action that you might think is not async:

def index = Action {
  Ok(views.html.index("Your new application is ready."))
}

Truth is, it is async.  Note that the "{ Ok(...) }" part of the code is not the method body of index.  It is an anonymous function that is being passed to the Action object apply method, which is creating an object of type Action.  Remember that!  The code of this action that you wrote is not the method body, but an anonymous function.  Action is a class with two attributes.  One of them is a body parser, and a body parser is just a function that takes a request header and returns an iteratee that consumes byte arrays and produces a body of a particular type, we'll call it A.  The second attribute is a function that takes a request with a body of type A, and returns a result.  This is where our anonymous function ends up.

Now an action is actually just an implementation of the EssentialAction trait.  An EssentialAction is a function that takes a request header, and returns an Iteratee that consumes byte arrays and produces a Result.  Action implements this by first applying the body parser to the RequestHeader, which gives it an iteratee to produce the body, then mapping this to be a request with a body, and then mapping this using the anonymous function from the action implementation.  If you haven't followed me, don't worry.  The point of explaining that so to show this, the following code does almost exactly the same thing as the first implementation of the action:

def index = new EssentialAction {
  def apply(rh: RequestHeader) = Iteratee.ignore[Array[Byte]].map { u =>
    Ok(views.html.index("Your new application is ready"))
  }
}

There are a few slight differences, in the code above, we are ignoring the body, where as the first implementation uses the default body parser, which will actually parse the body if one is available to be parsed, even though our code ignores it.  Though, in the case of a GET request, there will be no body, so parsing it is irrelevant.  The other difference is that the first one will have an intermediate Request[AnyContent] object created, which gets passed to the function passed to Action, and as you can see in our code, is promptly ignored.  We're skipping that step here too.

So, apart from the minor differences, they are essentially identical, not just in the end result, but in the way they work internally.  And, the point I want to make here, is that the second implementation shows very clearly that it is asynchronous.  We asynchronously parse the body, then we don't wait for the result, instead we pass our anonymous function to the map method, the same way as you map futures asynchronously.  And that's why I wanted you to remember that the core of our action implementation was an anonymous function, it's just a function that gets passed to an asynchronous map method.  There's nothing synchronous about it, it's plain old good old asynchronous code.  So what's the go with async?  Well let's have a look at an async action:

def index = Action {
  Async {
    WS.url("http://playframework.org").get().map { resp =>
      if (resp.status == 200) Ok("Play is up!") else Ok("Play is down :(")
    }
  }
}

Implementing this is an EssentialAction could look like this:

def index = new EssentialAction {
  def apply(rh: RequestHeader) = Iteratee.ignore[Array[Byte]].mapM { u =>
    WS.url("http://playframework.org").get().map { resp =>
      if (resp.status == 200) Ok("Play is up!") else Ok("Play is down :(")
    }
  }
}

As you can see there's no sign of "async" here, because nothing special is needed, it's already async.  The only difference is instead of calling map, I've called mapM.  The "M" is a convention used in functional programming (eg haskell uses it) to say this is a monadic operation, it takes a function that returns a container type.  If I were to convert the Iteratee to a Future (it is possible to do this), I would actually be calling flatMap instead of mapM, both methods have essentially the same signature.

However, this implementation is slightly different from what actually happens, because what actually happens in the first implementation is that we return an AsyncResult, the Async method is just a convenience method for creating an AsyncResult.  AsyncResult is not a very good name, it implies that other results are not asynchronous.  The existence of AsyncResult I think comes from a combination of how code has evolved from when it was first written (EssentialAction is actually a relatively new thing), and the convenience methods we've used for building EssentialAction (this is the Action class and companion object, it also happens to make action composition really easy).  When using the Action convenience methods, you don't have a choice as to whether mapM is called, or map is called, you just pass the function.  As it happens, map is called, which is why you can't just return a Future[Result].  Instead, you wrap it in an AsyncResult, and later on Play inspects whether the Result is a PlainResult or an AsyncResult, and if it's an AysncResult, it recurses using the wrapped Futures map function to eventually get to a PlainResult, and handles that.

So, let's be clear, things are async by default, but the naming of the helper methods confuses people, making them think there are two types of actions, synchronous and asynchronous.  Should we do something about this?  I don't know.  Here are some options:

* Unfortunately, supporting returning either Result or Future[Result] is not an option, due to type erasure.  However, we could define an implicit conversion that implicitly converts Future[Result] to AysncResult, and so fake it.  However, this still means we've got this "AsyncResult" that composing actions and filters need to deal with.
* Change the return type of Action to Future[Result].  But then what should actions that don't need futures do?  Wrap everything in Future.successful()?  That seems like a lot of unnecessary boiler plate to me, especially for unimplemented actions, actions that just redirect, etc.   An implicit conversion may be able to be used to convert Result to Future[Result].
* Change actions so they receive a Future[Request[A]].  This is option is a fair amount of extra boilerplate, but it does make it completely explicit that all actions are asynchronous, since to handle an action, you will simply map the request.  It doesn't however solve the problem for actions that ignore the request.
* Allow you to call Action.map { } and Action.mapM { } instead of just Action { }.  The first returns Result, the second returns a Future[Result].  Problem here is it's a bit confusing to newcomers, what does mapM mean?  Monads?  I thought that was just snobby language Haskell developers used to put everyone else off?

So anyway, these are my thoughts on async by default.

Cheers,

James

--
James Roper
Software Engineer
Typesafe - The software stack for applications that scale
Twitter: @jroper

James Roper

da leggere,
27 gen 2013, 15:33:1327/01/13
a Sadek Drobi, Guillaume Bort, Viktor Klang, Matt, play-fram...@googlegroups.com, Scott Clasen
On 27 janv. 2013, at 13:51, James Roper <james...@typesafe.com> wrote:


AsyncResult is not artificial. It means what it says. If you don't use Async then you ARE blocking. Use Async to not block.

But that's misleading.  Consider the following code:

import play.api.libs.concurrent.Execution.Implicits._

def myAction = Action {
  Async {
    Future {
      return Ok(someBlockingIoCall())
    }
  }
}

Is this blocking or not?  It uses Async, according to your statement, it's not blocking.  But in actual fact, there is no difference whatsoever, in terms of which thread pool does what, between that code, and this code:

def myAction = Action {
  return Ok(someBlockingIoCall())
}

And this is what I don't like about async,

This is cheating, what if you should not import this execution context? Things become more logical don't they?

To us it's obviously cheating.  But what about our users?  The reason why I started this thread is because I had encountered a number of, and since posting it have continued to encounter, smart users, sometimes users who are well known in the Scala community, that were under the impression that Play operates in two modes, synchronous, and asynchronous, and that when you use the Async "keyword", your action somehow becomes an asynchronously handled action.  This is what drives the "async by default" question, because people think well wouldn't Play Scale better if it treated everything asynchronously?  And that question is wrong, because Play does treat everything asynchronously, there's not two different modes, it's up to user code to not be synchronous.

So if we were to take a poll, and ask our users, or even just the people on the dev mailing list who have seen this whole discussion, and ask them which out the above two code blocks is safe, I think we would find that many, maybe even most would say that the first is safe.  It's not cheating for me to use an example that most ordinary users believe is the safe thing to do.

This isn't a question of jargon or what is the technically correct way of explaining things, this is a question of how our users understand things, and whether our API is misleading them to think the wrong thing.  How you or I understand it is irrelevant, since we know Play inside out and cannot be considered average users.  But as many on the dev mailing list have already admitted to, there is a problem here that we are confusing our users with the current naming.  I propose that telling people "everything is asynchronous, therefore you must be very careful to know and choose which thread pool executes your synchronous io code" is a better way than saying "if you do synchronous IO, use Async".

Will Sargent

da leggere,
29 gen 2013, 17:26:5329/01/13
a play-fram...@googlegroups.com
> To us it's obviously cheating. But what about our users? The reason why I
> started this thread is because I had encountered a number of, and since
> posting it have continued to encounter, smart users, sometimes users who are
> well known in the Scala community, that were under the impression that Play
> operates in two modes, synchronous, and asynchronous, and that when you use
> the Async "keyword", your action somehow becomes an asynchronously handled
> action. This is what drives the "async by default" question, because people
> think well wouldn't Play Scale better if it treated everything
> asynchronously? And that question is wrong, because Play does treat
> everything asynchronously, there's not two different modes, it's up to user
> code to not be synchronous.

There's more to it than that -- you have to know what it means to have
an ExecutionContext and why you would want more than one. The Akka
documentation just says it's "very similar to a
java.util.concurrent.Executor" [1] and the Scala documentation doesn't
mention it at all [2].

It would be really nice to have a graphical representation of the
system, with actors connected to mailboxes in a context detailing how
requests get sent off -- I think that people understand diagrams much
better than they understand unfamiliar terminology.

Will.

[1] http://doc.akka.io/docs/akka/snapshot/scala/futures.html
[2] http://www.playframework.org/documentation/2.0/ScalaAsync

Julien L.

da leggere,
29 gen 2013, 18:45:4329/01/13
a play-fram...@googlegroups.com
+1, I think that ExecutioContext are extremely imprecise for many people. The only thing they know about it, is that it must imported.

peter hausel

da leggere,
1 feb 2013, 10:12:0501/02/13
a play-fram...@googlegroups.com
Hi all,

Good discussion.

> Change the return type of Action to Future[Result]. 

Conceptually, I think `Future[Result]` would be the best return type but I agree with those who argued that in certain situation like in James's example i.e. `Ok(views.html.index("Your new application is ready."))`  requiring a a Future wrapper around this would be an overkill  - especially if you consider the Java API as well.

As for Async:

Personally, I think Async is a useful shortcut. And shortcuts are good as long as 1) it's clear that they are  shortcuts 2) the alternative would look like overkill (consider James's iteratee example or this from the scala standard lib: https://github.com/scala/scala/blob/master/src/library/scala/concurrent/package.scala#L83 ). Furthermore, we also need to consider Java users again, who could not use iteratee-s

That being said, I do agree with James's original point, the name Async is misleading, since there is not such thing as truly synchronous call in Play. 

A few ideas:

- Deprecate `Async` name (perhaps in play 2.1? or maybe after)

- introduce a new name that gives a better idea to end users what's going on. My recommendation would be 'Isolate' (after node/V8), since all we are talking about here is isolating a unit of work from the framework's thread.

- To emphasize play's async nature, I would further reduce the default number of internal threads play is using to dispatch actions and render assets (as far as I remember the latter is happening on the same EC). The main idea behind this is to emphasize that Play is async and if you do too much on the framework threads you will be blocking and sooner you realize this is the better.   (IMHO Ideally I think play's main core thread pool should be close to a single thread by default)
 
- add some clarification about this whole topic to the wiki

- improve javadoc/scaladoc around Async and Action

- all examples should be changed to use 'Isolate' (or whatever name people agree with) when a unit of work is more than just writing to the response. For example:

just my 2c.

Peter





Jean Helou

da leggere,
2 feb 2013, 02:44:2202/02/13
a peter hausel, play-fram...@googlegroups.com

As a, relatively new, user I have discovered a LOT about what Async means thanks to this discussion. And also what execution contexts mean for Futures.

I mostly want to get things done, which is one of the selling points of play. When I started using futures I got a message telling me I needed an execution context and I could import the global one. I did just that to get my app working without understanding all the implications. Reading the discussion, I realize I most likely am not doing this right.

I would like to clarify:
- is play using the "global" execution context to process actions ?

Here is a humble suggestion:
- Could play define additional execution contexts with a few knobs to adjust them and expose them through the api: for example
play.api.ExecutionContexts.persistence
play.api.ExecutionContexts.ws
...

Having a couple settings commented in application.conf allowing to configure the most obvious params on these contexts.

This way we can document the examples by using these execution contexts. This will make sure beginners don't make the mistake of using only one execution context...

--
jean

--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Vivian Pennel

da leggere,
2 feb 2013, 12:41:3702/02/13
a play-fram...@googlegroups.com
Hi, 
I'm working with play2 on some applications, and have learned a lot too reading this.
For me Action was synchronous result and Async was dedicated to execute instructions async until i read this.

On simple database read i use this kind of code currently :

import play.api.mvc._
import concurrent.{ExecutionContext}
import ExecutionContext.Implicits.global
import models._

object Application extends Controller {
  
  def index = Action {
    Async {
      //database call which return Future[T]
      StaticContent.findOneById("home").map { content =>
        Ok(views.html.index(content))
      }
    }
  }
}

As far i understand, since i'm not using play execution context, is this code really asynchronous ? This join previous question, are play execution context and global context related ?

To my "not play2/scala experimented" point of view, i would say that this discussion cleared some things, so i think that documenting properly those things should do the trick (especially which execution context users should import, or if they have to create one : how and when).
But if it can be simplified ('user" execution context with a more large thread pool ?) this can be good too. 

Vivian

James Roper

da leggere,
3 feb 2013, 17:44:3503/02/13
a Vivian Pennel, play-fram...@googlegroups.com
Ok, so some definite action items have come out of this:

* Document execution contexts
* Document a list of different approaches/best practices on how to use thread pools


--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Mushtaq Ahmed

da leggere,
3 feb 2013, 23:59:5903/02/13
a James Roper, Vivian Pennel, play-fram...@googlegroups.com
On Mon, Feb 4, 2013 at 4:14 AM, James Roper <james...@typesafe.com> wrote:
Ok, so some definite action items have come out of this:

* Document execution contexts
* Document a list of different approaches/best practices on how to use thread pools

Thank you for identifying this. Any docs on these topics will be hugely appreciated by my team who is using futures everywhere.
 

James Roper

da leggere,
4 feb 2013, 01:40:2604/02/13
a Mushtaq Ahmed, Vivian Pennel, play-fram...@googlegroups.com

Julien L.

da leggere,
5 feb 2013, 10:22:5805/02/13
a play-fram...@googlegroups.com, Mushtaq Ahmed, Vivian Pennel

Mushtaq Ahmed

da leggere,
6 feb 2013, 09:43:5106/02/13
a James Roper, Vivian Pennel, play-fram...@googlegroups.com
Great writeup, thank you. Any recommendation when scala.concurrent.ExecutionContext.Implicits.global can be used? Is it any different than the default play threadpool?
Rispondi a tutti
Rispondi all'autore
Inoltra
0 nuovi messaggi