Ktor: OkHttp client can't be closed and leaks a lot of threads

Created on 14 Sep 2019  路  2Comments  路  Source: ktorio/ktor

Ktor Version and Engine Used (client or server and name)
Client 1.3.0-beta-1 with okhttp

Describe the bug
Closing the client does not terminate threads started by okthttp.

To Reproduce

import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.request.get
import io.ktor.client.response.HttpResponse
import io.ktor.client.response.readBytes
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import java.lang.management.ManagementFactory

class Bug {

    @Test
    fun never_closed() {
        var counter = 0

        while (!Thread.interrupted() && counter < 20) {
            println("Creating client ${++counter}")
            HttpClient(OkHttp).use { client ->
                runBlocking {

                    // Remove this request and the threads seems to be removed.
                    client.get<HttpResponse>("https://www.vg.no").use { response ->
                        println("Got " + response.readBytes().size + " bytes")
                    }
                }
            }

            println(" - Number of threads: " + ManagementFactory.getThreadMXBean().threadCount)
            Thread.sleep(1000)
        }
    }

}

Expected behavior
I would expect threads to be stopped, but running the test above and you will see the number of threads increase with every iteration.

I've debugged the code and it seems like the code launched in OkHttpEngine:58 is never executed.
https://github.com/ktorio/ktor/blob/master/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpEngine.kt#L58

bug

All 2 comments

Hi @kimble, I reproduced the problem on 1.3.0-beta-1 as well as on master.

The problem could also be easily reproduced without Ktor. It's enough to create an OkHttp client, make a request and close client. Making these actions repeatedly we'll get an increasing number of threads in the system.

The root cause is that okhttp3.internal.connection.RealConnectionPool (see the source code here) contains executor which allocates a new thread almost each time a new OkHttpClient is created, but doesn't terminate it when we call connectionPool().evictAll() but waits for 60 seconds to terminate exceeded threads. Unfortunately, this behavior is hardcoded. BTW, the problem is also highlighted here: https://github.com/square/okhttp/issues/3372.

Meanwhile, I'll try to find a workaround for Ktor.

I merged #1562 with a workaround.

As we discussed with @e5l the workaround implements the following logic. Be default, if you use HttpClient(OkHttp) the OkHttp client won't be created each time and so-called prototype OkHttp client will be used. It allows limiting resource allocations but leads to permanently allocated prototype resources. In case you want to avoid these permanently allocated resources you could manually create an engine, pass it to all clients you want to create and then manually close it when it's not needed anymore.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

seanf picture seanf  路  3Comments

KennethanCeyer picture KennethanCeyer  路  4Comments

shinriyo picture shinriyo  路  4Comments

guenhter picture guenhter  路  4Comments

SimonSchubert picture SimonSchubert  路  4Comments