ktor: ktor client iOS hangs forever in runBlocking

commonMain

expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T

fun getGitHub(): HttpClientCall = runBlocking {
    HttpClient().call("https://www.github.com")
}

iosMain

actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
    return kotlinx.coroutines.runBlocking(context, block)
}

image

Xcode hangs forever when invoking the method:

func testRunBlocking() {
  GitHubKt.getGitHub()
}

If I delete HttpClient in runBlocking then the method successfully returns a value on iOS.

Versions:

kotlin_version=1.3.0-rc-190
coroutines_version=1.0.0-RC1
ktor_version=1.0.0-beta-2
serialization_version=0.8.2-rc13

Is it a known issue that ktor iOS doesn’t work in runBlocking? Is this even a ktor issue or a coroutines issue? https://github.com/Kotlin/kotlinx.coroutines/issues/462

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 20 (10 by maintainers)

Commits related to this issue

Most upvoted comments

I defined a custom runBlocking method using NSRunLoop and that works.

https://github.com/bootstraponline/run_blocking/commit/a7953192c7078ab41dbf382f56bdc4432a46c84b#diff-c1a933ca71f0c706f65401b240f8c806

// Expectation.kt
package example

import platform.Foundation.NSDate
import platform.Foundation.NSRunLoop
import platform.Foundation.addTimeInterval
import platform.Foundation.runUntilDate

class Expectation<T> {
    private var waiting = true
    private var result: T? = null

    fun fulfill(result: T?) {
        waiting = false
        this.result = result
    }

    fun wait(): T? {
        while (waiting) {
            advanceRunLoop()
        }

        return result
    }
}

private fun advanceRunLoop() {
    val date = NSDate().addTimeInterval(1.0) as NSDate
    NSRunLoop.mainRunLoop.runUntilDate(date)
}
// RunBlocking.kt
package example

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.launch
import platform.Foundation.NSRunLoop
import platform.Foundation.performBlock
import kotlin.coroutines.CoroutineContext

actual fun <T> runBlocking(block: suspend () -> T): T {
    val expectation = Expectation<T>()

    GlobalScope.launch(MainRunLoopDispatcher) {
        expectation.fulfill(block.invoke())
    }

    return expectation.wait() ?: throw RuntimeException("runBlocking failed")
}

private object MainRunLoopDispatcher : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        NSRunLoop.mainRunLoop().performBlock {
            block.run()
        }
    }
}

Well, running coroutines on background threads are not yet supported. Using runBlocking is quite dangerous. And for sure you can’t use runBlocking with MainLoopDispatcher and you are already on main loop. Are you sure you actually need blocking?

Your fix looks too verbose, you can use use function for client to get it closed

val result = HttpClient().use { it.get<String>("https://.... ") }

Unfortunately Dispatchers.Main doesn’t work with ios yet. See https://github.com/Kotlin/kotlinx.coroutines/issues/470