eclipse-vertx / vert.x

Vert.x is a tool-kit for building reactive applications on the JVM
http://vertx.io
Other
14.31k stars 2.08k forks source link

Retry on Future #4619

Open pendula95 opened 1 year ago

pendula95 commented 1 year ago

Introduce configurable retry mechanism on Future interface.

There are many use causes where this feature would be useful and would shorten a lot of boilerplate code that is copied and reused on many places in user code. Example is, retry of WebClient HTTP request that fails for recoverable reasons.

This is already available on rx api via multiple functions:

  1. retry() Repeatedly re-subscribes to the current Single indefinitely if it fails with an onError.
  2. retry(@NonNull BiPredicate<? super Integer,? super Throwable> predicate) Re-subscribe to the current Single if the given predicate returns true when the Single fails with an onError.
  3. retry(long times) Repeatedly re-subscribe at most the specified times to the current Single if it fails with an onError.
  4. retry(long times, @NonNull Predicate<? super Throwable> predicate) Repeatedly re-subscribe at most times or until the predicate returns false, whichever happens first if it fails with an onError.
  5. retry(@NonNull Predicate<? super Throwable> predicate) Re-subscribe to the current Single if the given predicate returns true when the Single fails with an onError.
  6. retryUntil(@NonNull BooleanSupplier stop) Retries until the given stop function returns true.
  7. retryWhen(@NonNull Function<? super Flowable<Throwable>,? extends org.reactivestreams.Publisher<?>> handler) Re-subscribes to the current Single if and when the Publisher returned by the handler function signals a value.

I would suggest to add support for 3,4,5 as in my opinion they seam as the most common and useful ones.

I would be willing to contribute this solution. This can be a nice addition to vertx5 API

vietj commented 1 year ago

retry means using a function, at the same time we do have a circuit breaker in vertx that is somehow related to that, so it would be good to think about a generic mechanism that makes this, perhaps in circuit breaker

pendula95 commented 1 year ago

Ok let my try to explain what I am trying to do via code usecase. I see that there is a similar functionality via circuit breaker but it looks like an feature that is designed for microservice management and architecture. What I want simply is to retry a future when specific conditions are meet. I don't like an idea of importing a microservice manager like circuit breaker in order to get simple functionality.

Lets say I have simple HttpClient that uses keep alive with a server. Pool is made an maintained. Lets say for some unknown reason server process restarts unexpectedly. In this case depending on the pool size my x requests will fail because all connections are dead but this does not mean I can not deliver my HTTP call. In order to avoid this with vert.x core I would need to write something like this:

public static Future<HttpResponse<Buffer>> sendRequestWithRetry(int retryCount) {
        Promise<HttpResponse<Buffer>> promise = Promise.promise();
        webClient.getAbs("http://localhost:9999")
                .send()
                .onSuccess(promise::complete)
                .onFailure(ex -> {
                    if (ex.getMessage() != null && ex.getMessage().contains("Connection reset by peer") && retryCount < 3) {
                        sendRequestWithRetry(retryCount + 1)
                                .onSuccess(promise::complete)
                                .onFailure(promise::fail);
                    } else {
                        promise.fail(ex);
                    }
                });
        return promise.future();
    }

So anywhere in the code I need to retry I would need to write helper retry functions like this. Also good usecase is with redis connector where I would like to retry a future if the response is empty (date still did not make it) and I would like to retry 3 times with delay of 300 ms as data might be late and I want to get it.

So in my opinion this should be migrate to Future API and then circuit breaker can wrap it in its own API as it currently does.

tsegismont commented 1 year ago

A future is a (deferred) value. You cannot retry a value.

What you can retry is an operation which is lazily defined. That is possible in two ways with Vert.x:

Le ven. 10 mars 2023 à 16:25, Lazar Bulić @.***> a écrit :

Ok let my try to explain what I am trying to do via code usecase. I see that there is a similar functionality via circuit breaker but it looks like an feature that is designed for microservice management and architecture. What I want simply is to retry a future when specific conditions are meet. I don't like an idea of importing a microservice manager like circuit breaker in order to get simple functionality.

Lets say I have simple HttpClient that uses keep alive with a server. Pool is made an maintained. Lets say for some unknown reason server process restarts unexpectedly. In this case depending on the pool size my x requests will fail because all connections are dead but this does not mean I can not deliver my HTTP call. In order to avoid this with vert.x core I would need to write something like this:

public static Future<HttpResponse> sendRequestWithRetry(int retryCount) { Promise<HttpResponse> promise = Promise.promise(); webClient.getAbs("http://localhost:9999") .send() .onSuccess(promise::complete) .onFailure(ex -> { if (ex.getMessage() != null && ex.getMessage().contains("Connection reset by peer") && retryCount < 3) { sendRequestWithRetry(retryCount + 1) .onSuccess(promise::complete) .onFailure(promise::fail); } else { promise.fail(ex); } }); return promise.future(); }

So anywhere in the code I need to retry I would need to write helper retry functions like this. Also good usecase is with redis connector where I would like to retry a future if the response is empty (date still did not make it) and I would like to retry 3 times with delay of 300 ms as data might be late and I want to get it.

So in my opinion this should be migrate to Future API and then circuit breaker can wrap it in its own API as it currently does.

— Reply to this email directly, view it on GitHub https://github.com/eclipse-vertx/vert.x/issues/4619#issuecomment-1463963240, or unsubscribe https://github.com/notifications/unsubscribe-auth/AALOLNVEQAXWCJJDZSYJG3TW3NBXBANCNFSM6AAAAAAVDHR7KI . You are receiving this because you are subscribed to this thread.Message ID: @.***>

dreamlike-ocean commented 1 year ago
io.smallrye.reactive smallrye-mutiny-vertx-web-client 3.3.0

This looks like what you need

thced commented 1 year ago

It's not as elegant/dynamic, just another means to model it. I think it should work(?):

public static Future<HttpResponse<Buffer>> sendRequestWithRetry(int retryCount) {
  return sendRequest()
    .recover(retry())
    .recover(retry())
    .recover(retry());
}

private static Function<Throwable, Future<HttpResponse<Buffer>>> retry() {
  return ex -> {
    if (Objects.requireNonNullElse(ex.getMessage(), "").contains("Connection reset by peer")) {
      return sendRequest();
    } else {
      return Future.failedFuture(ex);
    }
  };
}

private static Future<HttpResponse<Buffer>> sendRequest() {
  return webClient.getAbs("http://localhost:9999").send();
}