Brendan Weinstein
12/31/2019, 1:53 AMlaunch {
val response = withContext(Dispatchers.Default) {
val client = HttpClient { }
val emptyBody = object : OutgoingContent.NoContent() {
override val contentLength: Long = 0
override fun toString(): String = "EmptyContent"
}
client.get<String>(body = emptyBody, port = 0, block = {
url {
takeFrom("<https://api.basebeta.com>")
encodedPath = "/rankings"
}
})
}
output.send(Action.RankingsLoadedAction(
list = emptyList(),
diffResult = KDiffUtil.calculateDiff(
MItemDiffHelper(
oldList = emptyList(),
newList = emptyList()
)
),
fromNetwork = true
))
}
This leads to the below error
kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen <http://io.ktor.utils.io|io.ktor.utils.io>.core.ByteReadPacket@1bb0698
If I follow the stacktrace further down I see references to
io.ktor.client.features.HttpCallValidator.Companion
I've read that we should avoid companion objects being used by multiple threads. My understanding is that companion objects are implemented as singletons in native (static inners on jvm?). My hunch is that the inners of Ktor reference a singleton/companion object initialized on the main thread and that leads to recursive freezing throughout the ktor client object graph.
Is there a strategy to avoid this that still allows me to make requests on a background thread? I've seen code examples that use ktor on the main thread. For my aim I'd like to make a request via ktor on a background thread.Aleksey Chugaev
12/31/2019, 2:36 AMAleksey Chugaev
12/31/2019, 2:40 AMbasher
12/31/2019, 5:26 AMAleksey Chugaev
12/31/2019, 5:32 AMBrendan Weinstein
12/31/2019, 6:56 AMbasher
12/31/2019, 8:15 AMbasher
12/31/2019, 8:17 AMAleksey Chugaev
12/31/2019, 11:55 AMkpgalligan
12/31/2019, 6:13 PMkpgalligan
12/31/2019, 6:16 PMBrendan Weinstein
01/01/2020, 12:40 AMfun main() {
GlobalScope.launch(context = <http://Dispatchers.IO|Dispatchers.IO>, block = {
for (i in 0 until 1_000) {
delay(200)
println(Thread.currentThread().name)
}
})
GlobalScope.launch(context = <http://Dispatchers.IO|Dispatchers.IO>, block = {
for (i in 0 until 1_000_000) {
Thread.sleep(10)
assert("DefaultDispatcher-worker-1".equals(Thread.currentThread().name))
}
})
while(true) {
}
}
I was gonna say that I found enforcing this way unintuitive because my understanding of coroutines was that a single coroutine can be executed on multiple threads. I thought this because you can specify the context to be a dispatcher backed by a threadpool. And then it'd make sense to freeze an object by adding a field keeping track of the last thread it was accessed by. When that field changes, freeze the object.
Execution of a coroutine can happen on multiple threads, but if I am taking the right lesson from the code sample above, this only happens when a suspending function, async, withContext, or launch call is made within the coroutine.
And then it kind of makes sense that you'd freeze an object when it is referenced by a lambda parameter for any two of those functions.