[concurrency-interest] CompletableFuture improvements

Chris Purcell chris.purcell.39 at gmail.com
Mon Aug 17 14:27:27 EDT 2015


Thanks, Doug!

This seems like a good idea, that we would have done if it had
> crossed anyone's mind!  And it can be done now without any
> loss of compatibility. Pending any near-term objections, I'll
> try incorporating this.


Fantastic, thanks!


> (1) Internally, CompletableFuture's fluent API constructs a work queue of
>> callbacks to execute, to avoid StackOverflowErrors caused by callback
>> recursion, but there is no way to add custom tasks to it.
>>
>
> I think I'm missing the underlying functionality requirement here.
> Can you sketch out some use cases, and how they should be handled


I may be way off-base on *why* CompletableFuture has a work queue of
callbacks. I assumed it was because, if you have a long chain (a couple of
thousand) of dependent futures, and you call dependent callbacks
recursively within the upstream future's completion handler, then the call
stack will overflow. You can easily demonstrate this with
CompletableFutures:

    @Test
    public void recursive_callbacks_overflow() {
        CompletableFuture<Integer> head = new CompletableFuture<>();
        CompletableFuture<Integer> tail = head;
        for (int i = 0; i < 2_000; i++) {

            CompletableFuture<Integer> newTail = new CompletableFuture<>();
            tail.thenAccept(v -> newTail.complete(v + 1));
            tail = newTail;
        }
        head.complete(7);
        // This should return 2_007, but the stack overflows during
        // the recursion, the error is silently discarded, and the
        // downstream futures remain unset.
        assertEquals(-1, (int) tail.getNow(-1));
    }
However, thanks to the way CompletableFuture uses a work stack internally,
if you use the fluent API, this is not an issue even for hundreds of
thousands of dependent stages:

    @Test
    public void a_hundred_thousand_dependent_completion_stages() {
        CompletableFuture<Integer> head = new CompletableFuture<>();
        CompletableFuture<Integer> tail = head;
        for (int i = 0; i < 100_000; i++) {
            tail = tail.thenApply(v -> v + 1);
        }
        head.complete(7);
        assertEquals(100_007, (int) tail.getNow(-1));
    }

Is it merely fortuitous the fluent API has this feature? If so, my
apologies for coming out of left-field :) I assumed it was intentional,
then figured out how to get the first example to work without overflows:

    @Test
    public void
a_hundred_thousand_recursive_style_callbacks_with_MyFuture() {
        MyFuture<Integer> head = new MyFuture<>();
        MyFuture<Integer> tail = head;
        for (int i = 0; i < 100_000; i++) {
            MyFuture<Integer> newTail = new MyFuture<>();
            tail.thenAccept(v -> newTail.complete(v + 1));
            tail = newTail;
        }
        head.complete(7);
        assertEquals(100_007, (int) tail.getNow(-1));
    }
Aside from silly examples like this, calling complete from callbacks is
necessary for things like

   1. wrapping non-CompletableFuture futures, like arbitrary
   CompletionStages, or ListenableFutures
   2. implementing thenCombine to short-circuit when either input fails

It would be great if we could get this functionality in CompletableFuture.

(3) CompletableFuture uses concurrent assistance to execute its callback
>> queue.
>>
>
> Which some users do exploit by creating multiple independent continuation
> tasks. We can't remove this functionality.


I'm a bit surprised this isn't viewed as the job of the executor, but as
you say, it can't be removed now.

Thanks!
Chris
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://cs.oswego.edu/pipermail/concurrency-interest/attachments/20150817/adb14f6c/attachment.html>


More information about the Concurrency-interest mailing list