I know the web is full of advice not to catch Throwables, but does it apply when working with CompleteableFutures? For example, in
ExecutorService es = Executors.newFixedThreadPool(2);
CompletableFuture<String> cf = new CompletableFuture<>();
es.submit(() -> {
try {
cf.complete(getValue());
} catch (Exception e) { // should I catch Throwable instead?
cf.completeExceptionally(e);
}
});
Should I catch Throwable or Exception? If I catch Exception and Error is thrown, program will most likely go into deadlock.
You are right in that not catching Throwable bears the risk of the CompletableFuture never getting completed. So this risk will justify catching Errors resp. all Throwables which you normally wouldn’t.
But since you are basically reinventing supplyAsync, let’s compare the behavior of the two:
public class CF {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> getValue(), es);
try {
cf1.join();
}
catch(CompletionException ex) {
ex.printStackTrace();
}
System.err.println();
CompletableFuture<String> cf2 = new CompletableFuture<>();
es.submit(() -> {
try {
cf2.complete(getValue());
} catch(Throwable t) {
cf2.completeExceptionally(t);
}
});
try {
cf2.join();
}
catch(CompletionException ex) {
ex.printStackTrace();
}
}
private static String getValue() {
throw new OutOfMemoryError();
}
}
java.util.concurrent.CompletionException: java.lang.OutOfMemoryError
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.OutOfMemoryError
at CF.getValue(CF.java:39)
at CF.lambda$main$0(CF.java:11)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
... 3 more
java.util.concurrent.CompletionException: java.lang.OutOfMemoryError
at java.base/java.util.concurrent.CompletableFuture.reportJoin(CompletableFuture.java:412)
at java.base/java.util.concurrent.CompletableFuture.join(CompletableFuture.java:2044)
at CF.main(CF.java:31)
Caused by: java.lang.OutOfMemoryError
at CF.getValue(CF.java:39)
at CF.lambda$main$1(CF.java:24)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
So we see, catching errors is the standard behavior of CompletableFuture. Only the stack traces differ. Let’s see whether we can raise the convergence:
public class CF {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> getValue(), es);
try {
cf1.join();
}
catch(CompletionException ex) {
ex.printStackTrace();
}
System.err.println();
CompletableFuture<String> cf2 = new CompletableFuture<>();
es.submit(() -> {
try {
cf2.complete(getValue());
} catch(Throwable t) {
cf2.completeExceptionally(new CompletionException(t));
}
});
try {
cf2.join();
}
catch(CompletionException ex) {
ex.printStackTrace();
}
}
private static String getValue() {
throw new OutOfMemoryError();
}
}
java.util.concurrent.CompletionException: java.lang.OutOfMemoryError
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1702)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.OutOfMemoryError
at CF.getValue(CF.java:39)
at CF.lambda$main$0(CF.java:11)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
... 3 more
java.util.concurrent.CompletionException: java.lang.OutOfMemoryError
at CF.lambda$main$1(CF.java:26)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.OutOfMemoryError
at CF.getValue(CF.java:39)
at CF.lambda$main$1(CF.java:24)
... 5 more
That’s quiet close.
Of course, if getValue() doesn’t throw checked exceptions, there is no reason not to use CompletableFuture.supplyAsync(() -> getValue(), es);. If we have a reason to implement the completion manually, like having to handle checked exceptions, there are some things we can improve. If we don’t need the Future returned by submit, we can use execute instead, to avoid the creation of an unneeded FutureTask. Further, CompletableFuture marks its completion tasks with AsynchronousCompletionTask to help monitoring and debugging and its useful to follow the convention:
CompletableFuture<String> cf2 = new CompletableFuture<>();
es.execute((Runnable & CompletableFuture.AsynchronousCompletionTask)() -> {
try {
cf2.complete(getValue());
} catch(Throwable t) {
cf2.completeExceptionally(new CompletionException(t));
}
});
There is no direct consequence for the actual behavior though.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With