Is there any way to check if a `launch` is being b...
# coroutines
k
Is there any way to check if a
launch
is being blocked from executing due to not enough free threads? We're running into a weird issue where sometimes it seems like launches just take longer than they should to get started and am not really sure how to debug this.
d
Launch doesn’t necessarily use a new thread. Unless you specifically use a dispatcher that does so. It starts a coroutine Job, but it’s up to the dispatcher to decide when it can run it, so it depends on the coroutineContext.
k
Yeah, but my assumption is that all the current threads are actively in use, so it must be "parked" or "waiting" somewhere right? Is there a way to log these or to debug?
k
You might be able to get some useful info with
TimeSource.Monotonic
Copy code
val mark = TimeSource.Monotonic.markNow()
launch {
  val elapsed: Duration = mark.elapsedNow()
}
This won’t work for everything because a dispatcher doesn’t just dispatch launched coroutines. It dispatches every single block of code between suspension points. You might even be able to wrap dispatchers with something similar to the above to provide diagnostic information.
Maybe something like this?
Copy code
class DiagnosticDispatcher(private val delegate: CoroutineDispatcher) : CoroutineDispatcher {

  override fun dispatch(context: CoroutineContext, block: Runnable) {
    val mark = TimeSource.Monotonic.markNow()
    val diagnosticBlock = Runnable {
      val duration = mark.elapsedNow()
      println(duration)
      block.run()
    }
    delegate.dispatch(context, diagnosticBlock)
  }
}
I haven’t tested that anywhere, but I think that would print out the duration between when each block requests dispatch and when dispatch actually occurs
Here’s an example:
Copy code
final class DiagnosticDispatcher(private val delegate: CoroutineDispatcher) : CoroutineDispatcher() {

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        val mark = TimeSource.Monotonic.markNow()
        val diagnosticBlock = Runnable {
            val duration = mark.elapsedNow()
            println(duration)
            block.run()
        }
        delegate.dispatch(context, diagnosticBlock)
    }
}

@OptIn(ExperimentalStdlibApi::class)
fun main() = runBlocking {
    val dispatcher = currentCoroutineContext()[CoroutineDispatcher]!!
    withContext(DiagnosticDispatcher(dispatcher)) {
        flow {
            while (true) {
                val value = Random.nextLong(until = 5_000)
                yield()
                emit(value)
            }
        }.take(10).collect()
    }
}
With the following output:
Copy code
3.015291ms
20.5us
13.166us
6.166us
5us
4.584us
4.5us
4.167us
4.208us
5us
4.375us
k
Yes I suppose that is one solution, although it kinda relies on me wrapping every dispatcher which isn't really ideal 😕
k
If you're injecting your dispatchers from DI it should be fairly straightforward