Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a default completable(Executor) method to the ApiFuture interface to allow conversion to a CompletableFuture, along with a corresponding unit test. The feedback suggests several improvements to make the completable implementation more robust, including unwrapping ExecutionException, restoring the interrupted status on InterruptedException, propagating cancellation back to the underlying future, and adding a null check for the executor. Additionally, the unit test should be simplified using lambdas and updated to avoid swallowing exceptions in the asynchronous block.
| default CompletableFuture<V> completable(Executor executor) { | ||
| CompletableFuture<V> completableFuture = new CompletableFuture<>(); | ||
|
|
||
| addListener(() -> { | ||
| try { | ||
| completableFuture.complete(get()); | ||
| } catch (Exception e) { | ||
| completableFuture.completeExceptionally(e); | ||
| } | ||
| }, executor); | ||
|
|
||
| return completableFuture; | ||
| } |
There was a problem hiding this comment.
There are a few improvements we can make to the completable method to ensure robustness and correctness:
- Exception Unwrapping: When
get()throws anExecutionException, completing theCompletableFuturewith theExecutionExceptionitself leads to double-wrapping (e.g.,CompletionExceptionwrappingExecutionExceptionwrapping the actual cause) when callingjoin()or other monadic operations on theCompletableFuture. We should unwrapExecutionExceptionand complete with its cause. - InterruptedException Handling: If
get()throwsInterruptedException, we should restore the interrupted status of the current thread usingThread.currentThread().interrupt(). - Cancellation Propagation: If the returned
CompletableFutureis cancelled, the cancellation should propagate back to the underlyingApiFutureto release resources or stop the background work. - Null Check: Ensure
executoris not null by usingjava.util.Objects.requireNonNull.
default CompletableFuture<V> completable(Executor executor) {
java.util.Objects.requireNonNull(executor, "executor cannot be null");
CompletableFuture<V> completableFuture = new CompletableFuture<>();
addListener(() -> {
try {
completableFuture.complete(get());
} catch (java.util.concurrent.ExecutionException e) {
completableFuture.completeExceptionally(e.getCause() != null ? e.getCause() : e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
completableFuture.completeExceptionally(e);
} catch (Throwable t) {
completableFuture.completeExceptionally(t);
}
}, executor);
completableFuture.whenComplete((v, t) -> {
if (completableFuture.isCancelled()) {
cancel(true);
}
});
return completableFuture;
}References
- In Java, do not swallow InterruptedException. When catching it, restore the thread's interrupted status by calling Thread.currentThread().interrupt() and handle the interruption appropriately.
| new Executor() { | ||
| @Override | ||
| public void execute(Runnable r) { | ||
| r.run(); | ||
| } | ||
| }).handle( | ||
| new BiFunction<Integer, Throwable, Object>() { | ||
| @Override | ||
| public Object apply(Integer integer, Throwable throwable) { | ||
| flag.set(1); | ||
| return null; | ||
| } | ||
| }); |
There was a problem hiding this comment.
We can simplify the test by using Java 8 lambdas and method references instead of verbose anonymous inner classes. Additionally, ensure that we do not swallow exceptions in the asynchronous block so that any failures are allowed to fail the test.
Runnable::run)
.handle((integer, throwable) -> {
if (throwable != null) {
throw new RuntimeException(throwable);
}
flag.set(1);
return null;
});References
- In test cases, do not swallow exceptions in asynchronous blocks (such as CompletableFuture). Ensure that any exceptions thrown during async execution are allowed to fail the test.
There was a problem hiding this comment.
- I followed the style of the exiting test cases in not using lambdas
- I followed the style of the exiting test case in keeping it simple and not testing for impossible exceptions.
- Added test cases
This makes it easier to integrate
ApiFuture-based calls intoCompletableFuturepipelines.