Skip to content

Instantly share code, notes, and snippets.

@imminent
Last active June 13, 2023 02:50
Show Gist options
  • Save imminent/ac7f2a222e22f4009af4 to your computer and use it in GitHub Desktop.
Save imminent/ac7f2a222e22f4009af4 to your computer and use it in GitHub Desktop.

Revisions

  1. imminent revised this gist Nov 18, 2015. 2 changed files with 10 additions and 1 deletion.
    2 changes: 1 addition & 1 deletion RetryCallAdapterFactory.java
    Original file line number Diff line number Diff line change
    @@ -119,7 +119,7 @@ public void onFailure(Throwable throwable) {
    if (mRetries < mMaxRetries) {
    retryCall();
    } else {
    mDelegate.onFailure(new TimeoutError());
    mDelegate.onFailure(new TimeoutError(throwable));
    }
    }

    9 changes: 9 additions & 0 deletions TimeoutError.java
    Original file line number Diff line number Diff line change
    @@ -3,4 +3,13 @@
    import java.io.IOException;

    public class TimeoutError extends IOException {
    private static final long serialVersionUID = -6469766654369165864L;

    public TimeoutError() {
    super();
    }

    public TimeoutError(Throwable cause) {
    super(cause);
    }
    }
  2. imminent revised this gist Nov 18, 2015. 1 changed file with 4 additions and 3 deletions.
    7 changes: 4 additions & 3 deletions RetryCallAdapterFactory.java
    Original file line number Diff line number Diff line change
    @@ -18,8 +18,10 @@
    * Retries calls marked with {@link Retry}.
    */
    public class RetryCallAdapterFactory implements CallAdapter.Factory {

    private final ScheduledExecutorService mExecutor;

    private RetryCallAdapterFactory() {
    mExecutor = Executors.newScheduledThreadPool(1);
    }

    public static RetryCallAdapterFactory create() {
    @@ -39,7 +41,6 @@ public CallAdapter<?> get(final Type returnType, Annotation[] annotations, Retro
    final boolean shouldRetryCall = hasRetryAnnotation;
    final int maxRetries = value;
    final CallAdapter<?> delegate = retrofit.nextCallAdapter(this, returnType, annotations);
    final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    return new CallAdapter<Object>() {
    @Override
    public Type responseType() {
    @@ -48,7 +49,7 @@ public Type responseType() {

    @Override
    public <R> Object adapt(Call<R> call) {
    return delegate.adapt(shouldRetryCall ? new RetryingCall<>(call, executor, maxRetries) : call);
    return delegate.adapt(shouldRetryCall ? new RetryingCall<>(call, mExecutor, maxRetries) : call);
    }
    };
    }
  3. imminent revised this gist Nov 18, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RetryCallAdapterFactory.java
    Original file line number Diff line number Diff line change
    @@ -127,7 +127,7 @@ private void retryCall() {
    @Override
    public void run() {
    final Call<T> call = mCall.clone();
    call.enqueue(new RetryingCallback<>(call, mDelegate, mExecutor, mRetries + 1));
    call.enqueue(new RetryingCallback<>(call, mDelegate, mExecutor, mMaxRetries, mRetries + 1));
    }
    }, (1 << mRetries) * 1000 + random.nextInt(1001), TimeUnit.MILLISECONDS);
    }
  4. imminent revised this gist Oct 16, 2015. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions TimeoutError.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    package com.example.api;

    import java.io.IOException;

    public class TimeoutError extends IOException {
    }
  5. imminent created this gist Oct 15, 2015.
    19 changes: 19 additions & 0 deletions Api.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    package com.example.api;

    import java.util.Map;

    import retrofit.Call;
    import retrofit.http.Body;
    import retrofit.http.GET;
    import retrofit.http.POST;

    public interface Api {

    @Retry(1)
    @GET("action")
    Call getAction();

    @Retry
    @POST("action")
    Call postAction(@Body Map<String, Object> requestBody);
    }
    18 changes: 18 additions & 0 deletions ApiClient.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,18 @@
    package com.example.api;

    import com.squareup.okhttp.OkHttpClient;

    import retrofit.GsonConverterFactory;
    import retrofit.Retrofit;

    public class ApiClient {
    public final Api api;

    public ApiClient(OkHttpClient client) {
    final Retrofit retrofit = new Retrofit.Builder()
    .addCallAdapterFactory(RetryCallAdapterFactory.create())
    .baseUrl("https://api.example.com")
    .build();
    api = retrofit.create(Api.class);
    }
    }
    18 changes: 18 additions & 0 deletions Retry.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,18 @@
    package com.example.api;

    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;

    import static java.lang.annotation.ElementType.METHOD;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;

    /**
    * Makes the Call retry on failure
    */
    @Documented
    @Target(METHOD)
    @Retention(RUNTIME)
    public @interface Retry {
    int value() default 3;
    }
    135 changes: 135 additions & 0 deletions RetryCallAdapterFactory.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,135 @@
    package com.example.api;

    import java.io.IOException;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Type;
    import java.util.Random;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;

    import retrofit.Call;
    import retrofit.CallAdapter;
    import retrofit.Callback;
    import retrofit.Response;
    import retrofit.Retrofit;

    /**
    * Retries calls marked with {@link Retry}.
    */
    public class RetryCallAdapterFactory implements CallAdapter.Factory {

    private RetryCallAdapterFactory() {
    }

    public static RetryCallAdapterFactory create() {
    return new RetryCallAdapterFactory();
    }

    @Override
    public CallAdapter<?> get(final Type returnType, Annotation[] annotations, Retrofit retrofit) {
    boolean hasRetryAnnotation = false;
    int value = 0;
    for (Annotation annotation : annotations) {
    if (annotation instanceof Retry) {
    hasRetryAnnotation = true;
    value = ((Retry) annotation).value();
    }
    }
    final boolean shouldRetryCall = hasRetryAnnotation;
    final int maxRetries = value;
    final CallAdapter<?> delegate = retrofit.nextCallAdapter(this, returnType, annotations);
    final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    return new CallAdapter<Object>() {
    @Override
    public Type responseType() {
    return delegate.responseType();
    }

    @Override
    public <R> Object adapt(Call<R> call) {
    return delegate.adapt(shouldRetryCall ? new RetryingCall<>(call, executor, maxRetries) : call);
    }
    };
    }

    static final class RetryingCall<T> implements Call<T> {
    private final Call<T> mDelegate;
    private final ScheduledExecutorService mExecutor;
    private final int mMaxRetries;

    public RetryingCall(Call<T> delegate, ScheduledExecutorService executor, int maxRetries) {
    mDelegate = delegate;
    mExecutor = executor;
    mMaxRetries = maxRetries;
    }

    @Override
    public Response<T> execute() throws IOException {
    return mDelegate.execute();
    }

    @Override
    public void enqueue(Callback<T> callback) {
    mDelegate.enqueue(new RetryingCallback<>(mDelegate, callback, mExecutor, mMaxRetries));
    }

    @Override
    public void cancel() {
    mDelegate.cancel();
    }

    @SuppressWarnings("CloneDoesntCallSuperClone" /* Performing deep clone */)
    @Override
    public Call<T> clone() {
    return new RetryingCall<>(mDelegate.clone(), mExecutor, mMaxRetries);
    }
    }

    // Exponential backoff approach from https://developers.google.com/drive/web/handle-errors
    static final class RetryingCallback<T> implements Callback<T> {
    private static Random random = new Random();
    private final int mMaxRetries;
    private final Call<T> mCall;
    private final Callback<T> mDelegate;
    private final ScheduledExecutorService mExecutor;
    private final int mRetries;

    RetryingCallback(Call<T> call, Callback<T> delegate, ScheduledExecutorService executor, int maxRetries) {
    this(call, delegate, executor, maxRetries, 0);
    }

    RetryingCallback(Call<T> call, Callback<T> delegate, ScheduledExecutorService executor, int maxRetries, int retries) {
    mCall = call;
    mDelegate = delegate;
    mExecutor = executor;
    mMaxRetries = maxRetries;
    mRetries = retries;
    }

    @Override
    public void onResponse(Response<T> response, Retrofit retrofit) {
    mDelegate.onResponse(response, retrofit);
    }

    @Override
    public void onFailure(Throwable throwable) {
    // Retry failed request
    if (mRetries < mMaxRetries) {
    retryCall();
    } else {
    mDelegate.onFailure(new TimeoutError());
    }
    }

    private void retryCall() {
    mExecutor.schedule(new Runnable() {
    @Override
    public void run() {
    final Call<T> call = mCall.clone();
    call.enqueue(new RetryingCallback<>(call, mDelegate, mExecutor, mRetries + 1));
    }
    }, (1 << mRetries) * 1000 + random.nextInt(1001), TimeUnit.MILLISECONDS);
    }
    }
    }