CompletedFuture # whenComplete is not called if thenApply is used

I have the following code (as a result of my previous question ) that schedules a task on a remote server, and then polls to complete using ScheduledExecutorService#scheduleAtFixedRate . Upon completion of the task, it loads the result. I want to return Future caller so that they can decide when and how long to block, and give them the opportunity to cancel the task.

My problem is that if the client cancels the Future returned by the download method, the whenComplete block whenComplete . If I remove thenApply , this will happen. Obviously, I am misunderstanding something about Future ... What should I change?

 public Future<Object> download(Something something) { String jobId = schedule(something); CompletableFuture<String> job = pollForCompletion(jobId); return job.thenApply(this::downloadResult); } private CompletableFuture<String> pollForCompletion(String jobId) { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); CompletableFuture<String> completionFuture = new CompletableFuture<>(); ScheduledFuture<?> checkFuture = executor.scheduleAtFixedRate(() -> { if (pollRemoteServer(jobId).equals("COMPLETE")) { completionFuture.complete(jobId); } }, 0, 10, TimeUnit.SECONDS); completionFuture .whenComplete((result, thrown) -> { System.out.println("XXXXXXXXXXX"); //Never happens unless thenApply is removed checkFuture.cancel(true); executor.shutdown(); }); return completionFuture; } 

In the same note, if I do this:

 return completionFuture.whenComplete(...) 

instead

 completionFuture.whenComplete(...); return completionFuture; 

whenComplete also never executed. This seems to me very controversial. Shouldn't Future be logically returned to whenComplete one I should hold onto?

EDIT:

I changed my code to explicitly cancel the cancellation. This is disgusting and unreadable, but it works, and I could not find a better way:

 public Future<Object> download(Something something) throws ChartDataGenException, Exception { String jobId = schedule(report); CompletableFuture<String> job = pollForCompletion(jobId); CompletableFuture<Object> resulting = job.thenApply(this::download); resulting.whenComplete((result, thrown) -> { if (resulting.isCancelled()) { //the check is not necessary, but communicates the intent better job.cancel(true); } }); return resulting; } 
+5
source share
1 answer

Your structure is as follows:

  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ completionFuture | β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ whenComplete | β”‚ thenApply | β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ 

Therefore, when you cancel the future thenApply , the original completionFuture object remains untouched, since it does not depend on the thenApply stage. If, however, you do not bind the thenApply step, you return the original completionFuture instance, and canceling this step cancels all the dependent steps, as a result of which the whenComplete action should be executed immediately.

But when the thenApply step thenApply canceled, completionFuture can still end when the pollRemoteServer(jobId).equals("COMPLETE") condition is pollRemoteServer(jobId).equals("COMPLETE") , because this poll does not stop. But we do not know the relationship jobId = schedule(something) and pollRemoteServer(jobId) . If your application state changes in such a way that this condition will never be fulfilled after canceling the download, this future will never end ...


As for your last question, in which future β€œshould I stick?”, There is no need to have a linear chain of futures, while the convenient CompletableFuture methods make it easier to create such a chain, more than often, its least useful thing, since you could just write block of code if you have a linear relationship. Your chain model of two independent steps is correct, but cancellation does not work through it, but it will not work through a linear chain.

If you want to cancel the initial stage, you need a link to it, but if you want to get the result of the dependent stage, you will also need a link to this stage.

+4
source

Source: https://habr.com/ru/post/1258805/


All Articles