RxJava infinite retry with new observable - java

I am using the Rx-ified API for vertx, and this question has to do a potentially infinite retry-until-success loop I would like to implement but am having difficulty. I'm new to RxJava.
Here's what I'd like to do:
Send a request to another vertx component using the vertx message
bus
For as long as I get a timeout waiting for a response, re-issue
the request
Once I have a response to the request, inspect the
results, and if there is nothing usable, wait for a period and then
start all over again at step 1)
The first problem
The first problem I encounter is how to accomplish step 2).
If you are familiar with the vert.x Rx api, this is what it means to issue the request in step 1) above:
vertx.eventBus().<JsonObject>sendObservable( "theAddress", aJsonObject );
The above code returns an Observable instance that will emit either the response, or an error (if there was a timeout, for example). That Observable will never emit anything ever again (or else it will always emit exactly the same thing every time something subscribes, I don't know which).
RxJava retry operator doesn't seem to work
vertx.eventBus().<JsonObject>sendObservable( "theAddress", aJsonObject )
.retry()
I thought that in order to issue the retry, I could just use RxJava's retry() operator, which I tried, but doing so has exactly no useful effect whatsoever, due to the nature of the source observable. No new request message gets sent, because the only thing that is being "retried" is the subscription to the original source, which is never going to emit anything different.
RxJava retryWhen operator doesn't seem to work
vertx.eventBus().<JsonObject>sendObservable( "theAddress", aJsonObject )
.retryWhen( error -> {
return _vertx.eventBus().<JsonObject>sendObservable( "theAddress", aJsonObject )
})
So then I thought I could use RxJava's retryWhen() operator, which allows me to issue a second observable when the root observable emits an error. The second observable could, I figured, just be the same code above that produced the initial observer in step 1.
But, the retryWhen() operator (see documentation) does not allow this second observable to emit an error without ending the subscription with an error.
So, I'm having trouble figuring out how to set up a potentially infinite retry loop within the first part of this chain.
I must be missing something here, but I have not been able to determine what it is.
The second problem
vertx.eventBus().<JsonObject>sendObservable( "theAddress", aJsonObject )
// imagine that retryWhen() accomplishes an infinite retry
.retryWhen( error -> {
return _vertx.eventBus().<JsonObject>sendObservable( ... )
})
.flatMap( response -> {
// inspect response, if it has usable data,
// return that data as an observable
return Observable.from(response.data());
// if the response has no usable data,
// wait for some time, then start the whole process
// all over again
return Observable.timer(timeToWait).<WHAT GOES HERE?>;
})
The second problem is how to implement step 3. This seems to me to be like the first problem, only harder to understand, because I don't need to retry the immediate source observable, I need to wait for a bit, then start over at step 1).
Whatever Observable I create would seem to require all of the elements in the chain leading up to this point, which seems like a kind of recursion that should probably be avoided.
Any help or suggestions would be really welcome at this point.

Many thanks to Ben Christensen over on the RxJava Google Group for pointing out the defer() operator, which will generate a new Observable on each subscription. This can then be composed with the standard retry() operator to get inifinite retry.
So, the simplest solution to the first problem in my question looks like so:
Observable.defer( () -> vertx.eventBus().<JsonObject>sendObservable( "theAddress", aJsonObject ) )
.retry()
...if exponential backoff is required, you can add Observable.timer() with appropriate parameters in the factory method given to the defer() operator.
I am still looking into the second problem.

Related

Send/Update reactor Mono Object to different attributes of a Flux.concat()

I have a Flux.concat() operation which takes 3 checks like this:
public Mono<Boolean> checkSleep(Student std)
{
Flux.concat(isHealthy(std), isWealthy(std, sleep), isWise(std, sleep))
.filter(result -> !result)
.next()
.flatMap(result -> Mono.just(false)) //returns false if any one is false
.switchIfEmpty(Mono.just(true)); // returns true if all are true
}
each of this methods has a common external api call extService.getSleep(Student std) to get Sleep Object Mono<Sleep> for its flow.
I want to call extService.getSleep(Student std) only once in the entire flow,
ideally in the first check isHealthy(std) and pass the object Mono<Sleep> to the next 2 checks as well.
I am not understanding how to make this call as Flux.concat does not allow a Mono to be added in the prefix.
Each of the checks have similar body like this:
Mono<Boolean> isHealthy(Student std)
{
return Mono.just(std)
.flatMap(std->extService.getSleep(std)).map(sleep-> sleep.isValid());
}
in the next check I want to pass sleep object from previous method,
isWealthy(Student std, Sleep sleep)
I do not want to call extService.getSleep(std)) once again,
I thought of creating a variable outside these 3 methods and update it when the api returns a something,
it throws error saying "Variable used in lambda expression should be final or effectively final"
Let me know if there is a better way to handle this scenario.
I am new to reactive spring programming, any help is appreciated.
thanks in advance.
Your line of thinking was not far off!
Whenever you need to "reach outside" a publisher, consider using AtomicBoolean, AtomicInteger, etc. or the parameterized AtomicReference to get around the final or effectively final compiler warning. However, it should be noted that asynchronous operations like flatMap may not have the correct value when they get the wrapped values from these, so it's best to get around the problem in a different way.
Fortunately, Reactor has a myriad of useful methods on its publishers!
If I understand correctly, the checkSleep function should resolve to true if all three of isHealthy, isWealthy and isWise also resolve to true - false if even one of them resolve to false.
I have created a simple simulation of this scenario:
private Mono<Boolean> checkSleep(Student std) {
return getSleep(std)
.flatMapMany(sleep -> Flux.merge(isHealthy(std, sleep), isWealthy(std, sleep), isWise(std, sleep)))
.all(result -> result);
}
private Mono<Sleep> getSleep(Student std) {
return Mono.just(new Sleep(8));
}
private Mono<Boolean> isHealthy(Student std, Sleep sleep) {
return Mono.just(true);
}
private Mono<Boolean> isWealthy(Student std, Sleep sleep) {
return Mono.just(true);
}
private Mono<Boolean> isWise(Student std, Sleep sleep) {
return Mono.just(true);
}
This way, getSleep is only called once, and is used to flat map the emitted value into the three booleans you're looking for. The Flux::all method then ensures that the returned Mono will wrap true only if all three inners have emitted true.
Another thing to note is that I've replaced Flux::concat with Flux::merge. The former goes sequentially, subscribing, waiting for result, then repeat. These three publishers seem to be independent of one another, so replacing concat with merge allows all three to be subscribed to at the same time, thereby reducing time wasted with waiting.

Mono.timeout triggering immediately

I have not seen this question asked so it might be a really rare situation or maybe I am doing something wrong, but basically I am trying to create a Mono that waits on a message from an MQ JMS message.
Mono result = Mono.create(sink -> msgProcessor.registerListener(sink::success, requestId))
.timeout(Duration.ofSeconds(10))
.onErrorReturn(msgProcessor.timeout())
.doFinally(change -> msgProcessors.remove(requestId));
So it seems like timeout is always being triggered and the timeout method is executed. Anyone know why?
The create(Consumer<MonoSink<T>> callback) deals with the signals inside the Mono over the MonoSink<T> that lets you emit values through MonoSink.success(), MonoSink.success(value), MonoSink.error(throwable) methods.
In your example, you don't emit any value; thus, .timeout is always being triggered.
Since registerListener method does not produce any value, you should replace Mono.create with Mono.fromRunnable like this:
Mono.fromRunnable(() -> msgProcessor.registerListener(sink::success, requestId))
.timeout(Duration.ofSeconds(10))

Spring WebFlux: Merge Mono and Flux / Put Mono into Flux?

I'm wondering if it is possible to put the value of a Mono as a value into a Flux just like you could append any object to a list. I know there are some methods you could use but none of them fulfills my exact purpose. What I tried so far:
Flux<T> flux;
Mono<T> mono;
Flux.merge(flux, mono); // <- returns Flux<Object>
This doesn't sound too bad but notice that it does not return Flux<T> as I would need it but Flux<Object>. Same with Flux.concat(flux, mono);. The method Flux.zip(flux, mono); would stop merging as soon as the Mono completes as far as I understand.
Has somebody a solution for this?
This is what I ended up doing:
I have the method return a Flux of the desired type which in my case was an 'ExtendedResourceModel'. I create a Mono of this type which gets initialized by another method that I commented out to keep this answer as concise as possible. If a certain condition is met I want to also use a Flux from a different source so I use 'Flux.concat()' to put all elements into a single Flux. The 'concat'-method works by subscribing to the first argument, letting it finish, subscribing to the second one and so on.
In case of my condition not being met I just use 'concat' again but this time with only the Mono which will put the Mono's element into a new Flux. This way both cases have the same return type.
It is not possible to do, if you need to do that you can convert your Flux to mono
Mono monoToFlux = flux.collectList();
Mono mono;
Mono.zip (mono, monoToFlux).flatmap(tuple -> {
... more code ...
})

I don't know how to use the then operator of Project Reactor

I don't know how to use the then operator of Project Reactor. I don't quite understand the javadoc of this operator. Who can explain this to me? With some code examples would be great. I myself wrote an example like below:
public class Test11 {
public static void main(String[] args) {
Flux flux = Flux.just(1, 2, 3);
Mono<Void> mono1 = flux.then();
mono1.subscribe(null, null, () -> System.out.println("yes1"));
flux.subscribe(System.out::println, System.err::println);
}
}
I thought the result would be:
1
2
3
yes1
But the actual result is:
yes1
1
2
3
Please help me. And I hope you can write some code examples and no restriction to this example I wrote here.
then() is used when you don't care about what elements a publisher has output, you only care about when it finishes. So it takes an existing publisher, throws all of its elements away, and then propagates the completion signal.
However, I'm not sure your misunderstanding here comes from the then() operator itself, but instead the behaviour of reactive publishers upon subscription (and possibly that all reactive chains are immutable.) Of relevance here:
Nothing happens until you subscribe;
You can subscribe multiple times, and the entire chain will execute again every time you subscribe.
Whenever you use an operator on a chain, a new copy of that reactive chain is created with the new operator appended - you don't mutate the existing chain.
So in this case, line by line:
We have a flux chain which will emit 3 elements immediately upon subscription (1,2,3)
We have a mono1 chain which will emit nothing and complete immediately (since it's created from the above flux, so has nothing to wait for)
We subscribe to the mono1 chain with a subscriber that does nothing aside from print yes1 when it completes - and as per the above, it completes immediately and prints yes1
We subscribe to the original flux with a subscriber that prints each element as it's emitted, hence it prints 1, 2 & 3.
If you just want a notification (yes1) to be printed when the publisher completes, then you could just scrap the mono1 entirely, doing:
flux.subscribe(System.out::println, System.err::println, () -> System.out.println("yes1"));
...which would give you the output you expect.
The then() operator can really serve no use here, since it will always complete immediately. As above. you would only really use it in the case where you have to wait for something to complete, and you don't care about whatever it might emit in the meantime - say, for example, you have a flux of database updates you want to execute and you only care when they're all done, or you're making a PUT request you don't need a result from, you just need to know when it's complete.

How to perform a completable and then return a processed result with single?

I hope my question is clear enough.
Let's say your have an API that performs requests over a network backed by IO-streams (input and output).
Using RxJava (which I am debuting with), I would think it could be possible to do the following:
public Single<MyData> getMyDataFromApi() {
return requestMyData()
.map/flat/then()->waitAndprocessData() // here is the missing link for me. What can I use ?
.andThen()->returnData()
As you will understand, the method requestMyData returns a Completable which sole responsibility and only task it to perform said request (IO-type operation).
Then, upon performing the request, the remote entity shall process it and return a result the requested MyData object by performing an IO-type operation as well.
The key-point here, is that I work with streams (both input and output) which reading and writing operations are obviously performed in separate IO threads (using Schedulers.io()).
So in the end, is there a way so that my getMyDataFromApi() method does the following :
Perform the request -> it's a completable
Wait for the result -> something like a subscribe ? but without splitting the chain
Process the result -> it's a single or can be a lambada in map method
Return the result -> final element, obviously a single
To conclude, I strongly believe that requestMyData's signature should be that of a Single, because it's getter and I am expecting a result or an error.
Without having the implementation of the methods is quite hard to understand the real problem.
If requestMyData returns a Completable and waitAndprocessData a Single, you can do the following:
return requestMyData().andThen(waitAndprocessData());
Anyway remember that a Completable is computation without any value but only indication for completion (or exceptions).

Categories

Resources