Coroutines and heartbeat job

I have a simple app that needs to send heartbeat while main task is being executed

import kotlinx.coroutines.*
import java.net.URL
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue

@ExperimentalTime
fun main() {
    runBlocking {
        val job = launch {
            while (isActive) {
                println("send heartbeat")
                delay(250)
            }
        }

        println("Start loading")
        val (payloadSize, duration) = measureTimedValue {
            URL("https://sabnzbd.org/tests/internetspeed/20MB.bin")
                .openConnection()
                .getInputStream()
                .use { it.readAllBytes().size }
        }
        println("Loaded $payloadSize bytes. Time $duration")
        
        job.cancelAndJoin()
    }
}

currently “heartbeat” is not being sent.
Do I understand it correctly, that delay in heartbeat job actually waits for some other coroutine to get suspended? My thread is actually being blocked by URL openConnection, so nothing happens.

Is the only way to provide dedicated threadpool to heartbeat job?

 val job = launch(newSingleThreadContext("heartbeat")) {
            while (isActive) {
                println("send heartbeat")
                delay(250)
            }
        }

that works.
Or
wrapping web call in withContext works too

import kotlinx.coroutines.*
import java.net.URL
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue

@ObsoleteCoroutinesApi
@ExperimentalTime
fun main() {
    runBlocking {
        val job = launch {
            while (isActive) {
                println("send heartbeat")
                delay(250)
            }
        }

        withContext(Dispatchers.Default) {
            println("Start loading")
            val (payloadSize, duration) = measureTimedValue {
                URL("https://sabnzbd.org/tests/internetspeed/20MB.bin")
                    .openConnection()
                    .getInputStream()
                    .use { it.readAllBytes().size }
            }
            println("Loaded $payloadSize bytes. Time $duration")
        }

        job.cancelAndJoin()
    }
}

Is it because coroutines use Main thread dispatcher by default, and my heartbeat job is getting blocked in that thread?

1 Like

If the dispatcher is running at max capacity, yes.

Correct

The heartbeat is fine, it’s not causing any issues.

This is the way to go but you want Dispatchers.IO which has a default max parallelism of 64. Dispatchers.Default is not designed for blocking calls and it’s parallelism depends on you CPU (but is always at least 2).

No, runBlocking creates it’s own dispatcher using an event loop running synchronously inside runBlocking and uses that by default. Usually, if no dispatcher is specified Dispatchers.Default is used but runBlocking is special and its behavior is described in it’s docs here (though you need to open the jvm tab)

4 Likes