DEV Community

Kevin Galligan
Kevin Galligan

Posted on

Ktor and Kotlin/Native

Ktor and Kotlin/Native

The story of coroutines for Kotlin/Native has been complex for a while now, and with the news that the Kotlin/Native memory model will be changing, coroutines for Kotlin/Native will remain a complex story for some time.

Ktor is built around coroutines. The coroutines situation fundamentally affects how Ktor functions. There have been multiple moving targets in this story, which has created a lot of confusion and a number of bug reports that kind of cross over the same core issues.

In this post, I will attempt to add some clarity to the situation.

The Coroutines Situation

To be clear, when we're talking about "core
utines", we're generally talking about kotlinx.coroutines. It's technically a separate library, but I can't imagine a realistic situation where you'd actually use coroutines and not include kotlinx.coroutines.

Initially, kotlinx.coroutines for Kotlin/Native was single threaded. You could suspend execution, but not schedule work on different threads. This was due to Kotlin/Native's memory model.

Ktor was designed to run on this single-threaded model. Also, as far as I know on iOS, you would need to initiate Ktor calls from the main thread.

In late 2019, a draft PR emerged which allowed kotlinx.coroutines to communicate across threads. We'll call this the "MT" branch, for "multithreaded". From this branch, there have been parallel library releases. The current main release is '1.3.9' and the current multithreaded release is '1.3.9-native-mt'.

Unless something changes, the parallel branch and release model will likely stay the same for kotlinx.coroutines until the Kotlin/Native memory model update is complete.

Ktor

Until 1.4, Ktor was still designed to be run with the single-threaded version of kotlinx.coroutines. You could run it with the MT version, but you had to be very careful to keep the scope you were in to the main thread and make sure it was never frozen.

It's a little complicated to explain, and not super useful as it's now history, but here it is. Coroutines run in a scope, which has (among other things) a Dispatcher and a Job. When you change the Dispatcher to run code on another thread, that scope gets frozen. Once frozen, everything the Job holds will also be frozen. So, if you were doing something on a background thread in the same scope as something you were doing with Ktor, even if Ktor was only in the main thread, you'd get an InvalidMutabilityException.

I think one of the more confusing parts is that Ktor would work until you involved that scope in a background process, after which it would stop working.

To see this in action, watch the video.

To get around this, you either need to maintain a different scope for Ktor, and make sure it never got frozen, or isolate the Job. We did that with KaMPKit. It was a pretty ugly hack, but it worked.

Also, since Ktor depended on the non-MT version, you could unintentionally be using the non-MT version of kotlinx.coroutines even though your dependency configuration explicitly asked for it. You would need to specify that you wanted to use that version.

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

Kotlin and Ktor 1.4 release

The 1.4 release of Ktor actually fixes this issue. We didn't hear about it for a bit, but I think that was because the issue was confusing to a lot of folks. Although it's fixed for the MT branch, the situation has some further confusion, as the current release of Ktor pulls in the MT branch of kotlinx.coroutines. If your project expects the single-threaded version of kotlinx.coroutines, this situation can result in some unexpected behavior.

To experiment with Ktor in the different versions, you can check out our little sample app.

GitHub logo touchlab-lab / ktorcoroutines

Just showing ktor fix in 1.4.0

The master branch has 1.3.72 and shows the failure that was happening. The kotlin_140 branch has the updated versions and a functional Ktor sample.

Issues

Ktor needs to be run from the main thread. There are also issues with Ktor and testing, related to the threading restrictions. We'll be digging into this more at some point. Just be aware of it.

Also, Russell tells me there's a logging issue which I'm not even going to dig into right now because I want to publish this today…

TL;DR

Ktor and kotlinx.coroutines were not working well together on native before 1.4. Now they cooperate better. Using Ktor 1.4 will possibly pull in the MT branch of kotlinx.coroutines, so if your project is not designed for that, be aware that it's possibly an issue.

We've updated KaMP Kit to remove the hack we had put in. Ktor is now just regular Ktor

GitHub logo touchlab / KaMPKit

KaMP Kit by Touchlab is a collection of code and tools designed to get your mobile team started quickly with Kotlin Multiplatform.

Discussion (3)

Collapse
nutriz profile image
Jérôme Gully

Thanks for this article, I struggled with my iOs colleague all the day with ktor and InvalidMutabilityException ! I hope this will help me to understand and fix our project.

BTW the ktor/coroutine documentation at this time is very vague about all this !

Collapse
hugekontrast profile image
Ashish Khare😎

Fine article. Helpful, for I get to hear about Ktor. But ( a suggestion) please place TL; DR before actual article. Thank you!

Collapse
redstrike profile image
Tung Nguyen

Interesting insight 👍! I am curious about Ktor stability and innovative aspects in real-world usages. TL;DR should be at the top of the article or change it to Take Away / Conclusion instead.