spring webclient: retry with backoff on specific error - java

i'd like to retry the request 3 times after waiting 10sec when response is 5xx. but i don't see a method that I can use. On object
WebClient.builder()
.baseUrl("...").build().post()
.retrieve().bodyToMono(...)
i can see methods:
retrying on condition with retry count but no delay
.retry(3, {it is WebClientResponseException && it.statusCode.is5xxServerError} )
retrying with backoff and number of times but no condition
.retryBackoff
there is also a retryWhen but i'm not sure how to use it

With reactor-extra you could do it like:
.retryWhen(Retry.onlyIf(this::is5xxServerError)
.fixedBackoff(Duration.ofSeconds(10))
.retryMax(3))
private boolean is5xxServerError(RetryContext<Object> retryContext) {
return retryContext.exception() instanceof WebClientResponseException &&
((WebClientResponseException) retryContext.exception()).getStatusCode().is5xxServerError();
}
Update:
With new API the same solution will be:
.retryWhen(Retry.fixedDelay(3, Duration.ofSeconds(10))
.filter(this::is5xxServerError));
//...
private boolean is5xxServerError(Throwable throwable) {
return throwable instanceof WebClientResponseException &&
((WebClientResponseException) throwable).getStatusCode().is5xxServerError();
}

You can do this taking the following approach:
Use the exchange() method to obtain the response without an exception, and then throw a specific (custom) exception on a 5xx response (this differs from retrieve() which will always throw WebClientResponseException with either a 4xx or 5xx status);
Intercept this specific exception in your retry logic;
Use reactor-extra - it contains a nice way to use retryWhen() for more complex & specific retries. You can then specify a random backoff retry that starts after 10 seconds, goes up to an arbitrary time and tries a maximum of 3 times. (Or you can use the other available methods to pick a different strategy of course.)
For example:
//...webclient
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError()) {
return Mono.error(new ServerErrorException());
} else {
//Any further processing
}
}).retryWhen(
Retry.anyOf(ServerErrorException.class)
.randomBackoff(Duration.ofSeconds(10), Duration.ofHours(1))
.maxRetries(3)
)
);

the retryWhen with Retry.anyOf and Retry.onlyIf are deprecated I assume. I found this approach useful, and it allows us to process and throw a User defined exception.
for example :
retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.filter(error -> error instanceof UserDefinedException/AnyOtherException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new UserDefinedException(retrySignal.failure().getMessage())))

// ...
.retryWhen(
backoff(maxAttempts, minBackoff)
.filter(throwable -> ((WebClientResponseException) throwable).getStatusCode().is5xxServerError()))
// ...

Adding only withThrowable to your existing code can make it work. This has worked for me. You can try something like this :
For example :
.retryWhen(withThrowable(Retry.any()
.doOnRetry(e -> log
.debug("Retrying to data for {} due to exception: {}", employeeId, e.exception().getMessage()))
.retryMax(config.getServices().getRetryAttempts())
.backoff(Backoff.fixed(Duration.ofSeconds(config.getServices().getRetryBackoffSeconds())))))

here's how i do it:
.retryWhen(retryBackoffSpec())
private RetryBackoffSpec retryBackoffSpec() {
return Retry.backoff(RETRY_ATTEMPTS, Duration.ofSeconds(RETRY_DELAY))
.filter(throwable -> throwable instanceof yourException);
}

Related

How can catch MonoError?

I need to catch MonoError and stop an application with ErrorResponse, but the application works as I did not expect.
My code:
return checkText(text)
.then(getWordsFromText(text))
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
public Mono<Void> checkText(String text) {
if (text == null) {
return Mono.error(new Exception("wrong text"));
}
return Mono.empty();
}
my problem is that if text param is null -> I fall into getWordsFromText method. This is an incorrect execution, because if the text parameter is equal to null, then the application must exit with an error (with ErrorResponse).
I fixed it as (replacing 'then' to 'flatMap'):
return checkText(text)
.flatMap(voidParam -> getWordsFromText(text)) //replaced 'then' to 'flatMap'
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
and now it's working correctly. If text param is null I miss the call getWordsFromText method and fall in error handling (onErrorResume).
But I think using flatMap in my case is not a good idea, I don't like how it looks: .flatMap(voidParam -> ...
Can you have any ideas how possible to do better? (without 'flatMap')
In the first snippet, the call to getWordsFromText() is made while building your main reactive pipeline, before it is even subscribed to (i.e. at assembly time). The reason it works as intended in the second snippet is that flatMap only creates the inner publishers (and subsequently subscribes to them) as it receives elements from upstream (i.e. at subscription time).
In this case if you want to replace the flatMap you could try this: .then(Mono.fromCallable(() -> getWordsFromText(text)))

How to prevent Mono from being cancelled?

I am trying to implement something as a "race condition". This race condition must follow these situations:
Fire two simultaneous HTTP calls.
Return the response from the first call that was completed successfully.
Handle the last call. (The most important thing here is that I can not discard the last call, I do need to handle the result of it: whatever its status, success or fail).
This sample of code is the most close of the solution that I have achieved:
Mono<StatusMock> monoA = webClient.get()
.uri("https://some.url.a")
.retrieve()
.bodyToMono(StatusMock.class)
.subscribeOn(Schedulers.boundedElastic());
Mono<StatusMock> monoB = webClient.get()
.uri("https://some.url.b")
.retrieve()
.bodyToMono(StatusMock.class)
.doOnSuccess(this::verifyBody)
.onErrorStop()
.subscribeOn(Schedulers.boundedElastic());
StatusMock statusMock = Flux.first(monoA, monoB)
.blockFirst();
if (statusMock != null) {
return statusMock.getStatus();
}
return "empty";
}
private void verifyBody(StatusMock statusMock) {
if (statusMock.getStatus().contains("error")) {
log.error("throwing an exception");
throw new RuntimeException("error");
}
}
public class StatusMock {
private String status; // getters and setters implicit
}
In this example I used the Flux.first method, and it helps me a lot returning the first call, but it discards (cancel) the second one which is a problem since I need the result of the last call as well.
Is there any solution to this logic? Here I am using Spring Project Reactor, but I accept any library or framework that could help me with this situation.
You can use cache operator on the Monos to prevent them from being cancelled:
Mono<StatusMock> monoA = webClient.get()
// ...
.cache();
Mono<StatusMock> monoB = webClient.get()
// ...
.cache();
Mono.firstWithSignal(monoA, monoB);

How to get an element that caused an exception in Flux?

Let's say I have an array of ids: [9, 8, 7, 6].
I do some processing and one element causes to throw an exception. I want to handle this situation on my own way (let's say log it) and let the other elements go with the flow.
How can I know which one was it? I need to have this element in my onError processing.
Flux.fromArray(myArray)
.flatMap(element -> {
var foo = processMyEl(element);
return anotherProcess(foo); // this returns Mono
})
.onErrorOperator(element -> handleMyError(element)) // this line is what I need
So, what I saw, there's this almost nice .onErrorContinue((error, obj) -> that emits an error and an object.
But this obj is not the element that caused the exception but the object that did so. It happens inside of my processing methods and it doesn't have to be the same type of object every time.
.onErrorReturn(...) - not really what I want
.doOnError(error -> - no information of my element
.onErrorResume(error -> - same as above
there were suggestions that I can create my own Exception and pass there the element and then retrieve it from the exception. But how should I throw the exception?
Should I go with an old way of try catch:
Flux.fromArray(myArray)
.flatMap(el -> {
try {
var foo = processMyEl(el);
return anotherProcess(foo); // this returns Mono
} catch (Exception e) {
return Mono.error(new MyException(el));
}
})
.onErrorOperator(error -> handleMyError(error.getElement()))
It doesn't look well
Edit:
Not only it looks bad, but also doesn't work. The exception is not caught at all and triggers directly doOnTerminate() and stops the whole stream
Update:
Thanks to #JEY I used .onErrorResume() inside flatMap.
I also transformed first method to be a reactive stream by Mono.defer(() -> Mono.just(processMyEl(el))).
Just as a note: using Mono.defer() allows me to use onErrorResume since Mono.just() cannot signal errors.
Final code looks like this:
Flux.fromArray(myArray)
.flatMap(element -> Mono.defer(() -> Mono.just(processMyEl(element)))
.onErrorResume(th -> handleMyError(element, th))
)
.flatMap(foo -> anotherProcess(foo)
.onErrorResume(th -> handleMyError(foo, th)
)
Where:
private Mono<> handleMyError(el, th) {
// handling code
return Mono.empty()
}
As requested by #Kamil I'll add my comments as an answer:
You should just handle the error in the flatMap and return a Mono.empty() to discard it do something like:
Flux.fromArray(myArray)
.flatMap(el -> anotherProcess(processMyEl(el)).onErrorResume(th -> handleError(th, el))
With handle error like:
Mono<Void> handleError(Throwable th, Object element) {
LOG.error("An error occurred on {}", element, th);
return Mono.empty()
}
Or if you want to do something more complex that require async:
Mono<Void> handleError(Throwable th, Object element) {
return doSomethingThaReturnFluxOrMono(element).then();
}
} catch (Exception e) {
throw new MyException(el, e);
}

Preventing RxJava from wrapping my custom exception into composite one

I was given a task to make implementation using RxJava, and safeguard in such way, that if any error happens, it gets wrapped into custom exception.
Problem is, that regardless of what I do RxJavaPlugin decides to wrap my exception into CompositeException even when there is only one. Which fails tests.
I've tried everything I could find on the google all the way up to actually overwriting RxJavaPlugin's global error handler, but it ignores attempted changes.
implementation of function that is supposed to throw it at the moment of writing this post
Single<BigDecimal> summarizeConfirmedTransactions() throws SummarizationException {
try{
Observable<Transaction> observableTransactions = transactions.get()
.doOnError(throwable -> {
Exceptions.propagate(throwable);
});
Observable<Confirmation> observableConfirmations = confirmations.get()
.doOnError(throwable -> {
Exceptions.propagate(throwable);
});
return Observable.zip(observableTransactions, observableConfirmations, (t, c) -> new ConfirmableTransaction(t.transactionId, c.isConfirmed, t.value))
.filter(confirmableTransaction -> confirmableTransaction.isConfirmed)
.map(t -> t.value)
.reduce(BigDecimal::add)
.toSingle()
.doOnError(throwable -> {
Exceptions.propagate(throwable);
});
}catch(Exception e)
{
throw new SummarizationException(e.getMessage());
}
Assertion in test fails because exception ultimately thrown is CompositeException with single exception inside of it. and I am required to have it be of SummarizationException.class
big thanks in advance.
Edit:
On request, here is code used to test the solution. I am not allowed to modify this one.
#Test
public void shouldWrapErrorIntoException() {
final ConfirmedTransactionSummarizer summarizer =
new ConfirmedTransactionSummarizer(ALL_CONFIRMED::transactions, () -> Observable.error(new RuntimeException("Booom")));
summarizer
.summarizeConfirmedTransactions()
.subscribe(testObserver);
testObserver.assertError(SummarizationException.class);
testObserver.assertErrorMessage("Booom");
}
PS. I've asked the giver of the task, he said that "I'm the only one with such problem" and that I should not overcomplicate things and go for easiest solution.... which results in composite exception of 3 exceptions - one of which is my exception wrap and other two are instances of RuntimeException inserted by test code.
Ok, so after a little bit of more digging, and with helpful tip from a friend I've managed to nail down intention of the task:
What I was supposed to do there was:
Single<BigDecimal> summarizeConfirmedTransactions() throws SummarizationException {
Observable<Transaction> observableTransactions = transactions.get();
Observable<Confirmation> observableConfirmations = confirmations.get();
return Observable.zip(observableTransactions, observableConfirmations,
(t, c) -> new ConfirmableTransaction(c.isConfirmed, t.value))
.filter(confirmableTransaction -> confirmableTransaction.isConfirmed)
.map(t -> t.value)
.reduce(BigDecimal::add)
.toSingle()
.onErrorResumeNext(th -> Single.error(new SummarizationException(th.getMessage())));
}
TL:DR I was not supposed to "wrap" errors into thrown exception but wrap them into error response containing exception.
One way to handle this is by using try-catch block in your test, unwraping CompositeException and then asserting caught exception.
fun testSummarizationException() {
try {
summarizeConfirmedTransactions()
} catch (ex: Exception) {
val customException = (ex as? CompositeException)?.exceptions?.get(0)
// assert to make sure thrown exception is of custom type
assert(customException is SummarizationException)
}
}
This is where CompositeException is unwrapped to get custom exception.
val customException = (ex as? CompositeException)?.exceptions?.get(0)
Exception is type-casted to CompositeException if it's permissible. If casting is not allowed for this type, this will return null and fails the test.

Project Reactor: Designing a reactive API

I have a map function which defined as follows: Mono<OUT> map(IN in)
Here's a concrete example:
public Mono<Integer> map(String s) {
return Mono.fromCallable(() -> {
try {
Thread.sleep(1_000); // simulate HTTP request
return 1;
} catch (Exception e) {}
return -1; // need to return something.
});
}
The problem is that in case of an error (i.e. IOException), we still need to return some output. There's also a possibility that there might no be an answer (but no error occurred)
One solution could be an Optional::empty but I think it's cumbersome. Preferably, I'd like to return Mono::empty if an error occurred.
The reason is, Mono::empty gets consumed by the subscriber without any further handling. Here's an example:
Flux.just(
Mono.just("123"),
Mono.empty(),
Mono.just("456")
).flatMap(s -> s)
.subscribe(System.out::println);
The output would be:
123
456
How can achieve the same behaviour?
What should map look like?
EDIT:
Rethinking it, maybe I better off return some container (like Optional) or a custom one (Result) which can be empty.
If I understand correctly, here's what you need:
return Mono.fromCallable(() -> {
Thread.sleep(1_000); // simulate HTTP request
return 1;
}).onErrorResume(_ -> Mono.empty())

Categories

Resources