https://kotlinlang.org logo
#coroutines
Title
# coroutines
l

louiscad

07/16/2018, 3:12 PM
Has anyone experienced Android instrumented test methods that have
runBlocking
as their body, never returning while the inner coroutine is run successfully?
w

withoutclass

07/16/2018, 3:27 PM
I haven’t done any instrumented tests yet using coroutines 😞
Regular unit tests work just fine though
I’d be pretty surprised if they don’t complete.
l

louiscad

07/16/2018, 4:05 PM
So I am. @elizarov did you ever observed this? I just checked by adding a log on the last line of the
runBlocking
block, and after it, and the one after doesn't show up while the one before the closing brace does. In some other tests, the issue doesn't appear though. What I added is a
suspend fun
using
suspendCancellableCoroutine
, but it resumes fine...
d

dekans

07/16/2018, 4:21 PM
Where are you running this
runblocking
call? Your service/Activity might get killed by Android because you blocked it.
w

withoutclass

07/16/2018, 5:49 PM
Be curious to see an example
l

louiscad

07/16/2018, 7:00 PM
I'm running it in a
@Test
annotated method in
androidTest
source set of an Android library module.
I'll try to find some time to make a reproducer tomorrow at office
d

dgngulcan

07/16/2018, 9:11 PM
I had the issue when I had
runBlocking
body with
@UiThreadTest
annotated functions.
l

louiscad

07/17/2018, 7:59 AM
Here's a reproducer (put it in a class):
Copy code
@Test
fun reproduceRunBlockingBug() {
    runBlocking {
        runBlockingBug()
        println("I will be printed!")
    }
    println("I should be printed, but I won't")
}

private suspend fun runBlockingBug() = suspendCancellableCoroutine<Unit> { cont ->
    val job = Job(parent = cont.context[Job]!!)
    launch(cont.context, parent = job) {
        cont.resume(Unit)
    }
}
@elizarov the same code works on try.kotl.in, but hangs on
runBlocking
closing brace in android Junit tests
e

elizarov

07/17/2018, 8:53 AM
Hm… here the
job
is a child of
runBlocking
, so
runBlocking
waits for it to complete but it will not. It works as expected if you just
launch(cont.cotext)
without creating an interim
job
object.
l

louiscad

07/17/2018, 9:12 AM
How can I make the created job complete?
d

dekans

07/17/2018, 9:13 AM
join()
e

elizarov

07/17/2018, 9:13 AM
job.cancel()
But do you really need yet another job object? You can do
val job = launch(cont.context) { ... }
.
That this job will complete itself after it does
cont.resume
l

louiscad

07/17/2018, 9:16 AM
Yes I do need another job because I need to access it before I launch the coroutine in my use case. Here's my code:
Copy code
suspend fun <T> withSkyFacingCheck(
        zRef: Float,
        aRef: Float,
        useAvg: Boolean = true,
        avgSamplingDurationMillis: Long = maxSamplingWindowDurationMillis,
        block: suspend (SensorEventListener) -> T
) = suspendCancellableCoroutine<T> { cont ->
    val job = Job(parent = cont.context[Job]!!)
    val listener = SkyFacingCheckingSensorEventListener(zRef, aRef, useAvg, avgSamplingDurationMillis) {
        cont.cancel(IllegalStateException("Device moved!"))
        job.cancel()
    }
    launch(cont.context, parent = job) {
        val result = block(listener)
        cont.resume(result)
    }
}
The workaround would be to make the
job
property a
lateinit var
e

elizarov

07/17/2018, 9:17 AM
Mind the concurrency, though, in this case. It is tricky
l

louiscad

07/17/2018, 9:22 AM
In this case, the lambda of the listener is never run before the block, so it's safe enough to run, but I think
job.cancel()
with my previous approach is the safest option in regards to by the codebase example
TIL (and yesterday too 😉) Thanks!