Re: Counter-intuitive behavior of CompleteableFuture
Michał Górniewski via Concurrency-interest <[email protected]> Thu, 20 May 2021 15:28:29 +0200
| Newsgroups | gmane.comp.java.jsr.166-concurrency |
|---|---|
| Message-ID | <CAMFpyQKPunJyRPCbQrNmRp0utx0gy=sYnNdHKNtFg61BpFHy7g@mail.gmail.com> |
I'm still on Java 8, but this is a very good solution :) Thanks, Michał czw., 20 maj 2021 o 15:22 Viktor Klang <[email protected]> napisał(a): > > minimalCompletionStage to the rescue :) > > > public static void main(String[] args) { > CompletableFuture<String> initial = new CompletableFuture<>(); > > CompletionStage<String> withSteps = initial > .minimalCompletionStage <--- This will prevent consumers from having toCompletableFuture returning `this` > .thenApply(s -> { > System.out.println("STEP1: " + s); > return s; > }) > .thenApply(s -> { > System.out.println("STEP2: " + s); > return s; > }); > > CompletionStage<String> timeout = withSteps; > > timeout.whenComplete((s, throwable) -> > System.out.println("TIMEOUT: " + throwable)); > > timeout.completeExceptionally(new RuntimeException("TIMEOUT")); <--- now won't compile > initial.complete("SUCCESS"); > } > > On Thu, May 20, 2021 at 1:14 PM Michał Górniewski <[email protected]> wrote: >> >> This looks good from an API design perspective, since it clearly >> informs users that it really shouldn't call complete() on this Future >> :). >> >> But still, in Java you can do: >> >> timeout.toCompletableFuture().completeExceptionally(new >> RuntimeException("TIMEOUT")); >> >> So toCompletableFuture() would need to create new, depended, >> CompletableStage to be safe. >> >> Best regards, >> Michał >> >> czw., 20 maj 2021 o 14:59 Viktor Klang <[email protected]> napisał(a): >> > >> > A solution which has been used very successfully in Scala is the distinction between Promise (write a single value 0..1 times) and Future (read a single value 0..N times), which means that only the thread who owns a reference to the Promise can write values, which means that Futures can be freely shared since consumers cannot manipulate the result "further up the chain". >> > >> > You can achieve something similar in Java by separating CompletableFuture from CompletionStage. >> > >> > public static void main(String[] args) { >> > CompletableFuture<String> initial = new CompletableFuture<>(); >> > >> > CompletionStage<String> withSteps = initial >> > .thenApply(s -> { >> > System.out.println("STEP1: " + s); >> > return s; >> > }) >> > .thenApply(s -> { >> > System.out.println("STEP2: " + s); >> > return s; >> > }); >> > >> > CompletionStage<String> timeout = withSteps; >> > >> > timeout.whenComplete((s, throwable) -> >> > System.out.println("TIMEOUT: " + throwable)); >> > >> > timeout.completeExceptionally(new RuntimeException("TIMEOUT")); <--- now won't compile >> > initial.complete("SUCCESS"); >> > } >> > >> > >> > On Thu, May 20, 2021 at 12:50 PM Michał Górniewski via Concurrency-interest <[email protected]> wrote: >> >> >> >> What bothers me here is API of CompletableFuture itself, e.g: >> >> >> >> public CompletableFuture<ProcessingOutput> >> >> executeProcessing(Function<Path, ProcessingOutput> processor) { >> >> Path data = prepareData(); >> >> return CompletableFuture.supplyAsync(() -> processor.apply(data), >> >> processingExecutor) >> >> .whenComplete((o, ex) -> cleanup(data)); >> >> } >> >> >> >> Callers of this method may just break cleanup, without even knowing >> >> about this, but simply calling complete() on returned CF. But it seems >> >> that nothing can be done about it. >> >> >> >> czw., 20 maj 2021 o 14:40 Benjamin Manes <[email protected]> napisał(a): >> >> > >> >> > The timeout is applied to the STEP2 future which completes it exceptionally. This causes it to not run the mapping function for a result, as it was completed prior to that being triggered. You can use copy() to decouple the source from the timeout if you want both to complete; e.g. cache a long running value but fail a consumer if taking to long. >> >> > >> >> > On Thu, May 20, 2021 at 5:31 AM Michał Górniewski via Concurrency-interest <[email protected]> wrote: >> >> >> >> >> >> With following code: >> >> >> >> >> >> ------------------------------ >> >> >> public static void main(String[] args) { >> >> >> CompletableFuture<String> initial = new CompletableFuture<>(); >> >> >> >> >> >> CompletableFuture<String> withSteps = initial >> >> >> .thenApply(s -> { >> >> >> System.out.println("STEP1: " + s); >> >> >> return s; >> >> >> }) >> >> >> .thenApply(s -> { >> >> >> System.out.println("STEP2: " + s); >> >> >> return s; >> >> >> }); >> >> >> >> >> >> CompletableFuture<String> timeout = withSteps; >> >> >> >> >> >> timeout.whenComplete((s, throwable) -> >> >> >> System.out.println("TIMEOUT: " + throwable)); >> >> >> >> >> >> timeout.completeExceptionally(new RuntimeException("TIMEOUT")); >> >> >> initial.complete("SUCCESS"); >> >> >> } >> >> >> ------------------------------ >> >> >> >> >> >> I got output like this: >> >> >> >> >> >> ------------------------------ >> >> >> TIMEOUT: java.lang.RuntimeException: TIMEOUT >> >> >> STEP1: SUCCESS >> >> >> ------------------------------ >> >> >> >> >> >> I don't understand why "STEP2" is not executed in this case. Is this expected? >> >> >> For me this API is confusing and may create issues like this: >> >> >> https://github.com/resilience4j/resilience4j/issues/1427 >> >> >> >> >> >> Thanks, >> >> >> Michał Górniewski >> >> >> _______________________________________________ >> >> >> Concurrency-interest mailing list >> >> >> [email protected] >> >> >> http://cs.oswego.edu/mailman/listinfo/concurrency-interest >> >> _______________________________________________ >> >> Concurrency-interest mailing list >> >> [email protected] >> >> http://cs.oswego.edu/mailman/listinfo/concurrency-interest >> > >> > >> > >> > -- >> > Cheers, >> > √ > > > > -- > Cheers, > √ _______________________________________________ Concurrency-interest mailing list [email protected] http://cs.oswego.edu/mailman/listinfo/concurrency-interest