2

How can I exhaust retries after a number of failed async request calls? I am using AsyncHttpClient to send requests to our server. In case of request timeouts, connection exceptions, etc., I would like the client to retry N times and throw a custom exception. The calling method should receive this exception or it can be left unhandled.

// calls post
public void call(String data) throws CustomException {
    
    asyncHttpClient.post(data, 10);

}
// posts data to http endpoint
public void post(String data, int retries) throw CustomException {
    // if retries are exhausted, throw CustomException to call()
    if (retry <= 0) {
        throw new CustomException("exc");
    }

    BoundRequest request = httpClient.preparePost("http_endpoint");
    ListenableFuture<Response> responseFuture = httpClient.post(request);
 
    responseFuture.addListener(() -> {
        Response response = null;
        int status = 0;
        try {

            response = responseFuture.get();
            status = response.getStatusCode();

            // HTTP_ACCEPTED = {200, 201, 202}
            if (ArrayUtils.contains(HTTP_ACCEPTED, status)) {
                // ok
            } else {
                sleep(10);
                post(data, retry - 1);
            }
        } catch (InterruptedException e) {
            sleep(10);
            post(data, retry - 1);

        } catch (ExecutionException e) {
            // ConnectionException
            // RequestTimeoutException
            sleep(10); // 10 seconds
            post(data, retry - 1);
        } catch (Exception e) {
            sleep(10); // 10 seconds
            post(data, retry - 1 );
        } finally {
            responseFuture.done();
        }
    },  Runnable::run);

} 

This approach has a few problems:

  1. uses recursive calls to retry.
  2. the CustomException seems like it is never thrown and after retries == 0, the control goes back to the finally block.
...
} catch (ExecutionException e) {
    // ConnectionException
    // RequestTimeoutException
    sleep(10); // 10 seconds
    try {
        post(data, retry - 1);
    } catch (CustomException e) {
    }
}
...
deduper
  • 1,944
  • 9
  • 22
EasyQuestions
  • 327
  • 1
  • 10
  • 23
  • 1
    what packages or libs are you using? retrofit? okhttp? volley? explain completely your code and packages that you are using specifically these codes . – A Farmanbar Sep 27 '20 at 00:00
  • What is `responseFuture` an instance of ? The declaration is not included in the code provided. – ggordon Sep 27 '20 at 13:27
  • it is an instance of `ListenableFuture`, edited the code. – EasyQuestions Sep 27 '20 at 14:59
  • 2
    How satisfied are you with the two answers so far? Does either of them meet your needs? – deduper Oct 01 '20 at 21:05
  • In your very last snippet (*beneath problem #2*) you do *`catch(CustomException e){…}`* within an enclosing *`catch(ExecutionException e){…}`* block. But the only place *`CustomException`* shows up in your larger, first snippet is in the *`if (retry <= 0) {…}`* at the very top. What is your intention for omitting the nested *`catch(CustomException e){…}`* from the large snippet and only listing it separately in a different snippet? Is there any significance to it not being present in the larger snippet? The way you've split it out, it's not clear whether or not you intend it to be used. TIA. – deduper Oct 03 '20 at 13:16

2 Answers2

3

There is a predefined function in the AsyncHttpClient to handle MaxRetries,

The code below shows a simple implementation

AsyncHttpClientConfig cf = new DefaultAsyncHttpClientConfig.Builder().setMaxRequestRetry(5).setKeepAlive(true).build()
final AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient(cf);

You can remove the retry logic of yours and let AsyncHttpClient to handle the same.

Vishal V
  • 215
  • 1
  • 8
2

Alright, so tried to reproduce what you were trying to achieve with your code, but immediately realized that your CustomException only works if it is of type RuntimeException. The reason why is that you want to throw the exception during runtime and in another thread.

The code below shows a simple implementation of the Exception. Keep in mind that not all RuntimeExceptions stop the program. This is explained in this thread. So if you want to terminate the program, you have to manually stop it.

public class CustomException extends RuntimeException {

    public CustomException(String msg) {
        super(msg);
        // print your exception to the console
 
        // optional: exit the program
        System.exit(0);
    }

}

I changed the remaining of your implementation, so that you don't have to make recursive calls anymore. I removed the callback method and instead call the get() method, which waits for the request to finish. But since I am executing all of this in a separate thread it should be running in the background and not main thread.

public class Main {

    private final AsyncHttpClient httpClient;
    private final int[] HTTP_ACCEPTED = new int[]{200, 201, 202};

    private final static String ENDPOINT = "https://postman-echo.com/post";

    public static void main(String[] args) {
        String data = "{message: 'Hello World'}";
        Main m = new Main();
        m.post(data, 10);

    }

    public Main() {
        httpClient = asyncHttpClient();
    }

    public void post(final String data, final int retry) {

        Runnable runnable = () -> {
            int retries = retry;

            for (int i = 0; i < retry; i++) {
                Request request = httpClient.preparePost(ENDPOINT)
                        .addHeader("Content-Type", "application/json")
                        .setBody(data)
                        .build();

                ListenableFuture<Response> responseFuture = httpClient.executeRequest(request);
                try {
                    Response response = responseFuture.get();
                    int status = response.getStatusCode();

                    if (ArrayUtils.contains(HTTP_ACCEPTED, status)) {
                        System.out.println("Successful! Breaking Loop");
                        break;
                    } else {
                        Thread.sleep(10);
                    }

                } catch (InterruptedException | ExecutionException ex) {
                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                }
                retries--;
            }

            System.out.println("Remaining retries: " + retries);

            if (retries <= 0) {
                throw new CustomException("exc");
            }
        };

        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(runnable);
    }

}

Alternative

You could use the same Runnable to make asynchronous calls, without having to wait for future.get(). Each listener will conveniently be called in the same thread, which makes it more efficient for your use case.

public void post2(final String data, final int retry) {
        Request request = httpClient.preparePost(ENDPOINT)
                .addHeader("Content-Type", "application/json")
                .setBody(data)
                .build();

        ListenableFuture<Response> future = httpClient.executeRequest(request);

        MyRunnable runnable = new MyRunnable(retry, future, request);
        future.addListener(runnable, null);
}
public class MyRunnable implements Runnable {

        private int retries;
        private ListenableFuture<Response> responseFuture;
        private final Request request;

        public MyRunnable(int retries, ListenableFuture<Response> future, Request request) {
            this.retries = retries;
            this.responseFuture = future;
            this.request = request;
        }

        @Override
        public void run() {

            System.out.println("Remaining retries: " + this.retries);
            System.out.println("Thread ID: " + Thread.currentThread().getId());


            try {
                Response response = responseFuture.get();
                int status = response.getStatusCode();

                if (ArrayUtils.contains(HTTP_ACCEPTED, status)) {
                    System.out.println("Success!");
                    //do something here

                } else if (this.retries > 0) {
                    Thread.sleep(10);
                    this.execute();
                } else {
                    throw new CustomException("Exception!");
                }

            } catch (InterruptedException | ExecutionException e) {
                this.execute();
            }
        }

        private void execute() {
            this.retries -= 1;
            this.responseFuture = httpClient.executeRequest(this.request);
            this.responseFuture.addListener(this, null);
        }
}
Merve Sahin
  • 1,008
  • 2
  • 14
  • 26