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