[Play 2.6 Java] Non-blocking action

I’m trying to use non-blocking action. Following the documentation I’m able to use the first example. I need that the action returns more than one data, so I created a class for that.

private class ListProjectsData {

        private HashMap<String, Long> userProjects = new HashMap<>();
        private List<Project> projects;

        private ListProjectsData(HashMap<String, Long> userProjects, List<Project> projects) {
            this.userProjects = userProjects;
            this.projects = projects;
        }
        public HashMap<String, Long> getUserProjects() {
            return userProjects;
        }

        public List<Project> getProjects() {
            return projects;
        }
    }

    private HttpExecutionContext httpExecutionContext;
    @Inject
    public ProjectController(HttpExecutionContext ec) {
        this.httpExecutionContext = ec;
    }

    public CompletionStage<Result> listProject() {
        return calculateResponse().thenApplyAsync(ListProjectsData -> {
            // uses Http.Context
            //ctx().flash().put("info", "Response updated!");
            return ok(listProject.render(ListProjectsData.getProjects(), ListProjectsData.getUserProjects(), dataProject));
        }, httpExecutionContext.current());
    }
        //return ok(listProject.render(projects, userProjects, dataProject));
    private CompletionStage<ListProjectsData> calculateResponse() {
        List<Project> projects = projectRepository.findAll(new Sort(Sort.Direction.ASC, "name"));
        HashMap<String, Long> userProjects = new HashMap<>();

        //System.out.println(dataProject.size());
        for(Project project: projects) {
            Long n = userRepository.countByProjectsEquals(project.getName());
            if(!dataProject.containsKey(project.getName())) {
                List<Planning> plannings = planningRepository.findByDataContains(project.getName());
                dataProject.put(project.getName(), plannings.size());
            }
            userProjects.put(project.getName(), n);
        }
        return CompletableFuture.completedFuture(new ListProjectsData(userProjects, projects));
    }

The above code works fine. So I want to try the other documentation example because I’m using a long time mongo query.

 private MyExecutionContext myExecutionContext;

    @Inject
    public ProjectController(MyExecutionContext myExecutionContext) {
        this.myExecutionContext = myExecutionContext;
    }

    public CompletionStage<Result> listProject() {
        // Wrap an existing thread pool, using the context from the current thread
        Executor myEc = HttpExecution.fromThread((Executor) myExecutionContext);
        return supplyAsync(() -> calculateResponse(), myEc)
                .thenApplyAsync(ListProjectsData -> ok(listProject.render(ListProjectsData.getProjects(), ListProjectsData.getUserProjects(), dataProject)), myEc);
    }

But in this case, I’m unable to use the ListProjectsData getter to obtain the needed data. Where is the problem?

Thanks

What do you mean by “unable to use the ListProjectsData getter to obtain the needed data.”? Can you elaborate?

Using IntelliJ, the ListProjectsData.getProjects() reports cannot resolv method getProjects()

I am able to see getProjects() as valid method call after using your source.

Just a note that in the code provided, calculateResponse is a blocking method, and the use of CompletableFuture isn’t providing any benefit. If projectRepository.findAll is doing I/O, then it should be returning a CompletionStage itself, and must be implemented to perform any blocking I/O in an appropriate execution context.

If the whole method is intended to block, then it’s simpler to have it return ListProjectsData directly, rather than wrapping it in a CompletionStage.

1 Like

Correct. I guess probably the particular method call is part of a larger chain of calls not mentioned above and this method has been wrapped into a CompletableFuture so as to not break the chaining of futures.

So, if I understand I have benefit only on methods that does I/O operations. In this case projectRepository.findAll is the method that does the mongo query.

For the other problem, the code compilation error is the same as IntelliJ:
play.sbt.PlayExceptions$CompilationException: Compilation error[cannot find symbol
symbol: method getProjects()

findAll should return a CompletionStage, then, ideally using the native non-blocking API in the MongoDB driver.

There are some examples in this slide deck:

Thanks, very very interesting. Actually I’m using Spring Data MongoDB, so to use reactive access to Mongo I’ve to rewrite all the queries. Could be an interesting challenge.

In the slides there are a lot of techniques example, which of them is better to learn and use?

I had find that Spring Data MongoDB has @Async decorator to create asynchronous query. I don’t know if this is sufficient to create asynchronous query under Play. This matter is too complicated for me :-)

The best options are the ones that use the async MongoDB driver, such as example 6 in that slide deck.

You could also use the reactive streams API, which can be integrated easily with Akka Streams. This might be better if you are expecting a large number of results from the query, as you could handle them in a streaming fashion without loading all results into memory at the same time.

Spring Data now offers a reactive Mongo repository implementation as well, though it uses Spring’s custom API, and would need to be adapted to CompletionStage or Akka Streams.

I rewrote the method as the following:

public Result listProject() {
        List<Project> projects = projectRepository.findAll(new Sort(Sort.Direction.ASC, "name"));
        HashMap<String, Long> userProjects = new HashMap<>();
        for(Project project: projects) {
            Long n = userRepository.countByProjectsEquals(project.getName());
            if(!dataProject.containsKey(project.getName())) {
                CompletionStage<List<Planning>> plannings = planningRepository.findByDataContains(project.getName());
               plannings.thenApplyAsync(p -> dataProject.put(project.getName(), p.size()), httpExecutionContext.current());
              }
            userProjects.put(project.getName(), n);
        }
        return ok(listProject.render(projects, userProjects, dataProject));
    }

The query is:

 @Async
    CompletableFuture<List<Planning>> findByDataContains(String projects);

Do you think that this is sufficient to create a non blocking query?
Is there a way to debug if I’m using a blocking action or not?

I’m afraid that this does not look like a non-blocking action. There are a number of different issues with it.

  1. A non-blocking action in Play for Java should be returning a CompletionStage<Result> rather than a Result directly.
  2. Since planningRepository.findByDataContains is now returning a CompletionStage, it’s possible that this is using a non-blocking query, but projectRepository.findAll and userRepository.countByProjectsEquals still appear to be blocking.
  3. It is not safe to modify userProjects (a mutable HashMap) from multiple threads, as this code does.
  4. There is a race condition, because listProject can render the view before the asynchronous processing of the results of planningRepository.findByDataContains complete.
  5. The definition of dataProject isn’t included here, but is likely to also be unsafe to modify in this way.

To correct this, you’ll need to use async repositories for all of your queries, and change your logic around to avoid updating mutable collections from asynchronous actions.

Working with CompletionStage can take a little practice to wrap your head around, so it might be worth searching for some online tutorials to help get a hang of it.