ktorio / ktor

Framework for quickly creating connected applications in Kotlin with minimal effort
https://ktor.io
Apache License 2.0
13.02k stars 1.06k forks source link

ConnectionFactory raises NoSuchElementException #1424

Closed tateisu closed 4 years ago

tateisu commented 5 years ago

Ktor Version and Engine Used (client or server and name) Ktor 1.2.5, using HTTP client (CIO)

Describe the bug when my app create many request to other hosts, frequenly ConnectionFactory raises NoSuchElementException.

java.util.NoSuchElementException
    at java.util.concurrent.ConcurrentHashMap$KeyIterator.next(ConcurrentHashMap.java:3416)
    at java.util.concurrent.ConcurrentHashMap$KeyIterator.nextElement(ConcurrentHashMap.java:3423)
    at io.ktor.util.cio.Semaphore.leave(Semaphore.kt:56)
    at io.ktor.client.engine.cio.ConnectionFactory.connect(ConnectionFactory.kt:25)
    at io.ktor.client.engine.cio.ConnectionFactory$connect$1.invokeSuspend(ConnectionFactory.kt)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:334)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

ConcurrentHashMap.keys().nextElement may throw NoSuchElementException, but io.ktor.util.cio.Semaphore.leave() does not catch it.

To Reproduce

code to reproduce


data class SprayResult(
    val url: String,
    @Suppress("ArrayInDataClass") val bytes: ByteArray? = null,
    val ex: Throwable? = null
)

@KtorExperimentalAPI
fun main(args: Array<String>) = runBlocking {

    HttpClient(CIO).use { client ->

        val channel = Channel<SprayResult>()

        // spray requests to http://192.168.253.*/
        val list = (1..254)

        for (i in list) {
            val url = "http://192.168.253.$i/" // your lan addr
            GlobalScope.launch {
                try {
                    val bytes = client.get<ByteArray>(url)
                    channel.send(SprayResult(url, bytes = bytes))
                } catch (ex: Throwable) {
                    channel.send(SprayResult(url, ex = ex))
                }
            }
        }

        for (i in list) {
            val (url, bytes, ex) = channel.receive()
            if (bytes != null) {
                if (bytes.isNotEmpty()) println("$url ${bytes.size}bytes")
            } else when (ex) {
                null, is java.net.ConnectException, is FailToConnectException -> {
                }
                else -> {
                    println("$url ${ex.javaClass.simpleName} ${ex.message}")
                    ex.printStackTrace()
                }
            }
        }
    }
}

Expected behavior this error should not be happen.

Screenshots no, this is CLI.

tateisu commented 5 years ago

It may be needless to say, I use this code to detect FlashAir devices inside the LAN.

e5l commented 4 years ago

Hi @tateisu, thanks for the report. It was fixed with migration to kotlinx-coroutines semaphore.

brendanw commented 4 years ago

@e5l I am still seeing this on ktor-client-cio:1.2.6

Could you please link a commit and mention which version this get released in?

e5l commented 4 years ago

The fix in the 1.3.0-beta-2 update