Is CompletableFuture followed immediately by a get efficient? - java

I just found the following code, it supplies an asynchronous task but immediately get the result (so if I understand correctly, it blocks the current thread until the result is available).
Is it efficient ?
public String myMethod() {
CompletableFuture<String> futur = CompletableFuture.supplyAsync(() -> {
// my long call to an external API
return "theResult";
});
try {
return future.get(FUTURE_TIMEOUT_DURATION, TimeUnit.MINUTES);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
If the timeout is handled correctly in the call to the external API, do I need this completable futur ?
Is it ok to simplify the code like this ?
public String myMethod() {
// my long call to an external API
return "theResult";
}

If you doesn't expect any problem with timeout you most probably can remove code related to feature.
There is possibility that code use some threadlocal variables or otherwise relies on being executed in separate thread.

Related

Handling CompletableFuture exceptions in controller

I'm trying to get into CompletableFuture class for a project I'm running, and I got to some question here:
There is the following method: it tries to find a conversation by its ID or hash; and, if not found, it throws an exception. So far, so good.
public ConversationOutput getConversationByIdOrHash(String conversationIdOrHash)
throws ConversationNotFoundException {
Conversation conversation = this.conversationRepository.getByIdOrHash(conversationIdOrHash);
if (conversation == null) {
throw new ConversationNotFoundException(conversationIdOrHash);
}
return this.modelMapper.map(conversation, ConversationOutput.class);
}
Note that I am throwing ConversationNotFoundException from my method signature. My SpringBoot controller is reacting to this exception and it's all working fine since the beginning.
What I'm trying to do is to make this to a CompletableFuture return and actually throwing an exception, something similar to:
public CompletableFuture<ConversationOutput> getConversationByIdOrHashAsync(String conversationIdOrHash)
throws ConversationNotFoundException {
return CompletableFuture.supplyAsync(() -> this.getConversationByIdOrHash(conversationIdOrHash));
}
I've seen posts where people use exceptionally to handle exceptions, but what I really want to do is to throw it to my controller and let it handle it. Any suggestions of how can I make it?
Thank you all!
The question is do you care about the result of CompletableFuture.
CompletableFuture is like a special task and it is processed on other thread. If you don't invoke .join() you won't receive the results of CompletableFuture. This method also will propagate the exception if any occured. However it waits for CompletableFuture to finish and blocks the request.
However, there is no way to get exceptions from the inside of the CompletableFuture without waiting, you have to treat it like other task.
You can pass the completed future in case of a success, and failed future along with your custom exception.
public CompletableFuture<ConversationOutput> getConversationByIdOrHashAsync(String conversationIdOrHash) {
try {
return CompletableFuture.completedFuture(this.getConversationByIdOrHash(conversationIdOrHash));
} catch (ConversationNotFoundException e) {
return CompletableFuture.failedFuture(e);
}
}
and then at your controller level you can handle the exception.
final CompletableFuture<ConversationOutput> future = getConversationByIdOrHashAsync("idOrHash");
future.whenComplete((r, e) -> {
if (e != null) {
if (e instanceof ConversationNotFoundException) {
//handling
}
}
});

Mono vs CompletableFuture

CompletableFuture executes a task on a separate thread ( uses a thread-pool ) and provides a callback function. Let's say I have an API call in a CompletableFuture. Is that an API call blocking? Would the thread be blocked till it does not get a response from the API? ( I know main thread/tomcat thread will be non-blocking, but what about the thread on which CompletableFuture task is executing? )
Mono is completely non-blocking, as far as I know.
Please shed some light on this and correct me if I am wrong.
CompletableFuture is Async. But is it non-blocking?
One which is true about CompletableFuture is that it is truly async, it allows you to run your task asynchronously from the caller thread and the API such as thenXXX allows you to process the result when it becomes available. On the other hand, CompletableFuture is not always non-blocking. For example, when you run the following code, it will be executed asynchronously on the default ForkJoinPool:
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
});
It is clear that the Thread in ForkJoinPool that executes the task will be blocked eventually which means that we can't guarantee that the call will be non-blocking.
On the other hand, CompletableFuture exposes API which allows you to make it truly non-blocking.
For example, you can always do the following:
public CompletableFuture myNonBlockingHttpCall(Object someData) {
var uncompletedFuture = new CompletableFuture(); // creates uncompleted future
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
uncompletedFuture.completeExceptionally(exception);
return;
}
uncompletedFuture.complete(result);
})
return uncompletedFuture;
}
As you can see, the API of CompletableFuture future provides you with the complete and completeExceptionally methods that complete your execution whenever it is needed without blocking any thread.
Mono vs CompletableFuture
In the previous section, we got an overview of CF behavior, but what is the central difference between CompletableFuture and Mono?
It worth to mention that we can do blocking Mono as well. No one prevents us from writing the following:
Mono.fromCallable(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
})
Of course, once we subscribe to the future, the caller thread will be blocked. But we can always work around that by providing an additional subscribeOn operator. Nevertheless, the broader API of Mono is not the key feature.
In order to understand the main difference between CompletableFuture and Mono, lets back to previously mentioned myNonBlockingHttpCall method implementation.
public CompletableFuture myUpperLevelBusinessLogic() {
var future = myNonBlockingHttpCall();
// ... some code
if (something) {
// oh we don't really need anything, let's just throw an exception
var errorFuture = new CompletableFuture();
errorFuture.completeExceptionally(new RuntimeException());
return errorFuture;
}
return future;
}
In the case of CompletableFuture, once the method is called, it will eagerly execute HTTP call to another service/resource. Even though we will not really need the result of the execution after verifying some pre/post conditions, it starts the execution, and additional CPU/DB-Connections/What-Ever-Machine-Resources will be allocated for this work.
In contrast, the Mono type is lazy by definition:
public Mono myNonBlockingHttpCallWithMono(Object someData) {
return Mono.create(sink -> {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
sink.error(exception);
return;
}
sink.success(result);
})
});
}
public Mono myUpperLevelBusinessLogic() {
var mono = myNonBlockingHttpCallWithMono();
// ... some code
if (something) {
// oh we don't really need anything, let's just throw an exception
return Mono.error(new RuntimeException());
}
return mono;
}
In this case, nothing will happen until the final mono is subscribed. Thus, only when Mono returned by the myNonBlockingHttpCallWithMono method, will be subscribed, the logic provided to Mono.create(Consumer) will be executed.
And we can go even further. We can make our execution much lazier. As you might know, Mono extends Publisher from the Reactive Streams specification. The screaming feature of Reactive Streams is backpressure support. Thus, using the Mono API we can do execution only when the data is really needed, and our subscriber is ready to consume them:
Mono.create(sink -> {
AtomicBoolean once = new AtomicBoolean();
sink.onRequest(__ -> {
if(!once.get() && once.compareAndSet(false, true) {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
sink.error(exception);
return;
}
sink.success(result);
});
}
});
});
In this example, we execute data only when subscriber called Subscription#request so by doing that it declared its readiness to receive data.
Summary
CompletableFuture is async and can be non-blocking
CompletableFuture is eager. You can't postpone the execution. But you can cancel them (which is better than nothing)
Mono is async/non-blocking and can easily execute any call on different Thread by composing the main Mono with different operators.
Mono is truly lazy and allows postponing execution startup by the subscriber presence and its readiness to consume data.
Building up on Oleh's answer, a possible lazy solution for CompletableFuture would be
public CompletableFuture myNonBlockingHttpCall(CompletableFuture<ExecutorService> dispatch, Object someData) {
var uncompletedFuture = new CompletableFuture(); // creates uncompleted future
dispatch.thenAccept(x -> x.submit(() -> {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
uncompletedFuture.completeExceptionally(exception);
return;
}
uncompletedFuture.complete(result);
})
}));
return uncompletedFuture;
}
Then, later on you simply do
dispatch.complete(executor);
That would make CompletableFuture equivalent to Mono, but without backpressure, I guess.

using Supplier in CompletableFuture yields different result than using lambda

I have created a small example of reading a text file and wrap the call with CompletableFuture.
public class Async {
public static void main(String[] args) throws Exception {
CompletableFuture<String> result = ReadFileUsingLambda(Paths.get("path/to/file"));
result.whenComplete((ok, ex) -> {
if (ex == null) {
System.out.println(ok);
} else {
ex.printStackTrace();
}
});
}
public static CompletableFuture<String> ReadFileUsingSupplier(Path file) throws Exception {
return CompletableFuture.supplyAsync(new Supplier<String>() {
#Override
public String get() {
try {
return new String(Files.readAllBytes(file));
} catch (IOException e) {
e.printStackTrace();
return "test";
}
}
}, ForkJoinPool.commonPool());
}
public static CompletableFuture<String> ReadFileUsingLambda(Path file) throws Exception {
return CompletableFuture.supplyAsync(() -> {
try {
return new String(Files.readAllBytes(file));
} catch (IOException e) {
e.printStackTrace();
return "test";
}
} , ForkJoinPool.commonPool());
}
}
This code returns nothing. It executes and "nothing happens", no errors or output. If I call ReadFileUsingSupplier instead of ReadFileUsingLambda then I get the file content printed in the console!
To me this doesn't make sense because a lambda is a shorthand for writing an inline function and it shouldn't change the behaviour but in this example it does apparently.
I think it's just a matter of execution timing - the lambda may take a little more to execute, allowing the program to exit before you are done reading the file.
Try this:
add a Thread.sleep(1000); as the first statement within the try block in ReadFileUsingSupplier and you won't see any output
add a Thread.sleep(1000); at the end of your main when using ReadFileUsingLambda and you will see the expected output
To make sure your main doesn't exit before the future is completed, you can call:
result.join();
As noted, you need to result.join() in either case to avoid the main thread exiting too quickly.
It seems that there's a penalty for using lambdas vs anonymous closures while the JVM warms up, thereafter the performance is the same. I found this information at on another SO thread - which in turn links a performance study by Oracle.
As a sidenote it's not a great idea to Thread.sleep() to fix weird timing issues, ever. Figuring out the cause and applying the appropriate measures would be much clearer when re-read by you or by others, e.g.
System.out.println(result.get(5, TimeUnit.SECONDS));
This enables you to ditch the .join(), too.

Why aren't my callback methods working properly?

I use AsyncRestTemplate to make resttemplate asynchronously.
These methods should wait all asyncresttemplate processes till done, And It will return reviewContent.
Problem is callback methods are not working, before the entire method works done. So I can't take proper return value of optionName and membershipGradeCode and reviewType should be included in reviewContent.
Could someone explain what am I missing now?
rev#1
Success callback methods change the state of reviewContent, Could it be a problem?
public ReviewContent getRepresentativeReviewContent(Long dealNo, Long categoryNo, String setId) {
Optional<Map<String, Object>> review = Optional.ofNullable(boardApi.getRepresentativeReviewContent(dealNo));
if (review.isPresent()) {
Long memberNo = Long.valueOf(review.get().get("memberNo").toString());
ReviewContent reviewContent = new ReviewContent();
ListenableFuture<ResponseEntity<Map>> optionInfo = dealApi.asyncGetDealOption(Long.valueOf(review.get().get("optionNo").toString()));
optionInfo.addCallback(success -> {
try {
reviewContent.setOptionName((String) ((Map<String, Object>) success.getBody().get("data")).get("dealTitle"));
} catch (Exception e) {
reviewContent.setOptionName(null);
}
}, failure -> LOGGER.error("asyncGetDealOption", failure.getStackTrace()));
ListenableFuture<ResponseEntity<Map>> gradeInfoOfThisMember = mktApi.asyncGetMembershipGradeOfThisMember(memberNo);
gradeInfoOfThisMember.addCallback(success -> {
try {
reviewContent.setMembershipGradeCode((Integer) ((Map<String, Object>) success.getBody().get("data")).get("grade"));
} catch (Exception e) {
reviewContent.setMembershipGradeCode(0);
}
},
failure -> {
reviewContent.setMembershipGradeCode(0);
LOGGER.error("asyncGetMembershipGradeOfThisMember", failure.getStackTrace());
});
ListenableFuture<ResponseEntity<ReviewType>> reviewTypeByCategoryNo = boardApi.asyncGetReviewTypeByCategoryNo(categoryNo, setId);
reviewTypeByCategoryNo.addCallback(success -> {
try {
reviewContent.setReviewType(success.getBody());
} catch (Exception e) {
reviewContent.setReviewType(null);
}
},
failure -> {
reviewContent.setReviewType(null);
LOGGER.error("asyncGetReviewTypeByCategoryNo", failure.getStackTrace());
});
reviewContent.setReviewCount((Integer) review.get().get("reviewCount"));
reviewContent.setReviewAvgScore((Double) review.get().get("reviewAvgScore"));
reviewContent.setContents((String) review.get().get("contents"));
reviewContent.setCreateDt((String) review.get().get("createdDt"));
reviewContent.setUpdateDt((String) review.get().get("updatedDt"));
reviewContent.setMemberSrl(memberNo);
reviewContent.setTitle((String) review.get().get("title"));
reviewContent.setAccountSrl(Long.valueOf(review.get().get("accountNo").toString()));
reviewContent.setMemberId((String) review.get().get("memberId"));
reviewContent.setAccountSrl(Long.valueOf(review.get().get("accountNo").toString()));
boolean isApiExecutionDone = false;
while (!isApiExecutionDone) {
if (gradeInfoOfThisMember.isDone() && optionInfo.isDone() && reviewTypeByCategoryNo.isDone()) {
isApiExecutionDone = true;
}
}
return reviewContent;
}
return new ReviewContent();
}
So your problem is that the callbacks set properties on the object returned by your method. However, they are also executed asynchronously, and are not part of the done status of the Future: they are themselves executed once the Future is done, concurrently with the code in the getRepresentativeReviewContent method. Since the method returns as soon as all Futures are done, the properties aren't (all) set as they should.
Moreover, you didn't show the code for your ReviewContent object, but I'm pretty sure it doesn't declare the optionType, membershipGradeCode or reviewType fields as volatile. Since there are no barriers (such as synchronized blocks or Locks) in the method, there's no guarantee in the Java Memory Model that the values set in the callbacks (i.e. in other threads) would be seen in the thread executing the getRepresentativeReviewContent method.
Callbacks should only be used for side-effects outside of your main execution path, since it's hard to coordinate with them: you would have to use things like a CountDownLatch to make sure they have all executed, that would make the code even more complex.
Just wait for the asynchronous results in a straight-forward way (the code is untested though):
try {
// Not sure why you need to catch Exception here?
// If it's for flow control (absent entry in a Map), it's a bad practice.
// Just check it instead of catching NullPointerException.
reviewContent.setOptionName((String)
((Map<String, Object>) optionInfo.get().getBody().get("data"))
.get("dealTitle"));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOGGER.error("asyncGetDealOption", e);
reviewContent.setOptionName(null);
} catch (CancellationException | ExecutionException e) {
LOGGER.error("asyncGetDealOption", e);
reviewContent.setOptionName(null);
}
Another option is to compose the Futures, such as what can be done with Guava's Futures.transform to actually get the string you need out of the complete response, so you can just call get() on that composed Future to set your property. You'd still have to managed the errors, though.

How to check subscription status when not using Observable.create()?

Say you have some long running task wrapped by an observable:
Observable.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
return longRunningTask();
}
}
Is there any way to check whether the observable has been unsubscribed to determine if we should cancel the work and bail out?
More specifically, is it possible to check the status of a subscription (e.g. isUnsubscribed()) when using Observable.defer() or Observable.fromCallable()?
I'm aware that you can check subscriber.isUnsubscribed() when using Observable.create(), however, since it's ill-advised to use Observable.create(), how can this be done with other operators?
What about using Observable.doOnSubscribe(Action0) and Observable.doOnUnsubscribe(Action0). You can count the subscriptions and when there are none you can stop the job.
Greetings,
Martin
The fromCallable doesn't expose the consumer. For this, you need create with a body such as the following:
final SingleDelayedProducer<T> singleDelayedProducer =
new SingleDelayedProducer<T>(subscriber);
subscriber.setProducer(singleDelayedProducer);
try {
T result;
// computation
if (subscriber.isUnsubscribed()) {
return;
}
// more computation
result = ...
singleDelayedProducer.setValue(result);
} catch (Throwable t) {
Exceptions.throwOrReport(t, subscriber);
}

Categories