[concurrency-interest] Layered exception handling with CompletableFuture

Millies, Sebastian Sebastian.Millies at softwareag.com
Thu Aug 28 15:13:55 EDT 2014


Hello Peter,

thank you for this excellent post! I now understand how layered exception handling works with CompletableFutures. The key point I wasn’t aware of is that exceptionally() can just pass on CompletionExceptions.

I do not need the SupplierX interface, I just use Callable, and I call the utility toSupplier(), which is a bit more specific. Given that CompletableFuture already does all this exception magic, I really don’t understand why there is no supplyAsync(Callable) method in the standard interface.

I like unwrapWrap (again except for its name, perhaps call it unwrapX, or unwrapCE ?). I particularly like the try-catch-idiom using unwrap inside exceptionally(), and the neat way it avoids peppering instanceof all over the place. Also nice is that the interface FunctionX can remain private in a utility class exposing these static helper methods.

Sebastian


From: Peter Levart [mailto:peter.levart at gmail.com]
Sent: Wednesday, August 27, 2014 3:56 PM
To: Millies, Sebastian; concurrency-interest at cs.oswego.edu
Subject: Re: [concurrency-interest] Layered exception handling with CompletableFuture

On 08/27/2014 10:39 AM, Millies, Sebastian wrote:

So I find myself wrapping all the Callables, and tunneling the exceptions with RuntimeException, as Doug Lea recommends.

The interesting part of javadoc for CompletionStage is the following:

- Two method forms support processing whether the triggering stage completed normally or exceptionally: Method whenComplete allows injection of an action regardless of outcome, otherwise preserving the outcome in its completion. Method handle additionally allows the stage to compute a replacement result that may enable further processing by other dependent stages. In all other cases, if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause.

So If you do the following:

        CompletableFuture.supplyAsync(() -> {
            throw new IllegalStateException();
        }).whenComplete((x, t) -> {
            System.out.println("Result: " + x + "\nException: " + t);
        });

...you will get:

Result: null
Exception: java.util.concurrent.CompletionException: java.lang.IllegalStateException

So if stage funcition/supplier throws an (unchecked) exception, it is always wrapped with CompletionException as stage outcome. The signature of exception handling functions passed to CompletionStage methods declares plain Throwable, but at least CompletableFuture seems to always arange so that CompletionException is passed to those functions unless you call methods like completeExceptionally on it. You can use this to your advantage (will get to that shortly)...

Another "implementation detail" of CompletableFuture is evident if you try this:

        CompletableFuture.supplyAsync(() -> {
            throw new CompletionException(new Exception("I'm checked exception"));
        }).whenComplete((x, t) -> {
            System.out.println("Result: " + x + "\nException: " + t);
        });

...you will get:

Result: null
Exception: java.util.concurrent.CompletionException: java.lang.Exception: I'm checked exception

So if stage funcition/supplier throws CompletionException, it is not wrapped with another CompletionException as the stage outcome but it becomes the stage outcome directly.

Finally the following "implementation detail" of CompletableFuture is also useful for our purpose. If you try:

        try {
            CompletableFuture.supplyAsync(() -> {
                throw new CompletionException(new Exception("I'm checked exception"));
            }).get();
        } catch (ExecutionException | InterruptedException e) {
            System.out.println("Got: " + e);
        }

...you will get:

Got: java.util.concurrent.ExecutionException: java.lang.Exception: I'm checked exception

So if CompletableFuture outcome is CompletionException wrapping some Throwable, then calling get() on the future will throw ExecutionException wrapping this same Throwable. The cause of CompletionException is effectively unwrapped before being wrapped with ExecutionException in get().

This behaviour lends itself to the use of CompletionException as a general wrapper for any exception (checked or unchecked) thrown by the stage function/supplier.

You can create helper functional interfaces:

    interface SupplierX<T> {
        T get() throws Throwable;
    }

    interface FunctionX<T, R> {
        R apply(T t) throws Throwable;
    }

    // ... etc ... and utility static methods:

    static <T> Supplier<T> wrap(SupplierX<T> supplier) {
        return () -> {
            try {
                return supplier.get();
            } catch (Throwable e) {
                throw new CompletionException(e);
            }
        };
    }

    static <R> Function<Throwable, R> unwrapWrap(FunctionX<Throwable, R> fn) {
        return (throwable) -> {
            try {
                return fn.apply(throwable instanceof CompletionException
                                ? throwable.getCause()
                                : throwable);
            } catch (Throwable e) {
                throw new CompletionException(e);
            }
        };
    }

    // ... etc.


And then use them as following:


        CompletableFuture<String> cf = CompletableFuture.supplyAsync(wrap(
            () -> {
                double r = Math.random();
                if (r < 1d / 3d) return "OK";
                else if (r < 2d / 3d) throw new FileNotFoundException();
                else throw new EOFException();
            }
        ));

        cf = cf.exceptionally(unwrapWrap(
            (t) -> {
                try {
                    throw t;
                } catch (IOException e) {
                    throw new IllegalStateException("Converted from: " + e);
                }
            }
        ));

        cf = cf.exceptionally(unwrapWrap(
            (t) -> {
                try {
                    throw t;
                } catch (RuntimeException e) {
                    return "Handled: " + e;
                }
            }
        ));

        System.out.println(cf.get());



Regards, Peter

Software AG – Sitz/Registered office: Uhlandstraße 12, 64297 Darmstadt, Germany – Registergericht/Commercial register: Darmstadt HRB 1562 - Vorstand/Management Board: Karl-Heinz Streibich (Vorsitzender/Chairman), Dr. Wolfram Jost, Arnd Zinnhardt; - Aufsichtsratsvorsitzender/Chairman of the Supervisory Board: Dr. Andreas Bereczky - http://www.softwareag.com

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://cs.oswego.edu/pipermail/concurrency-interest/attachments/20140828/dc6ef3a6/attachment-0001.html>


More information about the Concurrency-interest mailing list