ktorio / ktor

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

CIO server unhandled exception during network shutdown #1219

Closed vasilenkoalexey closed 4 years ago

vasilenkoalexey commented 5 years ago

Ktor Version

1.2.2

Ktor Engine Used(client or server and name)

CIO embeddedServer

JVM Version, Operating System and Relevant Context

AndroidSDK 28, Android

Feedback

java.io.IOException: Software caused connection abort
        at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
        at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:43)
        at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
        at sun.nio.ch.IOUtil.read(IOUtil.java:192)
        at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:382)
        at kotlinx.io.nio.ChannelsKt.read(Channels.kt:117)
        at io.ktor.network.sockets.CIOReaderKt$attachForReadingDirectImpl$1$1.invokeSuspend(CIOReader.kt:75)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.ResumeModeKt.resumeMode(ResumeMode.kt:67)
        at kotlinx.coroutines.DispatchedKt.resume(Dispatched.kt:312)
        at kotlinx.coroutines.DispatchedKt.resumeUnconfined(Dispatched.kt:49)
        at kotlinx.coroutines.DispatchedKt.dispatch(Dispatched.kt:298)
        at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:250)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:260)
        at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:189)
        at io.ktor.network.selector.SelectorManagerSupport.handleSelectedKey(SelectorManagerSupport.kt:83)
        at io.ktor.network.selector.SelectorManagerSupport.handleSelectedKeys(SelectorManagerSupport.kt:63)
        at io.ktor.network.selector.ActorSelectorManager.process(ActorSelectorManager.kt:74)
        at io.ktor.network.selector.ActorSelectorManager$process$1.invokeSuspend(ActorSelectorManager.kt)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)

I created a simple embeddedServer at mobile app

embeddedServer(CIO, port = myPort, host = myHost) {
    routing {
        ...
    }
}.start(wait = false)

and exception occurred during network shutdown (just when turn off Wi-Fi or mobile data). It's as expected but what is the right way to handle it and prevent application crash? Exception only appears if there was at least one request from the client before network shutdown.

vasilenkoalexey commented 5 years ago

I tried to find a solution

    val handler = CoroutineExceptionHandler { _, e ->
        println(e)
    }
    GlobalScope.launch(handler) {
        embeddedServer(CIO, 80, parentCoroutineContext = coroutineContext) {
            launch {
                throw IOException()
            }
        }.start(wait = false)
    }
    GlobalScope.launch(handler) {
        embeddedServer(CIO, 81, parentCoroutineContext = coroutineContext) {
            throw IOException()
        }.start(wait = false)
    }
    GlobalScope.launch(handler) {
        embeddedServer(CIO, 82, parentCoroutineContext = coroutineContext) {
            CoroutineScope(Dispatchers.Default + environment.parentCoroutineContext).launch {
                throw IOException()
            }
        }.start(wait = false)
    }
    GlobalScope.launch(handler) {
        embeddedServer(CIO, 83, parentCoroutineContext = coroutineContext) {
            CoroutineScope(Dispatchers.Default).launch {
                throw IOException()
            }
        }.start(wait = false)
    }

All cases were handled by handler except the last. Exception described above is also not handled. So, looks like some network implementation executed at coroutine scope without parent coroutine context.

vasilenkoalexey commented 5 years ago

How about ideas to add clientContext parameter to ServerSocketImpl.accept(), pass it on to SocketImpl and NIOSocketImpl where put it to socketContext?

override val socketContext: CompletableJob = clientContext + Job()

Then HttpServer should accept input connection somehow like this

val client: Socket = server.accept(coroutineContext)

I think it will allow to handle such exceptions correctly.

ferama commented 4 years ago

It doesn’t seem to be related to the CIO engine only. I’m experiencing the same issue using the Netty engine.

Did you found any workaround in the meantime?

vasilenkoalexey commented 4 years ago

I just turned off keepAlive and exception does not occur

ferama commented 4 years ago

Can't find anything in the docs. How did you achieve that? Can you ignore the keepAlive header from ktor server? How to do it?

Thanks

vasilenkoalexey commented 4 years ago

I turned off it on server side. Just add

intercept(ApplicationCallPipeline.Call) {
    call.response.header("Connection", "close")
}

to embeddedServer

ferama commented 4 years ago

Many thanks. It seems to work :)

e5l commented 4 years ago

Closed as resolved

adoubleu commented 2 years ago

Does not seem to work for ktor 2.0.2. Any idea?

PrathikPrakash commented 1 year ago

Turning off keepAlive did not solve the problem in my case. Any other work arounds?