https://kotlinlang.org logo
Title
n

Nikolay Kasyanov

04/15/2021, 2:32 PM
Hey folks 👋 I’m upgrading a project to ktor 1.5.3 and it breaks some tests when run on JVM (iOS tests are fine). In these tests, a class is tested that provides a reactive interface to its underlying
HttpClient
instance, by using Reaktive helpers to “convert” coroutines into
Observable
(or other Reaktive types). To do actual asserts, Reaktive helpers like
TestObserver
are used:
fun `it does whatever a spider cat does`() {
    // ktorMockServerManager is just a way to configure mock engine handlers      
    ktorMockServerManager.enqueueRequest(
        ...
    )

    sut.doApiStuff(...)
        .test()
        .assertComplete()
}
The tests are completely synchronous, but as soon as I update ktor to 1.5.1 (1.5.0 is fine, though), they start failing because by the time
assertComplete
is called, the underlying coroutine hasn’t yet finished, so the tests fail. If I add a
sleep
call like this:
fun `it does whatever a spider cat does`() {

    ktorMockServerManager.enqueueRequest(
        ...
    )

    val observer = sut.doApiStuff(...)
        .test()

    sleep(1000)

    observer.assertComplete()
}
I wonder what change between 1.4.3 and 1.5.1 could lead to this and what’s the way around it?
👀 1
looks like it this might be it, the only “real” change to
MockEngine
since 1.4.3, added in 1.5.1: https://github.com/ktorio/ktor/commit/a6d103f6c3f13c1a136a313112a47a6af7927137#diff-3d93b6d65cea408ebd78460acc5fea1e9fb27799795984595067d5e421f939d1
I’d appreciate any clues here cc @e5l 🙏
This actually checks out: on native,
Dispatchers.clientDispatcher
yields
Unconfined
dispatcher, but on JVM it’s
ClosableBlockingDispatcher
thus tests don’t on iOS but fail on JVM
e

e5l

04/20/2021, 9:23 AM
I'm not sure that I can guess what coroutine is stale. It would be great to have more details. Could you file an issue with coroutines dump and reproducer?
n

Nikolay Kasyanov

04/22/2021, 9:50 AM
I see, I’ll try get some more details, thanks!
Hey @e5l, I’ll look into it but not sure about the timeline. Here’s what I can share now, by the time previously Reaktive-completable-under-test was already completed, that’s what I see in coroutine debugger. The
ClosableBlockingDispatcher
was added to MockEngine in ktor 1.5.1 if I remember correctly. Previously, the same coroutine would have executed immediately on the
Unconfined
dispatcher.
e

e5l

05/04/2021, 4:05 PM
Yep, that’s expected. The reason it was the same with Unconfined, but we didn’t await for running jobs
The main question is what job is running and how to stop it
n

Nikolay Kasyanov

05/04/2021, 4:06 PM
if I expand
call-context:2
, here’s what I see:
"call-context:2": RUNNING on thread "ktor-client-dispatcher-worker-1": RUNNING
 findLoadedClass:1281, ClassLoader (java.lang)
 loadClassOrNull:593, BuiltinClassLoader (jdk.internal.loader)
 loadClass:579, BuiltinClassLoader (jdk.internal.loader)
 loadClass:178, ClassLoaders$AppClassLoader (jdk.internal.loader)
 loadClass:522, ClassLoader (java.lang)
 ByteReadChannel:45, ByteChannelCtorKt (<http://io.ktor.utils.io|io.ktor.utils.io>)
 respond:75, MockUtilsKt (io.ktor.client.engine.mock)
 respond$default:73, MockUtilsKt (io.ktor.client.engine.mock)
 invokeSuspend:37, MockHttpClientFactory$get$1$2$1 (com.careem.captain.common.networking.mockserver)
 execute:61, MockEngine (io.ktor.client.engine.mock)
 invokeSuspend:86, HttpClientEngine$executeWithinCallContext$2 (io.ktor.client.engine)
 execute:61, MockEngine (io.ktor.client.engine.mock)
 invokeSuspend:86, HttpClientEngine$executeWithinCallContext$2 (io.ktor.client.engine)
 resumeWith:33, BaseContinuationImpl (kotlin.coroutines.jvm.internal)
 run:106, DispatchedTask (kotlinx.coroutines)
 runSafely:571, CoroutineScheduler (kotlinx.coroutines.scheduling)
 executeTask:750, CoroutineScheduler$Worker (kotlinx.coroutines.scheduling)
 runWorker:678, CoroutineScheduler$Worker (kotlinx.coroutines.scheduling)
 run:665, CoroutineScheduler$Worker (kotlinx.coroutines.scheduling)
e

e5l

05/04/2021, 4:08 PM
could you share the code of test as well?
n

Nikolay Kasyanov

05/04/2021, 4:08 PM
let me try…
e

e5l

05/04/2021, 4:08 PM
It looks like some coroutine from the test scope is not finished
You actually can setup
CoroutineTimeout
for test to get test timeout for all hanging coroutines
It will also print you all coroutines stack traces
n

Nikolay Kasyanov

05/04/2021, 4:10 PM
good to know, thanks! I’ll try to create a reproducer, should be easier than I initially assumed
sending the reproducer to you as a direct message
:tnx: 1
e

e5l

05/04/2021, 4:58 PM
You also can file a private issue in YT, and share it only with Ktor team
l

louiscad

05/04/2021, 4:59 PM
Or make the issue public, but attach the reproducer privately 🙂
👍 1
n

Nikolay Kasyanov

05/04/2021, 5:00 PM
sounds good