In latest versions of PlayFramework they started using CompletionStage
as return type for controllers that will be used for async execution or in a nutshell if you return CompletionStage
it is asynchronous execution...
Now when we know the work we submit to CF
is a long running IO
operation we need to pass a custom executor (otherwise it will be executed on FJP
by default).
Each controller execution has a HTTP
context which has in it all the request information also this context is necessary to have your EntityManagers
if you use JPA
...
If we just create custom ExecutorService
and inject it in our controller to use in supplyAsync()
we won't have all the context information.
Here is an example of some controller action returning CompletionStage
return supplyAsync(() -> {
doSomeWork();
}, executors.io); // this is a custom CachedThreadPool with daemon thread factory
}
and if we try to run something like this in doSomeWork()
Request request = request(); // getting request using Controller.request()
or use preinjected JPAAPI jpa
field in controller
jpa.withTransaction(
() -> jpa.em() // we will get an exception here although we are wrapped in a transaction
...
);
exception like
No EntityManager bound to this thread. Try wrapping this call in JPAApi.withTransaction, or ensure that the HTTP context is setup on this thread.
As you can see the jpa code is wrapped in transaction but no context was found because this is a custom pure Java threadpool.
What is the correct way to provide all the context information when using CompletableFuture and custom executor?
I also tried defining custom executors in application.conf
and lookup them from actor system but I will end up having MessageDispatcher
which although is backed by ExecutorService
is not compatible with CompletableFuture
(maybe I'm wrong? if so how to use it with CF?).
You can use play.libs.concurrent.HttpExecution.fromThread
method:
An
ExecutionContext
that executes work on the givenExecutionContext
. The current thread's contextClassLoader
andHttp.Context
are captured when this method is called and preserved for all executed tasks.
So, the code would be something like:
java.util.concurrent.Executor executor = getExecutorFromSomewhere();
return supplyAsync(() -> {
doSomeWork();
}, play.libs.concurrent.HttpExecution.fromThread(executor));
Or, if you are using a scala.concurrent.ExecutionContext
:
scala.concurrent.ExecutionContext ec = getExecutorContext();
return supplyAsync(() -> {
doSomeWork();
}, play.libs.concurrent.HttpExecution.fromThread(ec));
But I'm not entirely sure that will preserve the EntityManager
for JPA.