Search code examples
javaconcurrencyguavafuture

Handling exceptions when chaining Futures in guava


Regarding handling exceptions in the following code:

ListenableFuture<BufferedImage> imgFuture = downloadExecutor
                    .submit(new Downloader(url));
            ListenableFuture<BufferedImage> resizeFuture = Futures.transformAsync(
                    imgFuture, new AsyncFunction<BufferedImage, BufferedImage>()
                    {
                        @Override
                        public ListenableFuture<BufferedImage> apply(
                                BufferedImage input) throws Exception
                        {
                            ListenableFuture<BufferedImage> resizedFuture = null;
                            if (input != null)
                            {
                                resizedFuture = actionExecutor
                                        .submit(new ResizeImageAction(input));
                            }
                            return resizedFuture;
                        }
                    });

            ListenableFuture<BufferedImage> grayFuture = Futures
                    .transformAsync(resizeFuture, input -> {
                        return actionExecutor
                                .submit(new ToGrayImageAction(input));
                    });

Assuming that every action sumbitted to an executor can raise an exception, how will this code behave.

Does the transformAsync() method knows to not chain nulls or futures that raised exceptions?? Will using CheckedFuture help me here? If so, how should I use it?


Solution

  • It knows about thrown exception, but doesn't know about nulls because it's perfectly legal value. If exception will be thrown in the first ListenableFuture, then it will be propagated to the all transformers: their onFailure callbacks will be invoked, but not their transformation (because there is no value to transform).

    Javadoc to transformAsync:

    @return A future that holds result of the function (if the input succeeded) or the original input's failure (if not)

    Simple example:

    ListenableFuture<Integer> nullFuture = executor.submit(() -> null);
    ListenableFuture<Integer> exceptionFuture = executor.submit(() -> {
        throw new RuntimeException();
    });
    
    // Replace first argument with exceptionFuture to get another result
    ListenableFuture<Integer> transformer = Futures.transformAsync(nullFuture, i -> {
        System.out.println(i);
        return Futures.immediateCheckedFuture(1);
    }, executor);
    
    Futures.addCallback(transformer, new FutureCallback<Integer>() {
        @Override
        public void onSuccess(Integer result) {
            System.out.println(result);
      }
    
        @Override
        public void onFailure(Throwable t) {
            System.out.println(t);
      }
    });
    

    For the nullFuture "null and 1" will be printed, whereas for exceptionFuture only "java.lang.RuntimeException" will be printed, because exception was propagated to its transformer.