Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why OkHttp doesn't reuse its connections?

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.

like image 656
J. Snow Avatar asked Dec 07 '16 07:12

J. Snow


2 Answers

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.

like image 142
toien Avatar answered Sep 21 '22 09:09

toien


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();
like image 22
Wang Qian Avatar answered Sep 25 '22 09:09

Wang Qian