Analyzing OkHttp Timeout Mechanisms and Retrofit Request Handling
This article investigates why OkHttp timeouts appear ineffective in a Retrofit‑RxJava network stack, examines the internal flow of connectTimeout, readTimeout and writeTimeout through StreamAllocation, AsyncTimeout, and the Watchdog thread, and proposes configuration adjustments to avoid request stalls.
Introduction
Recent observations of occasional long‑lasting loads despite a 30‑second timeout setting prompted an in‑depth analysis of the OkHttp timeout configuration and its interaction with Retrofit and RxJava.
Problem Analysis
The business layer defines three timeout values (connect, read, write) all set to 30 s. Their meanings are:
connectTimeout – socket connection establishment timeout
readTimeout – timeout for reading server responses
writeTimeout – timeout for sending client data (e.g., uploads)
if (okConfig.getConnectTimeOut() > 0) {</code><code> okBuilder.connectTimeout(okConfig.getConnectTimeOut(), TimeUnit.SECONDS);</code><code>} else {</code><code> throw new IllegalArgumentException("connectTimeOut must be greater than 0");</code><code>}</code><code></code><code>if (okConfig.getWriteTimeOut() > 0) {</code><code> okBuilder.writeTimeout(okConfig.getWriteTimeOut(), TimeUnit.SECONDS);</code><code>} else {</code><code> throw new IllegalArgumentException("writeTimeout must be greater than 0");</code><code>}</code><code></code><code>if (okConfig.getReadTimeOut() > 0) {</code><code> okBuilder.readTimeout(okConfig.getReadTimeOut(), TimeUnit.SECONDS);</code><code>} else {</code><code> throw new IllegalArgumentException("readTimeout must be greater than 0");</code><code>}Source Code Flow – connectTimeout
In StreamAllocation.newStream() the timeout values are retrieved from the interceptor chain and passed to findHealthyConnection(), which eventually calls RealConnection.connectSocket() where the socket connection timeout is applied.
public HttpCodec newStream(OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {</code><code> int connectTimeout = chain.connectTimeoutMillis();</code><code> int readTimeout = chain.readTimeoutMillis();</code><code> int writeTimeout = chain.writeTimeoutMillis();</code><code> // ...</code><code> RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, ...);</code><code> // ...</code><code>}The connectSocket implementation on Android ultimately invokes socket.connect(address, connectTimeout), directly using the configured value.
public void connectSocket(Socket socket, InetSocketAddress address, int connectTimeout) throws IOException {</code><code> try {</code><code> socket.connect(address, connectTimeout);</code><code> } catch (AssertionError e) {</code><code> if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);</code><code> throw e;</code><code> } catch (SecurityException e) {</code><code> IOException ioException = new IOException("Exception in connect");</code><code> ioException.initCause(e);</code><code> throw ioException;</code><code> } // ...</code><code>}Source Code Flow – readTimeout
During Http1Codec creation, the read timeout is set on the raw socket and on the source/sink objects:
socket.setSoTimeout(readTimeout);</code><code>source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);</code><code>sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);The Source implementation reads from the input stream and checks the timeout before each read via timeout.throwIfReached().
public long read(Buffer sink, long byteCount) throws IOException {</code><code> timeout.throwIfReached();</code><code> // read from InputStream...</code><code>}AsyncTimeout and Watchdog
Both Source and Sink rely on AsyncTimeout, which maintains a sorted linked list of timeout nodes and a daemon Watchdog thread that wakes up when the head node expires, then calls timedOut() (typically closing the socket).
private static synchronized void scheduleTimeout(AsyncTimeout node, long timeoutNanos, boolean hasDeadline) {</code><code> // Insert node in order, start Watchdog if needed</code><code>} private static class Watchdog extends Thread {</code><code> public void run() {</code><code> while (true) {</code><code> AsyncTimeout timedOut = awaitTimeout();</code><code> if (timedOut == null) continue;</code><code> timedOut.timedOut();</code><code> }</code><code> }</code><code>}Retrofit Request Construction
Retrofit creates a dynamic proxy for service interfaces, builds a Request, wraps it into an okhttp3.Call, and enqueues it via Dispatcher.promoteAndExecute(). The dispatcher limits concurrent requests by maxRequests (default 64) and per‑host requests by maxRequestsPerHost (default 5).
private boolean promoteAndExecute() {</code><code> // iterate readyAsyncCalls</code><code> if (runningAsyncCalls.size() >= maxRequests) break;</code><code> if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue;</code><code> // execute asyncCall</code><code>}When many requests target the same host under poor network conditions, the per‑host limit can cause subsequent calls to wait, effectively extending the observed timeout beyond the configured value.
Root Cause & Solution
Increase maxRequestsPerHost (e.g., from 5 to 8) to allow more concurrent calls to the same host.
Reduce bursty request patterns by merging APIs where possible.
Adjust timeout values to realistic limits rather than maximising them.
These changes resolve the apparent timeout‑not‑triggering issue without altering the underlying OkHttp timeout mechanisms.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Xueersi Online School Tech Team
The Xueersi Online School Tech Team, dedicated to innovating and promoting internet education technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
