Re: Counter-intuitive behavior of CompleteableFuture

Michał Górniewski via Concurrency-interest <[email protected]> Thu, 20 May 2021 15:14:22 +0200
Newsgroups gmane.comp.java.jsr.166-concurrency
Message-ID <CAMFpyQ+rjsKovYOAF8b+=jWvRkpJt2tUXwiYN161iQYB2g9qqA@mail.gmail.com>
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,
> √
_______________________________________________
Concurrency-interest mailing list
[email protected]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest