Log inside Mono.error gets outputted twice - java

For some strange reason, the "User not found" log gets outputted twice in the logs of my application, despite calling findUserById only once.
I am not sure what causes this problem.
Is there a better way to approach this (logging and throwing an exception)?
Please note that the findById call is of an API.
Edit:
It appears that the exception is thrown only once. Also, if I replace Mono.error with Mono.defer, the log is printed twice as well.
public Mono<User> getUser(String id) {
Mono<User> thisIsEmpty = getNoUser(); // Assume that this is empty
return Mono.defer(() -> thisIsEmpty.switchIfEmpty(Mono.defer(() -> findUserById(id))));
}
public Mono<User> findUserById(String id) {
log.info("This is printed once.");
Mono<User> user = repository.findById(id).switchIfEmpty(Mono.error(() -> { // findById is an API call of a library I use
log.error("User not found (this is printed twice)"); // Gets printed twice
throw new UserException(MY_ERROR_CODE, 401);
}));
user.subscribe(User -> ... // Do something if it is not empty
return user;
}

Well, from Mono#error docs:
Create a Mono that terminates with an error immediately after being subscribed to. The Throwable is generated by a Supplier, invoked each time there is a subscription and allowing for lazy instantiation.
First your subscription is here:
user.subscribe(User -> ...
And I assume you have another subscription in code, that uses getUser.
That's why it's printed twice.

As #Eugene mention your problem is that you probably subscribe twice.
If you want to call it once then your method should be rewriten
public Mono<User> findUserById(String id) {
log.info("This is printed once.");
return repository.findById(id)
.switchIfEmpty(Mono.error(() -> { // findById is an API call of a library I use
log.error("User not found (this is printed twice)"); // Gets printed twice
throw new UserException(MY_ERROR_CODE, 401);
}))
.flatMap(user-> {
// do smth here
return Mono.just(user);
});
return user;
}
Reactive is a bit different so when you return mono it wont be executed until you subscribe to it (or block which you should not do until you must).
So basically what is happening in your case first time you "execute" when you do
user.subscribe(User -> ... // Do something if it is not empty
Second time is probably you have controller which is returning mono and spring boot is subscribing
Also not anything you do inside of you service subscribe wont be sent outside of it. You should use flatMap or any other Reactive operators for that.

Related

What's the easiest way to wait for Mono completion in the background?

We are given a Mono, that's handling some action(say a database update), and returns a value.
We want to add that Mono(transformed) to a special list that contains actions to be completed for example during shutdown.
That mono may be eagerly subscribed after adding to the list, to start processing now, or .subscribe() might not be called meaning it will be only subscribed during shutdown.
During shutdown we can iterate on the list in the following way:
for (Mono mono : specialList) {
Object value = mono.block(); // (do something with value)
}
How to transform the original Mono such that when shutdown code executes, and Mono was previously subscribed(), the action will not be triggered again but instead it will either wait for it to complete or replay it's stored return value?
OK, looks like it is as simple as calling mono.cache(), so this is how I used it in practice
public Mono<Void> addShutdownMono(Mono<Void> mono) {
mono = mono.cache();
Mono<Void> newMono = mono.doFinally(signal -> shutdownMonos.remove(mono));
shutdownMonos.add(mono);
return newMono;
}
public Function<Mono<Void>,Mono<Void>> asShutdownAwaitable() {
return mono -> addShutdownMono(mono);
}
database.doSomeAction()
.as(asShutdownAwaitable)
.subscribe() // Or don't subscribe at all, deferring until shutdown
Here is the actual shutdown code.
It was also important to me that they execute in order of being added, if user chose not to eagerly subscribe them, that's reason for Flux.concat instead of Flux.merge.
public void shutdown() {
Flux.concat(Lists.transform(new ArrayList<>(shutdownMonos), mono -> mono.onErrorResume(err -> {
logger.error("Async exception during shutdown, ignoring", err);
return Mono.empty();
}))
).blockLast();
}

Mono switchIfEmpty is always called

I have a bit more complex use case in Spring Gateway which is based on WebFlux and I ran into small issue with Mono usage. Long story short, my switchIfEmpty is called even if not required. I prepared small example which allows me to reproduce this problem:
public class ReactiveTest {
#Test
void test1() {
isOdd(2)
.flatMap(this::onNotEmpty)
.switchIfEmpty(Mono.defer(this::onEmpty))
.block();
}
Mono<String> isOdd(Integer number) {
return number % 2 != 0 ? Mono.just("Yes") : Mono.empty();
}
Mono<Void> onNotEmpty(String value) {
System.out.println("Value uppercased " + value.toUpperCase());
return Mono.empty();
}
Mono<Void> onEmpty() {
System.out.println("Value not present, this shouldn't been called if value was odd");
return Mono.empty();
}
}
I hope this is pretty self-explanatory, but just to be safe:
isOdd(Integer number) may produce Mono with data or empty Mono
I expect onNotEmpty to be called only if previous Mono had data
I expect onEmpty to be called only if isOdd produced empty Mono
Unfortunatelly, both onNotEmpty and onEmpty are called all the time, regardless if I pass odd or even number to isOdd.
How can I make sure that onEmpty is called only when isOdd produced Mono.empty()?
The onNotEmpty(String value) method is always returning Mono.empty(), meaning that .switchIfEmpty(Mono.defer(this::onEmpty)) will always be call either because isOdd(2) is already an empty Mono or because onNotEmpty(String value) method was called and returned an empty Mono.
In order to avoid this, you need to change your onNotEmpty(String value) method to return something else than an empty Mono.
Additionally, please avoid using block() since this defeats the whole purpose of using Spring WebFlux by blocking the thread waiting for something to be emitted by the reactive chain.

Use Reactor Core Java to send to browser the status of a long executing task, periodically

I have been working with some Reactor Core Java, because I want to figure out if this is possible to solve one problem I currently have using this framework.
At present I have a long, executing job that takes about 40-50 minutes to complete. The method looks more or less like this:
public void doLongTask(List<Something> list){
//instructions.
for(Something sm : list){
if(condition){
executeLongOperation();
}
//instructions
if(condition){
executeLongOperation();
}
}
}
in my controller I have something like this:
#GetMapping(path = "/integersReactor", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
#ResponseBody
public Flux<Integer> getIntegersReactor(){
logger.debug("Request getIntegersReactor initialized.");
return simpleSearchService.getIntegersReactor();
}
and in the service layer I have something like this:
#Override
public Flux<Integer> getIntegersReactor(){
return Flux.range(0, Integer.MAX_VALUE);
}
this is just a placeholder that I am using as a proof of concept. My real intentions are to somehow return a Flux of some object that I will define myself, this object will have a few fields that I will use to tell the consumer the status of the job.
Now, things get somewhat complicated now because I would like to send updates as the executeLongOperation(); are executed, and somehow instead of returning a flux of Integers, return a flux of an object that uses the return of executeLongOperation();
Can this be acomplished with Flux? How can I leverage Reactor Core java to push the return values of all of the times executeLongOperation(); is executed into a reactive stream that can be passed to the controller the same way that getIntegersReactor() does it in my example?
Yes it should be possible, but since the executeLongOperation is blocking, it will need to be offset on a dedicated thread (which reduces the benefits you get from a top-to-bottom reactive implementation).
Change your doLongTask to return a Flux<Foo>, make it concatenate Monos that wrap executeLongOperation on a dedicated thread (or better yet, change the executeLongOperation itself to return a Mono<Foo> and do the wrapping internally and subscribeOn another thread internally). Something like:
public Flux<Foo> doLongTask(List<Something> list) {
return Flux.fromIterable(list)
//ensure `Something` are published on a dedicated thread on which
//we can block
.publishOn(Schedulers.elastic()) //maybe a dedicated Scheduler?
//for each `Something`, perform the work
.flatMap(sm -> {
//in case condition is false, we'll avoid long running task
Flux<Foo> work = Flux.empty();
//start declaring the work depending on conditions
if(condition) {
Mono<Foo> op = Mono.fromCallable(this::executeLongOperation);
work = conditional.concatWith(op);
}
//all other instructions should preferably be non-blocking
//but since we're on a dedicated thread at this point, it should be ok
if(condition) {
Mono<Foo> op = Mono.fromCallable(this::executeLongOperation);
work = conditional.concatWith(op);
}
//let the flatMap trigger the work
return work;
});
}

Using Java `CompletablyFuture.exceptionally` to optionally continue through exceptions

I have a series of asynchronous tasks chained together using Java CompletableFutures. The code looks something like this:
CompletableFuture<Result> doTasks(final TaskId id) {
return firstTask.workAsync(id)
.thenComposeAsync(__ -> secondTask.workAsync(id))
.thenComposeAsync(__ -> thirdTask.workAsync(id))
.thenApplyAsync(__ -> fourthTask.workAsync(id));
}
However, firstTask.workAsync throws an exception indicating that the work has already been completed, which is OK in this situation, so I would like to just ignore it and continue through the chain.
Of course, I could just wrap that bit in a separate function where I can handle the exception, but is there a way to handle it directly in the CompletableFuture chain and continue to throw all other exceptions?
A co-worker suggested I use CompletableFuture.exceptionally, but all of the examples online that I see are totally useless and just return null, which looks like it would kill the chain. How would I use that in this case?
CompletableFuture.exceptionally can be used to continue when getting an exception in a CompletableFuture. In a nutshell, you need to check the type of the exception, and if it's an exception you want to continue on, you can return a new CompletableFuture, which can be empty since the result is not used down the chain.
CompletableFuture<Result> doTasks(final TaskId id) {
return firstTask.workAsync(id)
.exceptionally(t -> {
// Will continue down the chain if matches
if (t instanceof TotallyOkException) {
return null;
}
// This will throw an ExecutionException. I convert it to a RuntimeException here
// because I don't want to add throws statements up the chain.
throw new RuntimeException(t);
})
.thenComposeAsync(__ -> secondTask.workAsync(id))
.thenComposeAsync(__ -> thirdTask.workAsync(id))
.thenApplyAsync(__ -> fourthTask.workAsync(id));
}
In this case, it will throw all non-TotallyOkException exceptions.
Returning null in your exceptionally function will not, in itself, kill the chain. The only way it will kill the chain is a result of lack of null handling in the downstream function and causing a NullPointerException.
Your exceptionally function can be set up to handle some types of exception and not others. For example:
return firstTask.workAsync(id)
.thenComposeAsync(firstResult -> secondTask.workAsync(id))
.exceptionally(t -> {
if (t instanceof TransientException) {
return getUsingBackupMethod(id);
}
throw new RuntimeException(t);
});
This exceptionally function will (effectively) catch an exception thrown from either of the first two tasks.

How to handle error thown in RxJava chain

I am trying to execute following code
testRepository
.exists(data)
.flatMap(x -> {
if (x==null) {
return Observable.error(new Exception("Error"));
}
return Observable.just(x);
})
.flatMap(x -> testRepository.create(x))
.flatMap(x -> {
return Observable.just(x);
});
This code works when no error is thrown in first map. But in case when error is thrown it just hangs.
What is wrong here?
Thanks
You haven't declared error properly, you shouldn't perform explicit converting to error observable via flatMap, because in case positive state you create new observable for each item and combine them after all.
You may just use
.doOnNext(x -> {
if (x == null) throw new IllegalStateException("null item error");
})
And this exception will interrupt stream and will be properly handled in onError callback of subscription.
Last instruction also doesn't make sense, because you convert each element to a single item observable and then combine them back to a similar stream.
Ps: also it needs to be called .subscribe somewhere, but i think it is meant.

Categories

Resources