I'm trying to use a recently added feature from OkHttp 3.12.0: full-operation timeouts.
For that, I also rely on the new Invocation
class from retrofit 2.5.0 that allows me to retrieve the method annotations.
The annotation is:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Timeout {
int value();
TimeUnit unit();
}
The retrofit interface is:
public interface AgentApi {
@Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
@GET("something")
Call<String> getSomething();
}
And the interceptor is:
class TimeoutInterceptor implements Interceptor {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
final Invocation tag = request.tag(Invocation.class);
final Method method = tag != null ? tag.method() : null;
final Timeout timeout = method != null ? method.getAnnotation(Timeout.class) : null;
if (timeout != null) {
chain.call().timeout().timeout(timeout.value(), timeout.unit());
}
return chain.proceed(request);
}
}
I've correctly added the TimeoutInterceptor with .addInterceptor(...)
in the OkHttpClient provided to the Retrofit Builder.
Unfortunately, it doesn't work as I expected. The calls are not failing when the timeout is reached?
Though it works fine when using the chain methods from the interceptor:
chain
.withConnectTimeout(connect, unit)
.withReadTimeout(read, unit)
.withWriteTimeout(write, unit)
Is this because the call timeout must be set before the call is enqueued? (and the Interceptor is triggered too late in the process?), or is this something else?
As you probably guessed, the customization of network connection timeouts on Retrofit can't be done directly. Instead, you have to configure OkHttp, Retrofit's network layer, to customize the connection timeouts: OkHttpClient okHttpClient = new OkHttpClient.
The call timeout spans the entire call: resolving DNS, connecting, writing the request body, server processing, and reading the response body. If the call requires redirects or retries all must complete within one timeout period. The default value is 0 which imposes no timeout. Copyright © 2022 Block, Inc.
Interceptors, according to the documentation, are a powerful mechanism that can monitor, rewrite, and retry the API call. So, when we make an API call, we can either monitor it or perform some tasks.
Configure Timeout Settings If we don't specify a client, Retrofit will create one with default connect and read timeouts. By default, Retrofit uses the following timeouts: Connection timeout: 10 seconds. Read timeout: 10 seconds. Write timeout: 10 seconds.
Unfortunately you are right. It is because OkHttpClient
gets timeouts before it executes interceptors chain. If you look at Response execute()
method in okhttp3.RealCall class you will find line timeout.enter()
that is where OkHttp
schedules timeouts and it is invoked before getResponseWithInterceptorChain()
which is place where interceptors are executed.
Fortunately you can write workaround for that :)
Put your TimeoutInterceptor
in okhttp3
package (you can create that package in your app). That will allow you to have access to RealCall
object which has package visibility. Your TimeoutInterceptor
class should look like this:
package okhttp3;
public class TimeoutInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Invocation tag = request.tag(Invocation.class);
Method method = tag != null ? tag.method() : null;
Timeout timeout = method != null ? method.getAnnotation(Timeout.class) : null;
if (timeout != null) {
chain.call().timeout().timeout(timeout.value(), timeout.unit());
RealCall realCall = (RealCall) chain.call();
realCall.timeout.enter();
}
return chain.proceed(request);
}
}
Workaround consists in executing timeout.enter()
once again after changing timeout.
All magic happen in lines:
RealCall realCall = (RealCall) chain.call();
realCall.timeout.enter();
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With