CompletableFuture chaining with exception handling - java

I am trying to compose a chain of steps such that I can avoid a large nested chain of if and else calls by creating methods that return CompletableFuture<Boolean> in a manner such as....
client.connect(identifier).thenCompose(b -> client.authenticate())
.thenCompose(b -> client.sendSetting(settings))
.thenCompose(b -> client.saveSettings())
.thenCompose(b -> client.sendKey(key))
.thenCompose(b -> client.setBypassMode(true))
.thenCompose(b -> client.start())
.whenComplete((success, ex) -> {
if(ex == null) {
System.out.println("Yay");
} else {
System.out.println("Nay");
}
});
If the client methods return a CompletableFuture<Boolean> deciding whether to continue processing has to be done in each lambda in the chain and doesn't provide a method to abort early if one of the calls fail. I would rather have the calls return CompletableFuture<Void> and use Exceptions to control if 1) each successive step in the chain executes and 2) final determination of success of the full chain.
I am having trouble finding which method on CompletableFuture<Void> to swap for thenCompose to make things work (let alone compile).

public class FutureChaings {
public static CompletableFuture<Void> op(boolean fail) {
CompletableFuture<Void> future = new CompletableFuture<Void>();
System.out.println("op");
Executors.newScheduledThreadPool(1).schedule(() -> {
if(fail) {
future.completeExceptionally(new Exception());
}
future.complete(null);
}, 1, TimeUnit.SECONDS);
return future;
}
public static void main(String[] args) {
op(false).thenCompose(b -> op(false)).thenCompose(b -> op(true)).whenComplete((b, ex) -> {
if(ex != null) {
System.out.println("fail");
} else {
System.out.println("success");
}
});
}
}
I was able to contrive an example that behaved the way I wanted. So I know what calls to put together to get what I want. Now to figure out what the compiler doesn't like in my real code. Thanks for the comments.

Related

CompletableFuture.runAsync Swallowing Exceptions

Good morning,
I'm not quite getting the hang of CompletableFutures (I am an experienced developer, but I am not finding them particularly intuitive!).
Given the following snippet:
public CompletionStage<Void> leaveGame(GameService gameService)
{
return gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
});
}
which is called by the unit test:
#Test
public void shouldCompleteExceptionallyForFailedLeave()
{
var failFlow = new CompletableFuture<Void>();
failFlow.completeExceptionally(new Exception("TestNonExistentPlayer"));
when(mockedGameService.deregister(any(String.class))).thenReturn(failFlow);
try
{
player.leaveGame(mockedGameService).toCompletableFuture().get();
fail("Exception should have been thrown");
}
catch (Exception e)
{
assertEquals(Exception.class, e.getCause().getClass());
}
verify(mockedGameService, times(1)).deregister(any(String.class));
}
which mocks gameService.deregister(...) to completeExceptionally and return Exception.
In the above case, as expected, the exceptionally branch is triggered, the message is logged, and the exception in the unit test is caught, i.e. the fail(...) assertion is not triggered.
However, when I want to run a CompletionStage before leave game, e.g.:
public CompletionStage<Void> leaveGame(GameService gameService)
{
return CompletableFuture.runAsync(() -> System.out.println("BLAH"))
.thenRun(() -> gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
}));
}
The exceptionally branch is still triggered, but the exception is now not caught by the test method, i.e. the fail(...) assertion is triggered.
What am I doing wrong?
Many thanks in advance!
With your original definition
public CompletionStage<Void> leaveGame(GameService gameService)
{
return gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
});
}
The method leaveGame did not throw an exception but always returned a future. The caller has to examine the future to find out whether the encapsulated operation has failed.
Likewise when you move the same code into a Runnable like
public CompletionStage<Void> leaveGame(GameService gameService)
{
return CompletableFuture.runAsync(() -> System.out.println("BLAH"))
.thenRun(() -> gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
}));
}
The Runnable will not throw an exception. It’s still required to examine the future returned by gameService.deregister(…).exceptionally(…) to find out whether it failed, but now, you are not returning it but just dropping the reference.
To create a future whose completion depends on a future returned by a function evaluation, you need thenCompose:
public CompletionStage<Void> leaveGame(GameService gameService)
{
return CompletableFuture.runAsync(() -> System.out.println("BLAH"))
.thenCompose(voidArg -> gameService.deregister(playerName)
.exceptionally(t -> {
LOGGER.info("Could not deregister: {}", t.getMessage());
throw new CompletionException(t);
}));
}
So now you’re implementing a Function<Void,CompletionStage<Void>> rather than Runnable and the stage returned by the function will be use to complete the future returned by leaveGame.

Run whenComplete of CompletableFuture in original thread

How can I run whenComplete of the CompletableFuture in the original thread that CompletableFuture was created in?
// main thread
CompletableFuture
.supplyAsync(() -> {
// some logic here
return null;
}, testExecutorService);
.whenComplete(new BiConsumer<Void, Throwable>() {
#Override
public void accept(Void aVoid, Throwable throwable) {
// run this in the "main" thread
}
});
Expanded example for JavaFx:
button.setOnClick((evt) -> {
// the handler of the click event is called by the GUI-Thread
button.setEnabled(false);
CompletableFuture.supplyAsync(() -> {
// some logic here (runs outside of GUI-Thread)
return something;
}, testExecutorService);
.whenComplete((Object result, Throwable ex) -> {
// this part also runs outside the GUI-Thread
if (exception != null) {
// something went wrong, handle the exception
Platform.runLater(() -> {
// ensure we update the GUI only on the GUI-Thread
label.setText(ex.getMessage());
});
} else {
// job finished successfull, lets use the result
Platform.runLater(() -> {
label.setText("Done");
});
}
Platform.runLater(() -> {
button.setEnabled(true); // lets try again if needed
});
});
});
This is not the best code you could write in this situation, but it should bring the point across.

How do I run a CompletableFuture handler on the Vertx event loop thread?

I have a library xyz that gives me a CompletableFuture which I want to process on my Vertx (v3.5) event loop. Currently I am using CompletableFuture.handle(BiFunction) (see below) but I would like to use CompletableFuture.handleAsync(BiFunction, Executor), but I cannot figure out how to provide the vertx event loop thread to the second parameter in this method call.
I tried executing this whole code in Vertx.runOnContext() but the calls inside handleAsync still executed on Java's ForkJoin pool which I want to avoid.
CompletableFuture<Void> f = xyz.someMethod();
f.handle((v, th) -> { //Want to run this in handleAsync()
if (th == null) {
future.complete(null);
} else {
future.completeExceptionally(th);
}
return null;
});
You can achieve this by simply using vertx.nettyEventLoopGroup() as the second parameter, so your code will be something like this:
CompletableFuture<Void> f = xyz.someMethod();
f.handleAsync((v, th) -> {
if (th == null) {
future.complete(null);
} else {
future.completeExceptionally(th);
}
return null;
}, vertx.nettyEventLoopGroup());
Note: the above code will may run the callback code not on the same thread the Vertx future was running.
To preserve the threading model of the Vertx you need to use the following code:
CompletableFuture<String> f = xyz.someMethod();
Context context = vertx.getOrCreateContext();
f.handleAsync((v, th) -> {
context.runOnContext(event -> {
if (th == null) {
future.complete(null);
} else {
future.completeExceptionally(th);
}
});
return null;
});
This can be done the following way. It is a workaround, not an optimal solution. Initially I placed the runOnContext call outside handle method, which is why it did not work.
CompletableFuture<Void> f = xyz.someMethod();
f.handle((v, th) -> {
vertx.runOnContext(e->{
if (th == null) {
future.complete(null);
} else {
future.completeExceptionally(th);
}
});
return null;
});
This will make one extra context switch (from ForkJoin pool to vertx event loop) which is one disadvantage of this approach.

Try & Catch When Calling supplyAsync

I am new to CompletableFuture, I will like to call a method MetadataLoginUtil::login which can throw an exception. However, the code below is not compiled although I have 'exceptionally' written. It says that I must wrap the MetadataLoginUtil::login' within try & catch.
Please advise.
Thanks ahead !
public void run() throws ConnectionException {
CompletableFuture<Void> mt = CompletableFuture.supplyAsync(MetadataLoginUtil::login)
.exceptionally(e -> {
System.out.println(e);
return null;
})
.thenAccept(e -> System.out.println(e));
}
This is not a deficiency of how CompletableFuture works in general, but of the convenience methods, all using functional interfaces not allowing checked exceptions. You can solve this with an alternative supplyAsync method:
public static <T> CompletableFuture<T> supplyAsync(Callable<T> c) {
CompletableFuture<T> f=new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
try { f.complete(c.call()); } catch(Throwable t) { f.completeExceptionally(t); }
});
return f;
}
This is basically doing the same as the original supplyAsync, but allowing checked exceptions. So you can use it right like in your original attempt, only redirecting the initial supplyAsync call.
CompletableFuture<Void> mt = supplyAsync(MetadataLoginUtil::login)
.exceptionally(e -> { System.out.println(e); return null; } )
.thenAccept(e -> System.out.println(e));
CompletableFuture.supplyAsync(Supplier<U>) expects a java.util.function.Supplier<U> instance, and Supplier.get() method's signature does not allow for checked exceptions. To see this clearly, notice that CompletableFuture.supplyAsync(MetadataLoginUtil::login) is equivalent to
CompletableFuture<Void> mt = CompletableFuture.supplyAsync(new Supplier<Void>() {
#Override
public Void get() {
return MetadataLoginUtil.login();
}
})
which clearly cannot compile.
You can handle the exception inside your Supplier, changing CompletableFuture.supplyAsync(MetadataLoginUtil::login).exceptionally(e -> {System.out.println(e); return null; } ) to
CompletableFuture.supplyAsync(() -> {
try {
return MetadataLoginUtil.login();
} catch (Exception e) {
System.out.println(e);
return null;
}
})
It's not pretty, but CompletableFuture's API doesn't seem to work with checked exceptions very well.

Cancel task on timeout in RxJava

I'm experimenting with RxJava and Java 8's CompletableFuture class
and do not quite get how to handle timeout conditions.
import static net.javacrumbs.futureconverter.java8rx.FutureConverter.toObservable;
// ...
Observable<String> doSomethingSlowly() {
CompletableFuture<PaymentResult> task = CompletableFuture.supplyAsync(() -> {
// this call may be very slow - if it takes too long,
// we want to time out and cancel it.
return processor.slowExternalCall();
});
return toObservable(task);
}
// ...
doSomethingSlowly()
.single()
.timeout(3, TimeUnit.SECONDS, Observable.just("timeout"));
This basically works (if the timeout of three seconds is reached, "timeout" is published). I would however additionally want to cancel the future task that I've wrapped in an Observable - is that possible with an RxJava-centric approach?
I know that one option would be to handle the timeout myself, using task.get(3, TimeUnit.SECONDS), but I wonder if it's possible to do all task handling stuff in RxJava.
Yes, you can do this. You would add a Subscription to the Subscriber.
This allows you to listen in on unsubscriptions, which will happen if you explicitly call subscribe().unsubscribe() or if the Observable completes successfully or with an error.
If you see an unsubscription before the future has completed, you can assume it's because of either an explicit unsubscribe or a timeout.
public class FutureTest {
public static void main(String[] args) throws IOException {
doSomethingSlowly()
.timeout(1, TimeUnit.SECONDS, Observable.just("timeout"))
.subscribe(System.out::println);
System.in.read(); // keep process alive
}
private static Observable<String> doSomethingSlowly() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
return "Something";
});
return toObservable(future);
}
private static <T> Observable<T> toObservable(CompletableFuture<T> future) {
return Observable.create(subscriber -> {
subscriber.add(new Subscription() {
private boolean unsubscribed = false;
#Override
public void unsubscribe() {
if (!future.isDone()){
future.cancel(true);
}
unsubscribed = true;
}
#Override
public boolean isUnsubscribed() {
return unsubscribed;
}
});
future.thenAccept(value -> {
if (!subscriber.isUnsubscribed()){
subscriber.onNext(value);
subscriber.onCompleted();
}
}).exceptionally(throwable -> {
if (!subscriber.isUnsubscribed()) {
subscriber.onError(throwable);
}
return null;
});
});
}
}

Categories