Adwait Kumar
Adwait Kumar

Reputation: 1604

CompletableFuture.get not waiting for final block

I have a custom ExecutorService,

class CustomExecutorService implements ExecutorService {

        ExecutorService delegate;

        public CustomExecutorService(ExecutorService delegate){
            this.delegate = delegate;
        }
        // All methods are just delegated to the delegate except execute
        
        public void execute(Runnable command) {
            this.delegate.execute(wrapRunnable(command));
        }

        private Runnable wrapRunnable(final Runnable command) {
            return () -> {
                try {
                    command.run();
                } finally {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Finalizer done.");
                }
            };
        }
    }

Now this is my test code,

ExecutorService executorService = new CustomExecutorService(Executors.newCachedThreadPool());
CompletableFuture.runAsync(() -> System.out.println("Command done."), executorService).get();
System.out.println("Main Thread Done.");

Now I expect the following output to happen,

Command done.
Finalizer done. //After a wait of 2 seconds
Main Thread Done.

However what is happening is,

Command done.
Main Thread Done.

And it is not even waiting for the finally block and main thread is exiting. I am not able to explain this behaviour as CompletableFuture#get is supposed to be blocking, but it seems it is not waiting for the finallly block.

I am aware that finally is not guaranteed to be executed if the finally is going to be executed by a daemon thread and all other non-daemon threads exit before finally is called. However ExecutorService threads are non-daemon threads, so finally should be executed.

I am running this on corretto-11.0.11

Upvotes: 0

Views: 1100

Answers (2)

Calum
Calum

Reputation: 5926

When you run CompletableFuture.runAsync it wraps your Runnable in a task (AsyncRun in the source) which runs the runnable, then marks the CompletableFuture as complete. This is what's passed to the executor.

In your executor you are wrapping that task so the future is already marked complete by the time your finally block runs, which is why get() returns immediately after that point.

Upvotes: 4

ZIHAO LIU
ZIHAO LIU

Reputation: 387

Look the source code of CompletableFuture at Line 1627(Jdk 1.8) as shows below:

public void run() {
    CompletableFuture<Void> d; Runnable f;
        if ((d = dep) != null && (f = fn) != null) {
            dep = null; fn = null;
            if (d.result == null) {
                try {
                    f.run();
                    d.completeNull(); // line 1627
                } catch (Throwable ex) {
                    d.completeThrowable(ex);
                }
            }
            d.postComplete();
        }
    }
}

When the Runnable f is done, CompletableFuture's result will be set to null, then the get() method will not be bloked anymore. And in your case Runnable f is equal to System.out.println("Command done.")

You can set a breakpoint at the entry of CompletableFuture runAsync(Runnable runnable, Executor executor) and debug step by step, you will find when the result of CompletableFuture is set.

Upvotes: 2

Related Questions