§Handling asynchronous results
§Make controllers asynchronous
Internally, Play Framework is asynchronous from the bottom up. Play handles every request in an asynchronous, non-blocking way.
The default configuration is tuned for asynchronous controllers. In other words, the application code should avoid blocking in controllers, i.e., having the controller code wait for an operation. Common examples of such blocking operations are JDBC calls, streaming API, HTTP requests and long computations.
Although it’s possible to increase the number of threads in the default execution context to allow more concurrent requests to be processed by blocking controllers, following the recommended approach of keeping the controllers asynchronous makes it easier to scale and to keep the system responsive under load.
§Creating non-blocking actions
Because of the way Play works, action code must be as fast as possible, i.e., non-blocking. So what should we return from our action if we are not yet able to compute the result? We should return the promise of a result!
Java 8 provides a generic promise API called CompletionStage
. A CompletionStage<Result>
will eventually be redeemed with a value of type Result
. By using a CompletionStage<Result>
instead of a normal Result
, we are able to return from our action quickly without blocking anything. Play will then serve the result as soon as the promise is redeemed.
The web client will be blocked while waiting for the response, but nothing will be blocked on the server, and server resources can be used to serve other clients.
§How to create a CompletionStage<Result>
To create a CompletionStage<Result>
we need another promise first: the promise that will give us the actual value we need to compute the result:
CompletionStage<Double> promiseOfPIValue = computePIAsynchronously();
CompletionStage<Result> promiseOfResult = promiseOfPIValue.thenApply(pi ->
ok("PI value computed: " + pi)
);
Play asynchronous API methods give you a CompletionStage
. This is the case when you are calling an external web service using the play.libs.WS
API, or if you are using Akka to schedule asynchronous tasks or to communicate with Actors using play.libs.Akka
.
A simple way to execute a block of code asynchronously and to get a CompletionStage
is to use the CompletableFuture.supplyAsync()
helper:
CompletionStage<Integer> promiseOfInt = CompletableFuture.supplyAsync(() -> intensiveComputation());
Note: It’s important to understand which thread code runs on which promises. Here, the intensive computation will just be run on another thread.
You can’t magically turn synchronous IO into asynchronous by wrapping it in a
CompletionStage
. If you can’t change the application’s architecture to avoid blocking operations, at some point that operation will have to be executed, and that thread is going to block. So in addition to enclosing the operation in aCompletionStage
, it’s necessary to configure it to run in a separate execution context that has been configured with enough threads to deal with the expected concurrency. See Understanding Play thread pools for more information.It can also be helpful to use Actors for blocking operations. Actors provide a clean model for handling timeouts and failures, setting up blocking execution contexts, and managing any state that may be associated with the service. Also Actors provide patterns like
ScatterGatherFirstCompletedRouter
to address simultaneous cache and database requests and allow remote execution on a cluster of backend servers. But an Actor may be overkill depending on what you need.
§Using CompletionStage inside an Action
You must supply the HTTP execution context explicitly as an executor when using a Java CompletionStage
inside an Action, to ensure that the HTTP.Context remains in scope. If you don’t supply the HTTP execution context, you’ll get “There is no HTTP Context available from here” errors when you call request()
or other methods that depend on Http.Context
.
You can supply the play.libs.concurrent.HttpExecutionContext
instance through dependency injection:
public class Application extends Controller {
@Inject HttpExecutionContext ec;
public CompletionStage<Result> index() {
someCompletableFuture.supplyAsync(() -> {
// do something with request()
}, ec.current());
}
}
§Async results
We have been returning Result
up until now. To send an asynchronous result our action needs to return a CompletionStage<Result>
:
public CompletionStage<Result> index() {
return CompletableFuture.supplyAsync(() -> intensiveComputation())
.thenApply(i -> ok("Got result: " + i));
}
§Actions are asynchronous by default
Play actions are asynchronous by default. For instance, in the controller code below, the returned Result
is internally enclosed in a promise:
public Result index() {
return ok("Got request " + request() + "!");
}
Note: Whether the action code returns a
Result
or aCompletionStage<Result>
, both kinds of returned object are handled internally in the same way. There is a single kind ofAction
, which is asynchronous, and not two kinds (a synchronous one and an asynchronous one). Returning aCompletionStage
is a technique for writing non-blocking code.
Next: Streaming HTTP responses