Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

withTimeout function gives IllegalStateException: There is no event loop. Use runBlocking { ... } to start one. in Kotlin Multiplatform iOS client

Update: It works if I first execute a coroutine without timeout and then withTimeout. But If I execute a coroutine withTimeout first then it gives me an error. same goes for Async as well.

I am creating a demo kotlin multiplatform application where I am executing an API call with ktor. I want to have a configurable timeout function on ktor request so I am using withTimeout at coroutine level.

Here is my function call with network API.

suspend fun <T> onNetworkWithTimeOut(
    url: String,
    timeoutInMillis: Long,
    block: suspend CoroutineScope.() -> Any): T {
    return withTimeout(timeoutInMillis) {
        withContext(dispatchers.io, block)
    } as T
}

suspend fun <T> onNetworkWithoutTimeOut(url: String, block: suspend CoroutineScope.() -> Any): T {
    return withContext(dispatchers.io, block) as T
}

Here is my AppDispatcher class for the iOSMain module.

@InternalCoroutinesApi
actual class AppDispatchersImpl : AppDispatchers {
@SharedImmutable
override val main: CoroutineDispatcher =
    NsQueueDispatcher(dispatch_get_main_queue())

@SharedImmutable
override val io: CoroutineDispatcher =
    NsQueueDispatcher(dispatch_get_main_queue())

internal class NsQueueDispatcher(
    @SharedImmutable private val dispatchQueue: dispatch_queue_t
) : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        NSRunLoop.mainRunLoop().performBlock {
            block.run()
        }
    }
}

}

so the function with the timeout gives me the following error in iOS client.

kotlin.IllegalStateException: There is no event loop. Use runBlocking { ... } to start one.

I am using 1.3.2-native-mt-1 version of the kotlin-coroutine-native. I have created a sample demo application at the following URL. https://github.com/dudhatparesh/kotlin-multiplat-platform-example

like image 459
Paresh Dudhat Avatar asked Dec 17 '19 09:12

Paresh Dudhat


2 Answers

So, as mentioned in comment above I had similar issue but turned out that it wasn't picking up the native-mt version due to transitive dependencies in other libraries. Added following and it's resolving now.

        implementation('org.jetbrains.kotlinx:kotlinx-coroutines-core-native') 
        {
            version {
                strictly '1.3.3-native-mt'
            }
        }

Also note guidance in https://github.com/Kotlin/kotlinx.coroutines/blob/native-mt/kotlin-native-sharing.md

Starting to make use of this in https://github.com/joreilly/PeopleInSpace

like image 146
John O'Reilly Avatar answered Nov 11 '22 19:11

John O'Reilly


If you want to use [withTimeout] functions in coroutines you have to modify your Dispatcher to implement Delay interface. Here is an example of how this can be achieved:

@UseExperimental(InternalCoroutinesApi::class)
class UI : CoroutineDispatcher(), Delay {

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(dispatch_get_main_queue()) {
            try {
                block.run()
            } catch (err: Throwable) {
                throw err
            }
        }
    }

    @InternalCoroutinesApi
    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeMillis * 1_000_000), dispatch_get_main_queue()) {
            try {
                with(continuation) {
                    resumeUndispatched(Unit)
                }
            } catch (err: Throwable) {
                throw err
            }
        }
    }

    @InternalCoroutinesApi
    override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
        val handle = object : DisposableHandle {
             var disposed = false
                private set

            override fun dispose() {
                disposed = true
            }
        }
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeMillis * 1_000_000), dispatch_get_main_queue()) {
            try {
                if (!handle.disposed) {
                    block.run()
                }
            } catch (err: Throwable) {
                throw err
            }
        }

        return handle
    }

}

This solution can be easily modified for your needs.

More information can be found in this thread.

UPDATE

At the moment there is a version 1.3.9-native-mt of kotlinx:kotlinx-coroutines-core artifact which gives the ability to use Dispatchers.Main on ios platform (it supports delay out of the box). It even supports Dispatchers.Default which is used for background work. You can read docs in native-mt branch. Worth noting that the version for ios should be set strictly:

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9-native-mt") {
            version {
                strictly('1.3.9-native-mt')
            }
        }
like image 4
art Avatar answered Nov 11 '22 20:11

art