Skip to content

Instantly share code, notes, and snippets.

@cescoffier
Last active March 31, 2023 06:45
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cescoffier/e9abce907a1c3d05d70bea3dae6dc3d5 to your computer and use it in GitHub Desktop.
Save cescoffier/e9abce907a1c3d05d70bea3dae6dc3d5 to your computer and use it in GitHub Desktop.
Various examples of retries with Mutiny
//usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS io.smallrye.reactive:smallrye-mutiny-vertx-web-client:1.1.0
//DEPS io.smallrye.reactive:mutiny:0.7.0
//DEPS org.slf4j:slf4j-nop:1.7.30
package io.vertx.mutiny.retry;
import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.ext.web.client.WebClient;
import java.time.Duration;
import java.util.Random;
/**
* Various examples of retries.
* CTRL+C to exist.
*/
public class Retry {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
WebClient client = WebClient.create(vertx);
// Just start a picky service failing 50% of the times.
startPickyService(vertx);
System.out.println("Hit CTRL+C to exit the program");
// Retrying forever
invokePickyService(client)
.onFailure().retry().indefinitely()
.subscribe().with(item -> System.out.println("[Retrying forever]: " + item));
// Retrying at most twice
invokePickyService(client)
.onFailure().retry().atMost(2)
.subscribe().with(
item -> System.out.println("[Retrying atMost(2)]: " + item),
failure -> System.out.println("[Retrying atMost(2) failed]: " + failure.getMessage())
);
// Retrying with backoff
invokePickyService(client)
.onFailure().retry().withBackOff(Duration.ofSeconds(1)).withJitter(0.2).atMost(10)
.subscribe().with(
item -> System.out.println("[Retrying backoff]: " + item),
failure -> System.out.println("[Retrying backoff failed]: " + failure.getMessage())
);
// Retrying with backoff and deadline
invokePickyService(client)
.onFailure().retry().withBackOff(Duration.ofSeconds(1)).withJitter(0.2)
.expireIn(5000)
.subscribe().with(
item -> System.out.println("[Retrying backoff with deadline]: " + item),
failure -> System.out.println("[Retrying backoff with deadline]: " + failure.getMessage())
);
}
private static Uni<String> invokePickyService(WebClient client) {
return client.getAbs("http://localhost:8080")
.send()
.onItem().transform(resp -> {
if (resp.statusCode() == 200) {
return resp.bodyAsString();
} else {
throw new IllegalStateException(resp.bodyAsString());
}
});
}
private static void startPickyService(Vertx vertx) {
Random random = new Random();
vertx.createHttpServer()
.requestHandler(req -> {
if (random.nextBoolean()) {
req.response().endAndForget("Hello!");
} else {
req.response().setStatusCode(500).endAndForget("Not in a mood");
}
})
.listenAndAwait(8080);
}
}
@Lukpier
Copy link

Lukpier commented Dec 10, 2021

Hi!!

Is it possibile to handle the last failure of the retry policy?

As example, a web service might not be available and throw ServiceUnavailableException.
I'd like to retry at specified number of times (with backoff, jitter, etc.) the call.
After the last failure, i would like to recover it with a fallback Item.. something like that:

asyncThrowingServiceUnavailable
                .onItem()
                .transform(...)
                .onFailure(ServiceUnavailableException.class)
                .retry()
                .withBackOff(Duration.ofMillis(200), Duration.ofMillis(1000))
                .atMost(5)
                .onFailure()
                .recoverWithItem(....)

unfortunately, it seems that the second "onFailure()" overrides the more specific one, and the recover never happens.

@cescoffier
Copy link
Author

Don't you have the exception in the suppressed exception list?

@Lukpier
Copy link

Lukpier commented Dec 10, 2021

Yup, i see: Suppressed: java.lang.IllegalStateException: Retries exhausted: 5/5

I also tried to "catch" the specific IllegalStateException in the second onFailure, but the following recoverWithItem does not take place and the exception in thrown instead.

@cescoffier
Copy link
Author

Which version of mutiny are you using? I think we added the last exception to the suppressed list.

@Lukpier
Copy link

Lukpier commented Dec 10, 2021

i'm using quarkus-resteasy-mutiny with Quarkus version 2.5.0.Final (Mutiny 1.1.2)

@Lukpier
Copy link

Lukpier commented Dec 10, 2021

In addition, i also tried to retrieve the IllegalStateException suppressed showing the number of retries attempted:

.onFailure((failure) -> Arrays.stream(failure.getSuppressed()).anyMatch(f -> f instanceof IllegalStateException))

It exits from the retry block after the first iteration (http client stub is called only once in my test)

@fixingitt
Copy link

Hello @cescoffier,

Is it possible to retry a request and for each request, wait a certain ammount of seconds? The following code waits 30 seconds but it is considering all of the retries. What i would like to do is to wait 30 seconds for the response everytime it retries.

WebClient.create(vertx)
.get(endpoint)
.send()
.onItem()
.transformToMulti(response -> {
........................................................
return Uni.createFrom().item(response.bodyAsJsonObject().getJsonArray("content")).toMulti();
})
.onFailure()
.retry()
.withBackOff(Duration.ofSeconds(5), Duration.ofSeconds(10))
.withJitter(0.7)
.atMost(3)
.onFailure().invoke(error -> log.error("Didn't work.")
.toUni()
.await()
.atMost(Duration.ofSeconds(30));

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment