CompletionStage.exceptionallyCompose

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list

Chris Hegarty noticed a nonuniformity in CompletionStage
(and CompletableFuture) APIs: There is no analog to method
exceptionally() for compose-based stages.

Reminder: method exceptionally() is a more useful and
convenient form of method handle() for cases in which
the action is a no-op unless the source completed
exceptionally:

    public CompletionStage<T> exceptionally
        (Function<Throwable, ? extends T> fn);

This could also be provided with a function type returning
another CompletionStage, not a value:

  public CompletionStage<T> exceptionallyCompose
          (Function<Throwable, ? extends CompletionStage<T>> fn);

Not supporting this for compose-based cases forces ugly/awkward
workarounds, so it should be added for the same reason as method
exceptionally()

It is possible to default-implement this is CompletionStage, with
a better implementation in CompletableFuture.

The main moral is that with fluent APIs, you always eventually
end up supporting all possible combinations of capabilities.
We somehow missed this one when adding methods in jdk9 update.
Any thoughts about this or other cases would be welcome.

-Doug
_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest
Reply | Threaded
Open this post in threaded view
|

Re: CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list
Hi.

This sounds good. Will it be available with exceptionallyComposeAsync(Function [, Executor]) format as well?

Any thoughts about this or other cases would be welcome.

I could also think of:

    // if the source completes or fails, allow mapping it to another stage
    CompletionStage<U> handleCompose(BiFunction<T, Throwable, CompletionStage<U>> handler)

    // signal the completion or error on the specified executor
    CompletionStage<T> thenAsync(Executor executor)


In addition, I could think of a bunch of static combinators (sorry if they are already there somewhere else):

    // if any of the stages completes, consume its value and ignore the others
    static CompletionStage<Void> acceptWhenAny(Stream<CompletionStage<T>> stages, Consumer<? super T> handler)

    // if any of the stages completes, transform its result and ignore the others
    static CompletionStage<U> applyWhenAny(Stream<CompletionStage<T>> stages, Function<? super T, U> handler)

    // when all of the stages complete, provide the consumer with a list of values gathered
    static CompletionStage<Void> thenCombineAll(Stream<CompletionStage<T>> stages, Consumer<? super List<T>> handler)

    // when all stages complete, transform the list of results into some other value
    static CompletionStage<U> thenCombineAll(Stream<CompletionStage<T>> stages, Function<? super List<T>, U> handler)

    // when all of the stages complete or fail, provide the success values and the error values to a handler
    static CompletionStage<Void> whenCompleteAll(Stream<CompletionStage<T>> stages, BiConsumer<? super List<T>, ? super List<Throwable>> handler)



2018-03-30 13:52 GMT+02:00 Doug Lea via Concurrency-interest <[hidden email]>:

Chris Hegarty noticed a nonuniformity in CompletionStage
(and CompletableFuture) APIs: There is no analog to method
exceptionally() for compose-based stages.

Reminder: method exceptionally() is a more useful and
convenient form of method handle() for cases in which
the action is a no-op unless the source completed
exceptionally:

    public CompletionStage<T> exceptionally
        (Function<Throwable, ? extends T> fn);

This could also be provided with a function type returning
another CompletionStage, not a value:

  public CompletionStage<T> exceptionallyCompose
          (Function<Throwable, ? extends CompletionStage<T>> fn);

Not supporting this for compose-based cases forces ugly/awkward
workarounds, so it should be added for the same reason as method
exceptionally()

It is possible to default-implement this is CompletionStage, with
a better implementation in CompletableFuture.

The main moral is that with fluent APIs, you always eventually
end up supporting all possible combinations of capabilities.
We somehow missed this one when adding methods in jdk9 update.
Any thoughts about this or other cases would be welcome.

-Doug
_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest



--
Best regards,
David Karnok

_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest
Reply | Threaded
Open this post in threaded view
|

Re: CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list
In reply to this post by JSR166 Concurrency mailing list
"you always eventually end up supporting all possible combinations of
capabilities"
This means that one should always try to factorize combinations of
capabilities.
The description of CompletionStage  reads:
A stage of a possibly asynchronous computation, that performs an action or
computes a value when another CompletionStage completes.
This means that designers of CompletionStage suppose that actions of
asynchronous computation are connected directly, and an action is performed
when another action completes.
First, this approach restricts parallelism. Second, it does not takes into
account Petri Net computational model, where actions (transitions) are not
connected directly, but via places for values (tokens). This means, that
instead of considering all possible combinations of action-action
interactions, we factorize them in action-place and place-action
interactions.
Action-place interaction is simple: actions just puts a value (or error
token) into place like in any other collection. It need not to bother to
make this in async way. Place-action interaction is slightly more complex,
but still simple enough: action fires when all places are full. This is pure
asynchronous procedure call, where tokens in places are considered as
procedure arguments. Whether action executes in sync or async way, is
defined by the author of the action's code: only he knows if overhead of
async execution is worth the overhead and delay.
So I consider CompletionStage flawed by design, and instead of fixing it, it
should be deprecated in favor of a bipartite execution model.



--
Sent from: http://jsr166-concurrency.10961.n7.nabble.com/
_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest
Reply | Threaded
Open this post in threaded view
|

Re: CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list
Hi Alexei,

What you describe as "bipartite execution model" is exactly what CompletableFuture (the implementation of CompletionStage) is about. At least I see an analogue to your description...

You describe an "action-place" interaction as: "actions just puts a value (or error token) into place like in any other collection".  A CompletableFuture is a "place" in your terminology. It might already be "fused" with an action or not. Simply constructing new CompletableFuture just creates a "place". When you say CompletableFuture.supply[Async](Supplier [, Executor]), you create a place which is already fused with an action.

You also say "It need not to bother to make this in async way". Well, you have to create an initial action somehow. How you execute an initial action from graph-of-action-places constructing thread should be decidable by the user - synchronously or asynchronously. This has nothing to do with how an action-place interaction is performed. In CompletableFuture it is always performed synchronously, as you describe. After completion, the action just puts the value/exception into the place.

What you describe in "place-action" interaction is also exactly what CompletableFuture does: "action fires when all places are full". What CompletableFuture mostly offers are two special cases of this: "action fires when the place is full" and "action fires when both places are full". CF mostly assumes that the action has a single or two "procedure arguments". With .then[Accept|Apply][Async] you attach an action to a place and decide whether this "next" action should execute synchronously or asynchronously. With .then[AcceptBoth|Combine][Async] you bring two places into play and a single tow-argument action triggered when both are full. When I say that CF mostly offers two special cases of "place-action" interaction, I'm referring to the fluent part of its API. You can take N places and combine them into one "compound" place with static method .allOf(CF ...) which you than can use to attach an action further down the chain. There are all sorts of other methods. I just mentioned those that are needed to implement you desctibed "bipartite execution model".

Speaking of CF.allOf(...) I wonder why its signature wasn't constructed as following:

    public static <T> CompletableFuture<T[]> allOf(CompletableFuture<? extends T>... cfs)

By analogy, the signature of CF.anyOf(...) could be the following:

    public static <T> CompletableFuture<T> anyOf(CompletableFuture<? extends T>... cfs)


I wonder if this could be compatibly changed.



Regards, Peter

On 03/30/18 20:03, Alexei Kaigorodov via Concurrency-interest wrote:
"you always eventually end up supporting all possible combinations of
capabilities"
This means that one should always try to factorize combinations of
capabilities.
The description of CompletionStage  reads:
A stage of a possibly asynchronous computation, that performs an action or
computes a value when another CompletionStage completes.
This means that designers of CompletionStage suppose that actions of
asynchronous computation are connected directly, and an action is performed
when another action completes.
First, this approach restricts parallelism. Second, it does not takes into
account Petri Net computational model, where actions (transitions) are not
connected directly, but via places for values (tokens). This means, that
instead of considering all possible combinations of action-action
interactions, we factorize them in action-place and place-action
interactions.
Action-place interaction is simple: actions just puts a value (or error
token) into place like in any other collection. It need not to bother to
make this in async way. Place-action interaction is slightly more complex,
but still simple enough: action fires when all places are full. This is pure
asynchronous procedure call, where tokens in places are considered as
procedure arguments. Whether action executes in sync or async way, is
defined by the author of the action's code: only he knows if overhead of
async execution is worth the overhead and delay.
So I consider CompletionStage flawed by design, and instead of fixing it, it
should be deprecated in favor of a bipartite execution model.



--
Sent from: http://jsr166-concurrency.10961.n7.nabble.com/
_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest


_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest
Reply | Threaded
Open this post in threaded view
|

Re: CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list
On Sat, March 31, 2018 7:59 am, Peter Levart via Concurrency-interest wrote:

>
> Speaking of CF.allOf(...) I wonder why its signature wasn't constructed
> as following:
>
>  public static <T> CompletableFuture<T[]> allOf(CompletableFuture<?
extends T>... cfs)
>
> By analogy, the signature of CF.anyOf(...) could be the following:
>
> public static <T> CompletableFuture<T> anyOf(CompletableFuture<? extends
T>... cfs)
>
>
> I wonder if this could be compatibly changed.
>

These were done this way in part due to experience with
ExecutorService.invokeAll, where the signature  was once
more-or-less compatibly changed once. I think the main
issue is that supplying only one form of any/all methods
can't satisfy enough users. This might be a good time to
contemplate additions.

-Doug



_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest
Reply | Threaded
Open this post in threaded view
|

Re: CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list
In reply to this post by JSR166 Concurrency mailing list
Hi David,

On 03/30/18 14:45, Dávid Karnok via Concurrency-interest wrote:
Hi.

This sounds good. Will it be available with exceptionallyComposeAsync(Function [, Executor]) format as well?

Any thoughts about this or other cases would be welcome.

I could also think of:

    // if the source completes or fails, allow mapping it to another stage
    CompletionStage<U> handleCompose(BiFunction<T, Throwable, CompletionStage<U>> handler)

    // signal the completion or error on the specified executor
    CompletionStage<T> thenAsync(Executor executor)

This one is a shortcut for thenApplyAsync(Function.identity(), executor). This is sometimes useful (even with default executor) when you have to return a CompletionStage and don't want the receiver of that CS to use the thread(s) that are completing your original CS.

Even better would be a way to hand-out a special "restricted" CompletionStage (a super-type of it?) that would limit the user to async-only methods. This way you force user to decide which threads to use and those are not your threads. Useful on the API boundary when you want to restrict usage of your internal threads to your internal tasks.



In addition, I could think of a bunch of static combinators (sorry if they are already there somewhere else):

    // if any of the stages completes, consume its value and ignore the others
    static CompletionStage<Void> acceptWhenAny(Stream<CompletionStage<T>> stages, Consumer<? super T> handler)

This would be equivalent to:

   
CompletableFuture.anyOf(toCompletableFutures(stages)).thenAccept(handler);

If:

-
CompletableFuture.anyOf signature was:

    public static <T> CompletableFuture<T> anyOf(CompletableFuture<? extends T>... cfs)

- and you had this utility method:

    @SuppressWarnings("unchecked")
    static <T> CompletableFuture<T>[] toCompletableFutures(Stream<CompletionStage<T>> stages) {
        return (CompletableFuture<T>[]) stages.map(CompletionStage::toCompletableFuture).toArray(CompletableFuture[]::new);
    }



    // if any of the stages completes, transform its result and ignore the others
    static CompletionStage<U> applyWhenAny(Stream<CompletionStage<T>> stages, Function<? super T, U> handler)

    // when all of the stages complete, provide the consumer with a list of values gathered
    static CompletionStage<Void> thenCombineAll(Stream<CompletionStage<T>> stages, Consumer<? super List<T>> handler)

    // when all stages complete, transform the list of results into some other value
    static CompletionStage<U> thenCombineAll(Stream<CompletionStage<T>> stages, Function<? super List<T>, U> handler)

    // when all of the stages complete or fail, provide the success values and the error values to a handler
    static CompletionStage<Void> whenCompleteAll(Stream<CompletionStage<T>> stages, BiConsumer<? super List<T>, ? super List<Throwable>> handler)

I think it would be better to improve (make it type-safe) the way multiple CompletableFutures are combined into one "compound" CompletableFuture which can the be used to attach actions to in the usual way. Less method(s) are needed that way.

Regards, Peter




2018-03-30 13:52 GMT+02:00 Doug Lea via Concurrency-interest <[hidden email]>:

Chris Hegarty noticed a nonuniformity in CompletionStage
(and CompletableFuture) APIs: There is no analog to method
exceptionally() for compose-based stages.

Reminder: method exceptionally() is a more useful and
convenient form of method handle() for cases in which
the action is a no-op unless the source completed
exceptionally:

    public CompletionStage<T> exceptionally
        (Function<Throwable, ? extends T> fn);

This could also be provided with a function type returning
another CompletionStage, not a value:

  public CompletionStage<T> exceptionallyCompose
          (Function<Throwable, ? extends CompletionStage<T>> fn);

Not supporting this for compose-based cases forces ugly/awkward
workarounds, so it should be added for the same reason as method
exceptionally()

It is possible to default-implement this is CompletionStage, with
a better implementation in CompletableFuture.

The main moral is that with fluent APIs, you always eventually
end up supporting all possible combinations of capabilities.
We somehow missed this one when adding methods in jdk9 update.
Any thoughts about this or other cases would be welcome.

-Doug
_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest



--
Best regards,
David Karnok


_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest


_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest
Reply | Threaded
Open this post in threaded view
|

Re: CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list
In reply to this post by JSR166 Concurrency mailing list
Peter,
you said CompletableFuture is a place. Then what is a transition? it is not
defined explicitly. I can only guess that when I write

CompletableFuture next = this.thenCombine(otherFuture, bifunction);

method thenCombine() is internally interpreted something like:

CompletableFuture out = new CompletableFuture();
BiTransition trans = new BiTransition(bifunction, out);
this.thenAccept(trans.firstArg);
otherFuture.thenAccept(trans.secondArg);
return out;

Evidently, when API exposes only such complex procedures, it cannot cover
all possible combinations of capabilities.

So I want that type Transition should be explicitly exposed as a first-class
citizen along with CompletableFuture, and I could freely compose an
execution graph out of transitions and places. Now numerous methods of
CompletableFuture still cannot cover all the needs of a programmer. For
example, I cannot create a transition with 3 input arguments. I cannot
create a transition with more than one output. This is because the execution
model of CompletableFuture is not properly factorized.

I cannot also create a reusable transition, which fires each time when all
input places get filled. Akka's actor is an example of such a reusable
transition, but again it is restricted: it has only 2 inputs, one for the
state and another for a message.

Reusable transitions require also reusable places. We could consider
java.util.stream.Stream as such a reusable place, but the whole
java.util.stream design demonstrates the same linearity of thinking, where
pipeline consists of homogeneous stages, and not of distinct places and
transitions. As a result, an execution graph of arbitrary topology cannot be
declared.



--
Sent from: http://jsr166-concurrency.10961.n7.nabble.com/
_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest
Reply | Threaded
Open this post in threaded view
|

Re: CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list
In reply to this post by JSR166 Concurrency mailing list
FWIW, Guava has both catching() and catchingAsync(), which are basically exceptionally() and exceptionallyCompose(). In our depot, we see about 2 calls to catching() for every 1 call to catchingAsync(). Naturally, results will vary for different codebases with different requirements.

(OK, actually, we see about an equal number of calls to both. But oddly, we had catchingAsync() before we had catching(), so I'm subtracting out the number of catchingAsync() calls that existed before we added catching(). Since some of those calls might have been deleted (or converted to catching() calls), that might artificially reduce the number of catchingAsync() calls. But I feel comfortable saying that our ratio is between 2:1 and 1:1.)

(Sorry, I haven't read the rest of the thread. I can try to put together some data+thoughts on allOf() and friends if it would be helpful.)

_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest
Reply | Threaded
Open this post in threaded view
|

Re: CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list
Just wanted to add my vote to adding exceptionallyCompose, and also handleCompose would also be good. I've come across numerous times when I've needed these abstractions on CompletionStage.

On 7 April 2018 at 04:32, Chris Povirk via Concurrency-interest <[hidden email]> wrote:
FWIW, Guava has both catching() and catchingAsync(), which are basically exceptionally() and exceptionallyCompose(). In our depot, we see about 2 calls to catching() for every 1 call to catchingAsync(). Naturally, results will vary for different codebases with different requirements.

(OK, actually, we see about an equal number of calls to both. But oddly, we had catchingAsync() before we had catching(), so I'm subtracting out the number of catchingAsync() calls that existed before we added catching(). Since some of those calls might have been deleted (or converted to catching() calls), that might artificially reduce the number of catchingAsync() calls. But I feel comfortable saying that our ratio is between 2:1 and 1:1.)

(Sorry, I haven't read the rest of the thread. I can try to put together some data+thoughts on allOf() and friends if it would be helpful.)

_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest




--
James Roper
Senior Octonaut

Lightbend – Build reactive apps!
Twitter: @jroper


_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest
Reply | Threaded
Open this post in threaded view
|

Re: CompletionStage.exceptionallyCompose

JSR166 Concurrency mailing list
In reply to this post by JSR166 Concurrency mailing list
No, sorry, I forgot: The common reason that people use catchingAsync() is not to return a Future. Instead, they use it to be able to throw a checked exception from within apply() itself. (Background: For practical and historical reasons, Guava's catching() uses Function, which can't throw, but catchingAsync() uses AsyncFunction, which can.)

I had previously calculated that only 15% of our catching()/catchingAsync() users need to return a Future. But apparently just as many other users don't need to return a Future yet do use catchingAsync() so that they can throw a checked exception.

So, for exceptionally() and exceptionallyCompose(), Google's depot suggests the usage ratio would be more like 6:1 than the 2:1 or 1:1 I claimed above (again, for "codebases like Google's," whatever that means).

_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest