something definitely changed on the 1.3.3-native-m...
# kotlin-native
k
something definitely changed on the 1.3.3-native-mt branch, because it has also broken Ktor on Kotlin/Native, which was also working fine in my project a few days ago
well shit. I'm dead in the water. parts of my project won't work w/o 1.3.3-native-mt, and Ktor doesn't work with it
b
much of ktor can't be frozen
k
yeah. it seems that I am going to have to ditch 1.3.3-native-mt and come up with some ugly workaround to get the rest of my code working.
k
I mentioned this in previous chats on the subject, and in the MT coroutines PR. if you use the same coroutines scope for ktor as you do with other coroutines that will cross thread boundaries, it’ll freeze and stop working. Our current workaround is to have them in separate scopes
See comment from a couple weeks ago: https://github.com/Kotlin/kotlinx.coroutines/pull/1648
It’s not a great workaround, but it works
Copy code
open class BaseModel : KoinComponent {
    internal val mainScope = MainScope(Dispatchers.Main)
    internal val ktorScope = MainScope(Dispatchers.Main)

    open fun onDestroy() {
        mainScope.job.cancel()
        ktorScope.job.cancel()
    }
}

internal class MainScope(private val mainContext: CoroutineContext) : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = mainContext + job + exceptionHandler

    internal val job = Job()
    private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
        showError(throwable)
    }

    //TODO: Some way of exposing this to the caller without trapping a reference and freezing it.
    fun showError(t: Throwable) {
        printThrowable(t)
    }
}
k
thanks @kpgalligan, let me play around with that a little
Uncaught Kotlin exception: kotlinx.coroutines.CoroutinesInternalError: Fatal exception in coroutines machinery for AwaitContinuation(Shareable[used]){HttpResponseData=(statusCode=200 OK)}@1fc23da8. Please read KDoc to 'handleFatalException' method and report this incident to maintainers
Caused by: kotlin.ClassCastException: kotlin.coroutines.native.internal.CompletedContinuation cannot be cast to kotlinx.coroutines.DispatchedContinuation
k
Don’t know. Working for us
k
ok - do you see any issues with this:
Copy code
CoroutineScope(Dispatchers.Main).async {
                validationService.validateMessagesReceived(
                    config.validationUrl, config.validationAccount, sentSuccessfully
                )
            }.await()
validateMessagesReceived
creates the client, sends the request, and consumes the response
and closes the client
I still get kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen io.ktor.utils.io.core.ByteReadPacket@33e09548
i am guessing because this is all executed within the flow of the original scope that changed threads
it's quite ironic that Ktor is the reason I had to put in this workaround/thread switching in the first place, to work around a dead lock when used with
runBlocking
k
I don’t know why your code isn’t working, or if the code sample above is OK or not. Here’s how we call ktor
Copy code
ktorScope.launch {
                val breedResult = ktorApi.getJsonFromApi()
                val breedList = breedResult.message.keys.toList()
                insertBreedData(breedList)
                settings.putLong(DB_TIMESTAMP_KEY, currentTimeMS)
            }
In the method
insertBreedData
we launch aother coroutine, so the ktor scope doesn’t leave the main thread
Copy code
private fun insertBreedData(breeds: List<String>) {
            mainScope.launch {
                dbHelper.insertBreeds(breeds)
            }
        }
If your ktor scope gets pushed over the thread boundary, it’ll from then on not work.
If
CoroutineScope(Dispatchers.Main).async
etc is itself calling from a thread that isn’t main, then you are on some level crossing thread boundaries.
k
I can't figure out how anything inside Ktor is getting frozen. anything related to Ktor is done on the main thread. there's only one unrelated call that is done from a non-main thread, and I have a call to ensure the parent object doesn't get frozen.
but if I remove that unrelated call it works
k
What’s the call?
k
it's a call from a dispatch queue that sends a message to a channel
k
I mean code
If you’re moving to another thread and in any way doing that from the existing context, it involves the coroutines machinery
k
Copy code
override fun messageSent(messageId: String, success: Boolean) = runBlocking {
            channel.send(messageId to success)
        }
Copy code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
            accessor.messageSent("1", true)
            accessor.messageSent("2", true)
            accessor.messageSent("3", true)
            Unit
        }.freeze())
a
We removed ktor from one of our projects because of this. Replaced with simple expect/actual impls.
k
honestly I could have done that about 5 times over in the amount of time I've wasted trying to make this work 😛
k
I would just remove ktor.
k
20 loc to get the iOS side working, and the rest of my code became much simpler
👍 2
k
We’ll see how things land after the MT coroutines matures. Part of the issue is that ktor wasn’t designed for that. However, there’s been a lot of interest in a separate library. Maybe something mobile specific. Certainly something that can run easily without regard for coroutine context. I know of several teams that just do expect/actual rather than use ktor for whatever reason.
k
yeah. I feel like kind of a dumbass for investing multiple days into it when I could do expect/actual so easily for my use case. I am sure another library will come along soon.
it would have been best to switch when I ran into the original
runBlocking
deadlock, but hindsight is always 20/20