I´m using retryWhen when a external http request to one of my external services fails.
The problem is that I´m using
RxHelper.toObservable(httpClient.request(method, url))
To get my observable response, and becuase vertx internally use ReadStreamAdapter I cannot use the retryWhen because it´s complain
java java.lang.IllegalStateException: Request already complete
Here a code example:
RxHelper.toObservable(httpClient.request(method, url))
.retryWhen(new ServiceExceptionRetry())
.subscribe(f -> replySuccess(eventMsg, event, f), t -> handleError(t, eventMsg, event));
Any idea how to achieve this?
You can use defer to create an Observable from method and client every time like this:
Observable.defer(() -> RxHelper.toObservable(httpClient.request(method, url)))
.retryWhen(new ServiceExceptionRetry())
.subscribe(f -> replySuccess(eventMsg, event, f), t -> handleError(t, eventMsg, event));
Related
I'm quite new to Mono and Flux. I'm trying to join several downstream API responses. It's a traditional blocking application. I don't wish to collect a list of Mono, I want a List of the payloads returned from the downstream APIs, which I fetch from the Mono. However the 'result' being returned to the controller at times only has some or none of the downstream API responses. What is the correct way to do this? I've read several posts How to iterate Flux and mix with Mono states
you should not call subscribe anywhere in a web application. If this is bound to an HTTP request, you're basically triggering the
reactive pipeline with no guarantee about resources or completion.
Calling subscribe triggers the pipeline but does not wait until it's
complete
Should I be using CompletableFuture?
In my Service I attempted
var result = new ArrayList<List<>>();
List<Mono<X>> monoList = apiCall();
Flux.fromIterable(monoList)
.flatMap(m -> m.doOnSuccess(
x -> {
result.add(x.getData());
}
)).subscribe();
I also attempted the following in controller, but the method returns without waiting for subscribe to complete
var result = new ArrayList<List<X>>();
Flux.concat(
this.service.callApis(result, ...)
).subscribe();
return result;
In my service
public Mono<Void> callApis(List<List<x>> result, ..) {
...
return Flux.fromIterable(monoList)
.flatMap(m -> m.doOnSuccess(
x -> {
result.add(x.getData()...);
}
)).then();
The Project Reactor documentation (which is very good) has a section called Which operator do I need?. You need to create a Flux from your API calls, combine the results, and then return to the synchronous world.
In your case, it looks like all your downstream services have the same API, so they all return the same type and it doesn't really matter what order those responses appear in your application. Also, I'm assuming that apiCall() returns a List<Mono<Response>>. You probably want something like
Flux.fromIterable(apiCall()) // Flux<Mono<Response>>
.flatMap(mono -> mono) // Flux<Response>
.map(response -> response.getData()) // Flux<List<X>>
.collectList() // Mono<List<List<X>>>
.block(); // List<List<X>>
The fromIterable(...).flatMap(x->x) construct just converts your List<Mono<R>> into a Flux<R>.
map() is used to extract the data part of your response.
collectList() creates a Mono that waits until the Flux completes, and gives a single result containing all the data lists.
block() subscribes to the Mono returned by the previous operator, and blocks until it is complete, which will (in this case) be when all the Monos returned by apiCall() have completed.
There are many possible alternatives here, and which is most suitable will depend on your exact use case.
I am using Retrofit2 /RxJava on Android. My POST call works fine in general. But now I added the call from another location. When invoked, there is no HTTP post request sent, no errors/exceptions. HttpInterceptor isn't seeing the call either. Its hard for me to find out what I am doing wrong in this instance.
mAccountManager.onAuthChange()
.subscribeOn(Schedulers.io())
.subscribe(authCode -> {
if (mAccountManager.isLoggedIn(authCode)) {
someOtherApi.getIds()
.subscribeOn(Schedulers.io())
.subscribe(idList -> {
mUserApi.doSomething(idList);
});
}
});
...
#POST("/api/users/dosomething")
Observable<Void> doSomething(#Body IdList idList);
I made sure retrofit api is called by putting breakpoint.
Any idea what I am missing?
Retrofit's rx-adapter creates cold observable, i.e it won't do anything until you subscribe to it. so the problem for you is, you are not subscribing to mUserApi.doSomething(idList); API call.
so just subscribe to it & it'll get called. Also I am not sure about the code of mAccountManager.onAuthChange(), but you should have the onError part as well inside the subscribe to avoid UndeliverableException
i'm currently trying to poll multiple endpoints (which are different)
the problem is i want to keep polling only the endpoints which didn't return the status i need in an aggregated manner so the flow is basically :
build the requests -> merge them to one stream -> poll for response -> check is status matches :
if doesn't wait and redo the flow
if does take the observer out of the stream
this is what i have written and it feels like i'm missing something
Observable.merge(buildRequests())
.repeatWhen(obs -> obs.delay(5000, TimeUnit.MILLISECONDS))
.takeUntil(response -> CheckShouldRepeat(response)).subscribe(whatever());
thanks a bunch!
Observable.fromCallable(() -> buildRequests())
.repeatWhen(o -> CheckShouldRepeat(v -> Observable.timer(5000, TimeUnit.MILLISECONDS)));
This can help.
I need to send some data after user registered. I want to do first attempt in main thread, but if there are any errors, I want to retry 5 times with 10 minutes interval.
#Override
public void sendRegisterInfo(MailData data) {
Mono.just(data)
.doOnNext(this::send)
.doOnError(ex -> logger.warn("Main queue {}", ex.getMessage()))
.doOnSuccess(d -> logger.info("Send mail to {}", d.getRecipient()))
.onErrorResume(ex -> retryQueue(data))
.subscribe();
}
private Mono<MailData> retryQueue(MailData data) {
return Mono.just(data)
.delayElement(Duration.of(10, ChronoUnit.MINUTES))
.doOnNext(this::send)
.doOnError(ex -> logger.warn("Retry queue {}", ex.getMessage()))
.doOnSuccess(d -> logger.info("Send mail to {}", d.getRecipient()))
.retry(5)
.subscribe();
}
It works.
But I've got some questions:
Did I correct to make operation in doOnNext function?
Is it correct to use delayElement to make a delay between executions?
Did the thread blocked when waiting for delay?
And what the best practice to make a retries on error and make a delay between it?
doOnXXX for logging is fine. But for the actual element processing, you must prefer using flatMap rather than doOnNext (assuming your processing is asynchronous / can be converted to returning a Flux/Mono).
This is correct. Another way is to turn the code around and start from a Flux.interval, but here delayElement is better IMO.
The delay runs on a separate thread/scheduler (by default, Schedulers.parallel()), so not blocking the main thread.
There's actually a Retry builder dedicated to that kind of use case in the reactor-extra addon: https://github.com/reactor/reactor-addons/blob/master/reactor-extra/src/main/java/reactor/retry/Retry.java
I'm trying to use RX Java to consume some data coming from a source that keeps sending objects.
I'm wondering how to implement a retry policy for cases in which my own code throws an exception. For example a network exception should trigger a retry with exponential backoff policy.
Some code :
message.map(this::processMessage)
.subscribe((message)->{
//do something after mapping
});
processMessage(message) is the method which contains the risky code that might fail and its the part of code that I want to retry but I dont want to stop the observable from consuming data from the source.
Any thoughts on this?
message
.map(this::processMessage)
.retryWhen(errors -> errors.flatMap(error -> {
if (error instanceof IOException) {
return Observable.just(null);
}
// For anything else, don't retry
return Observable.error(error);
})
.subscribe(
System.out::println,
error -> System.out.println("Error!")
);
or catch the error
message.map(this::processMessage)
.onErrorReturn(error -> "Empty result")
.subscribe((message)->{})
or procses the error
message
.map(this::processMessage)
.doOnError(throwable -> Log.e(TAG, "Throwable " + throwable.getMessage()))
.subscribe(
System.out::println,
error -> System.out.println("Error!")
);
Untested, but retryWhen differs to repeatWhen that is not only called in onComplete.
http://blog.danlew.net/2016/01/25/rxjavas-repeatwhen-and-retrywhen-explained/
-> Each error is flatmapped so that we can either return onNext(null) (to trigger a resubscription) or onError(error) (to avoid resubscription).
Backoff Policy:
source.retryWhen(errors ->
errors
.zipWith(Observable.range(1, 3), (n, i) -> i)
.flatMap(retryCount -> Observable.timer((long) Math.pow(5, retryCount), TimeUnit.SECONDS))
);
flatMap + timer is preferable over delay in this case because it lets us modify the delay by the number of retries. The above retries three times and delays each retry by 5 ^ retryCount, giving you exponential backoff with just a handful of operators!
Take an example from articles:
https://medium.com/#v.danylo/server-polling-and-retrying-failed-operations-with-retrofit-and-rxjava-8bcc7e641a5a#.a6ll8d5bt
http://kevinmarlow.me/better-networking-with-rxjava-and-retrofit-on-android/
They helped me oneday.
Recently I developed the library that exactly suits your needs.
RetrofitRxErrorHandler
If you combine Exponential strategy with backupObservable you will get the expected result.