On 11/24/2015 06:38 PM, Martin Buchholz wrote:
Peter: Thanks. I finally got around to thinking about this. Your argument against mutating the source Future is a good one. Even though the extra information will prove useful, you've convinced me that changing the past in this way is going to lead to trouble (like time travel always does in the movies!).

Better behavior seems: always fail with the exception from the failed action, and add the source exception to that as a suppressed exception. This conveys the same information, but with the exceptions trading places. Propagating the source exception in preference to the action exception seems wrong because it's the action's job to handle any Throwable, hence propagation should not be the default. The only downside is that this is slightly less compatible. Any one who was relying on being able to extract the source exception in this case (unlikely!) will still be able to do that, but will need to change their code to get the suppressed exception.


It's a pity that this changes the specification (or at least bends it).

What do you think of exception cloning?

Regards, Peter


On Mon, Nov 23, 2015 at 7:27 AM, Peter Levart <peter.lev...@gmail.com <mailto:peter.lev...@gmail.com>> wrote:

    Hi,



    On 11/23/2015 03:12 PM, Doug Lea wrote:

        On 11/23/2015 04:54 AM, Peter Levart wrote:


            In CompletableFuture.uniWhenComplete method, the possible
            exception thrown from
            BiConsumer action is added as suppressed exception to the
            exception of the
            previous stage. This updated exception is then passed as
            completion result to
            next stage. When previous stage is appended with more than
            one asynchronous
            continuation:

            ...then both secondary exceptions are added as suppressed
            to the same primary
            exception:

            This is not nice for two reasons:

            - Throwable::addSuppressed is not thread-safe
            - The consumer of the result of one CompletableFuture can
            see the exceptional
            result being modified as it observes it.


        Thanks. The minimal solution is to lock, at least avoiding
        conflict
        among multiple whenCompletes:

        !                 else if (x != ex)
        !                     x.addSuppressed(ex);

        --- 771,781 ----

        !                 else if (x != ex) {
        !                     synchronized (x) {
        !                         x.addSuppressed(ex);
        !                     }
        !                 }

        This is not as good a solution as your proposal to add
        Throwable.clone(),
        but we should do this until something like clone is in place.


    Sorry for confusion. I now noticed that Throwable.addSuppressed()
    and getSuppressed() methods are actually synchronized! I don't
    know how I missed that...

    So above patch is not needed. Even printing uses getSuppressed()
    that returns a snapshot. So the only thing remaining is the
    behavior that exceptions can change while they are being observed.
    If this is acceptable then this whole report of mine is just a
    bunch of misinformation.

    Regards, Peter



            When this stage is complete, the given action is invoked
            with the result (or
            null if none) and the exception (or null if none) of this
            stage as arguments.
            The returned stage is completed when the action returns.
            If the supplied action
            itself encounters an exception, then the returned stage
            exceptionally completes
            with this exception unless this stage also completed
            exceptionally."

            Could specification be tweaked a bit? The last statement
            leaves it open to what
            actually happens when "this stage also completes
            exceptionally".


        The looseness was intentional. We'd like to improve
        debuggability of
        implementations without strictly promising a particular form
        in interfaces.
        This is similar to what's done in ForkJoinTask, where we try to
        relay exception causes from other threads, but can't promise
        anything
        beyond a plain exception.

        -Doug




Reply via email to