javaplayframeworkconcurrencycompletable-future

PlayFramework custom executors when using CompletableFutures and Java


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?).


Solution

  • You can use play.libs.concurrent.HttpExecution.fromThread method:

    An ExecutionContext that executes work on the given ExecutionContext. The current thread's context ClassLoader and Http.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.