I'm performing an http-benchmark using OkHttp 3.5.0. I'm sending thousands of requests to the same URL.
I expect that the OkHttp-client uses a ConnectionPool and reuses its connections over and over. But if we look into netstat
we'll see many connections in the TIME_WAIT state:
TCP 127.0.0.1:80 127.0.0.1:51752 TIME_WAIT
TCP 127.0.0.1:80 127.0.0.1:51753 TIME_WAIT
TCP 127.0.0.1:80 127.0.0.1:51754 TIME_WAIT
TCP 127.0.0.1:80 127.0.0.1:51755 TIME_WAIT
TCP 127.0.0.1:80 127.0.0.1:51756 TIME_WAIT
...
After a couple of thousands of requests I'm getting a SocketException: No buffer space available (maximum connections reached?)
The code preforming requests (Kotlin):
val client = OkHttpClient.Builder()
.connectionPool(ConnectionPool(5, 1, TimeUnit.MINUTES))
.build()
val request = Request.Builder().url("http://192.168.0.50").build()
while (true) {
val response = client.newCall(request).execute()
response.close()
}
If instead of response.close()
I use response.body().string()
, then SocketException
doesn't happen, but netstat
still shows plenty of TIME_WAIT connections, and the benchmark performance is getting lower and lower.
What am I doing wrong?
PS: I've tried to use Apache HttpClient and its PoolingHttpClientConnectionManager
, and seems like it works perfectly. But I'd like to figure out what's wrong with OkHttp.
my version is 3.13.0, which is not too far from 3.5.0 and i meet TIME_WAIT
problem too.
after dive into source code, i found in CallServerInterceptor.java
line 142:
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
and in StreamAllocation.java line 367:
public void noNewStreams() {
Socket socket;
Connection releasedConnection;
synchronized (connectionPool) {
releasedConnection = connection;
socket = deallocate(true, false, false); // close connection!
if (connection != null) releasedConnection = null;
}
closeQuietly(socket);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
}
which means okhttp will CLOSE connection if "Connection: close" header exists in either request or response
although long time passed since question been submit, i hope this answer help guys who encountering this problem, good luck.
Thanks to toien and I find my solution (for a non-standard http server).
public class Http1CodecWrapper implements HttpCodec {
private HttpCodec codec;
public Http1CodecWrapper(HttpCodec codec) {
this.codec = codec;
}
// ...
@Override
public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
return codec.readResponseHeaders(expectContinue)
.addHeader("Connection", "keep-alive");
}
}
OkHttpClient httpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation allocation = realChain.streamAllocation();
HttpCodec codec = new Http1CodecWrapper(realChain.httpStream());
RealConnection connection = (RealConnection) realChain.connection();
return realChain.proceed(request, allocation, codec, connection);
}
})
.build();
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