| async by default | James Roper | 22/01/13 04:48 | 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 :(") }
} } 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 |
| Re: async by default | James Ward | 22/01/13 05:36 | Wow. Thanks for that detailed explanation! That is really great stuff
to know. -James > -- > > |
| Re: async by default | Viktor Klang | 22/01/13 06:27 | 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 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 Typesafe - The software stack for applications that scaleTwitter: @viktorklang |
| Re: async by default | Julien Richard-Foy | 22/01/13 06:56 | 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]. |
| Re: async by default | pascal.voitot.dev | 22/01/13 07:05 | >>I resend my mail because I sent it just to James R. ;)
Great explanation. 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.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... Anyway, I'm biased as I've never been mistaken about this...
Pascal -- |
| Re: async by default | pascal.voitot.dev | 22/01/13 07:06 | 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 -- |
| Re: async by default | Alex Jarvis | 22/01/13 07:19 | 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 -- |
| Re: async by default | Guillaume Bort | 22/01/13 07:59 | 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.
|
| Re: async by default | Julien Richard-Foy | 22/01/13 08:04 | > Now because of the current limitations with type erasureWhich problem are you referring to? |
| Re: async by default | Guillaume Bort | 22/01/13 08:42 | We can't have apply(f: RequestHeader => Result) and apply(f: RequestHeader => Future[Result])
Because both are erased as apply(Function1)
-- |
| Re: async by default | Julien Richard-Foy | 22/01/13 08:44 | Do we need that EssentialAction extends Function1[…]?
|
| Re: async by default | Julien Richard-Foy | 22/01/13 08:47 | 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]? |
| Re: async by default | Guillaume Bort | 22/01/13 08:50 | 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.
|
| Re: async by default | Viktor Klang | 22/01/13 09:06 | On Tue, Jan 22, 2013 at 5:50 PM, Guillaume Bort <guillau...@gmail.com> wrote: 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, √
Typesafe - The software stack for applications that scaleTwitter: @viktorklang |
| Re: async by default | Scott Clasen | 22/01/13 17:33 | 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? |
| Re: async by default | James Ward | 22/01/13 18:39 | Were these tested with Play 2.0 or 2.1?
-James > -- > > |
| Re: async by default | Scott Clasen | 22/01/13 18:47 | It was 2.0.4 |
| Re: async by default | James Roper | 22/01/13 18:47 | 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.
--
|
| Re: async by default | Scott Clasen | 22/01/13 19:05 | 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. |
| Re: async by default | Mushtaq Ahmed | 22/01/13 20:54 | This is an excellent thread with a lot of useful info. +1 for adding this to the docs. |
| Re: async by default | Scott Clasen | 22/01/13 21:33 | http://blog.ometer.com/2011/11/13/task-dispatch-and-nonblocking-io-in-scala/ is the blog post I was referring to |
| Re: async by default | Guillaume Bort | 23/01/13 02:57 | 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. |
| Re: async by default | James Roper | 23/01/13 03:05 | On Wed, Jan 23, 2013 at 9:57 PM, Guillaume Bort <guillau...@gmail.com> wrote:
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.
|
| Re: async by default | Sadek Drobi | 23/01/13 05:11 | 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. -- |
| Re: async by default | Julien Richard-Foy | 23/01/13 05:26 | 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]]. |
| Re: async by default | Sadek Drobi | 23/01/13 05:30 | 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.
On Wed, Jan 23, 2013 at 2:11 PM, Sadek Drobi <s...@zenexity.com> wrote:
|
| Re: async by default | Sadek Drobi | 23/01/13 05:30 | Yeah, I thought of this, but again it is involving trickery for syntax, something I really dislike because it complicates the API. On Wed, Jan 23, 2013 at 2:26 PM, Julien Richard-Foy <j...@zenexity.com> wrote: Yet another solution, not involving an implicit conversion and |
| Re: async by default | rintcius | 23/01/13 22:23 | 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 |
| Re: async by default | Julien Richard-Foy | 24/01/13 04:30 | On Wed, Jan 23, 2013 at 2:30 PM, Sadek Drobi <s...@zenexity.com> wrote: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 } } |
| Re: async by default | James Roper | 24/01/13 19:23 | I have another thought (not completely related, though): do you think 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.
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).
|
| Re: async by default | Sadek Drobi | 24/01/13 23:36 | On Fri, Jan 25, 2013 at 4:23 AM, James Roper <james...@typesafe.com> wrote: Or loginForm.bodyParser
We should resist much more to implicit conversions,
.bodyParser is better in that case
I agree.
|
| Re: async by default | Julien Richard-Foy | 25/01/13 00:36 | 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?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. |
| Re: async by default | Guillaume Bort | 25/01/13 02:08 | 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.
-- |
| Re: async by default | Matt | 26/01/13 10:45 | 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:
Easy to grok. Easy to read. Just my $0.02 M |
| Re: async by default | Viktor Klang | 26/01/13 10:56 | From the top of my head:
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 Typesafe - The software stack for applications that scaleTwitter: @viktorklang |
| Re: async by default | Guillaume Bort | 27/01/13 02:57 | 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. -- |
| Re: async by default | Sadek Drobi | 27/01/13 03:43 | On Sun, Jan 27, 2013 at 11:57 AM, Guillaume Bort <guillau...@gmail.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. 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. |
| Re: async by default | James Roper | 27/01/13 04:50 |
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.
Typesafe - The software stack for applications that scaleTwitter: @jroper |
| Re: async by default | Sadek Drobi | 27/01/13 06:55 | Sent from my iPhone
This is cheating, what if you should not import this execution context? Things become more logical don't they? It has if you don't intentionally use that execution context. |
| Re: async by default | Scott Clasen | 27/01/13 09:07 | 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 |
| Re: async by default | Luis Ángel Vicente Sánchez | 27/01/13 09:27 | 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ó:
|
| Re: async by default | James Roper | 27/01/13 12:33 |
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". |
| Re: async by default | Will Sargent | 29/01/13 14:26 | > To us it's obviously cheating. But what about our users? The reason why IThere'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 |
| Re: async by default | Julien L. | 29/01/13 15:45 | +1, I think that ExecutioContext are extremely imprecise for many people. The only thing they know about it, is that it must imported. |
| Re: async by default | peter hausel | 01/02/13 07:12 | Hi all,
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
|
| Re: async by default | Jean Helou | 01/02/13 23:44 | 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: Here is a humble suggestion: 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... -- -- |
| Re: async by default | Vivian Pennel | 02/02/13 09:41 | 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 {
//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 |
| Re: async by default | James Roper | 03/02/13 14:44 | 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
|
| Re: async by default | Mushtaq Ahmed | 03/02/13 20:59 | On Mon, Feb 4, 2013 at 4:14 AM, James Roper <james...@typesafe.com> wrote: Thank you for identifying this. Any docs on these topics will be hugely appreciated by my team who is using futures everywhere.
|
| Re: async by default | James Roper | 03/02/13 22:40 | Here is some documentation I've drafted: -- |
| Re: async by default | Julien L. | 05/02/13 07:22 | In addition, Sadek has post a topic on this subject: http://sadache.tumblr.com/post/42351000773/async-reactive-nonblocking-threads-futures-executioncont |
| Re: async by default | Mushtaq Ahmed | 06/02/13 06:43 | Great writeup, thank you. Any recommendation when scala.concurrent.ExecutionContext.Implicits.global can be used? Is it any different than the default play threadpool? |