TransWikia.com

Own ExecutorService used to create CompletableFuture does not terminate

Stack Overflow Asked by daniu on February 2, 2021

I’m trying to use my own ExecutorService to create a set of CompletableFutures to chain several process steps. These steps might throw exceptions.

When they do, it seems to me the thread in the ExecutorService is not released although I’m trying to handle this case.

class Scratch {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        AtomicInteger counter = new AtomicInteger();
        Supplier<?> throwingException = () -> { throw new RuntimeException("throw " + counter.incrementAndGet()); };
        Function<String, CompletableFuture<?>> process =
                url -> CompletableFuture.supplyAsync(throwingException, executor)
                        .exceptionally(Scratch::log);
        var collect = IntStream.range(1, 10).mapToObj(i -> "url" + i)
                .map(process)
                .toArray(CompletableFuture[]::new);
        final CompletableFuture<Void> together = CompletableFuture.allOf(collect);
        System.out.println("joining");
        together.exceptionally(Scratch::log).join();
        System.out.println("finished");
        if (executor.awaitTermination(5, TimeUnit.SECONDS)) {
            System.out.println("exiting cleanly");
        } else {
            System.out.println("not terminated");
        }
        executor.submit(() -> System.out.println("still executing"));
    }
    static <T> T log(Throwable t) {
        System.out.println(t.getMessage());
        return null;
    }
}

Output is

java.lang.RuntimeException: throw 1
joining
java.lang.RuntimeException: throw 2
java.lang.RuntimeException: throw 3
java.lang.RuntimeException: throw 4
java.lang.RuntimeException: throw 5
java.lang.RuntimeException: throw 6
java.lang.RuntimeException: throw 7
java.lang.RuntimeException: throw 8
java.lang.RuntimeException: throw 9
finished
not terminated

The process started by this also isn’t terminated (which is how I noticed).

It seems to me this should mean there are no threads left in the ExecutorService at this point, but that doesn’t seem to be the case; if we lower the thread pool capacity, it will still run all submitted tasks, and if we add submit another after the failed termination (eg executor.submit(() -> System.out.println("still executing"));), it will get executed.

If we don’t pass our own ExecutorService to the CompletableFutre::supplyAsync, the process will terminate as expected.

I also tried other versions of handling the exceptional state (like using together.whenComplete()), but that has the same result.

Why is this happening, and how can I make sure the ExecutorService terminates correctly?

EDIT: I realized that it’s not the exception that’s causing the problem, this will occur with any task provided to CompletableFuture with your own executor service, which makes total sense given Eugene’s reply. I’m changing the question title.

One Answer

There are two things going on here. First one is that when you execute without an explicit Executor, your actions run in the ForkJoinPool. That pool uses daemon-threads, which do not stop the VM to exit. So when your main is over, VM exists.

The second point is in the documentation of awaitTermination, actually:

Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.

Since you did not call shutDown and that pool creates non-daemon threads, the process does not exit.

Answered by Eugene on February 2, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP