I've been having a shot at kotlin multiplatform and it's brilliant, but threading stumps me. The freezing of state between threads makes sense conceptually, and works fine in simple examples where small objects or primitives are passed back and forth, but in real world applications I can't get around InvalidMutabilityException.
Take the following common code snippet from an android app
class MainViewModel(
private val objectWhichContainsNetworking: ObjectWhichContainsNetworking
)
private var coreroutineSupervisor = SupervisorJob()
private var coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main + coreroutineSupervisor)
private fun loadResults() {
// Here: Show loading
coroutineScope.launch {
try {
val result = withContext(Dispatchers.Default) { objectWhichContainsNetworking.fetchData() }
// Here: Hide loading and show results
} catch (e: Exception) {
// Here: Hide loading and show error
}
}
}
Nothing very complex, but if used in common code and run from Kotlin/Native then pow InvalidMutabilityException on MainViewModel.
It seems the reason for this is that anything passed in withContext is frozen recursively so because objectWhichContainsNetworking is a property of MainViewModel and is used in withContext then MainViewModel gets caught in the freeze.
So my question is, is this just a limitation of the current Kotlin/Native memory model? Or perhaps the current version of coroutines? And are there any ways round this?
Note: coroutines version: 1.3.9-native-mt. kotlin version 1.4.0.
Edit 1:
So it appears that the above slimmed down code actually works fine. It turns out the incriminating code was an updateable var in the view model (used to keep a reference to the last view state) which becomes frozen and then throws the exception when it tries to be mutated. I'm going to make an attempt of using Flow/Channels to ensure there's no var reference needed and see if this fixes the overall problem.
Note: if there is a way to avoid MainViewModel being frozen in the first place it would still be fantastic!
Edit 2:
Replaced the var with Flow. I couldn't get standard flow collecting in iOS until using the helpers here: https://github.com/JetBrains/kotlinconf-app/blob/master/common/src/mobileMain/kotlin/org/jetbrains/kotlinconf/FlowUtils.kt.
MainViewModel still gets frozen, but as all it's state is immutable it's no longer a problem. Hope it helps someone!
In your original code, you are referencing a field of the parent object, which causes you to capture the whole parent and freeze it. It is not an issue with coroutines. Coroutines follows the same rules as all the other concurrency libraries in Kotlin/Native. It freezes the lambda when you cross threads.
class MainViewModel(
private val objectWhichContainsNetworking: ObjectWhichContainsNetworking
)
//yada yada
private fun loadResults() {
coroutineScope.launch {
try {
val result = withContext(Dispatchers.Default) {
//The reference to objectWhichContainsNetworking is a field ref and captures the whole view model
objectWhichContainsNetworking.fetchData()
}
} catch (e: Exception) {}
}
}
To prevent this from happening:
class MainViewModel(
private val objectWhichContainsNetworking: ObjectWhichContainsNetworking
){
init{
ensureNeverFrozen()
}
//Etc
The most complicated thing with the memory model is this. Getting used to what's being captured and avoiding it. It's not that hard when you get used to it, but you need to learn the basics.
I've talked about this at length:
Practical Kotlin/Native Concurrency
Kotlin Native Concurrency Hands On
KotlinConf KN Concurrency
The memory model is changing, but it'll be quite a while before that lands. Once you get used to the memory model, the immutable issues are generally straightforward to diagnose.
Related
I'm using Koin 3.0.0-alpha-4 version and when I'm trying to use injected class by koin, then it throws exception for iOS side:
KotlinException=kotlin.IllegalStateException: Must be main thread, KotlinExceptionOrigin
I have a singleton class where I'm initializing objects using koin like that:
#ThreadLocal
object ObjectFactory : KoinComponent{
val appStateRepository: AppStateRepository by inject()
val appStateRepositoryDemo = AppStateRepository()
}
if I use appStateRepository inside background thread (Dispatchers.Default), which is injected by koin, then it throws exception IllegalStateException: Must be main thread, but if I use appStateRepositoryDemo than it works fine
Here is a method which I'm invoking from iOS to inject modules
fun initKoinIos(
userDefaults: NSUserDefaults,
doOnStartup: () -> Unit
): KoinApplication = initKoin {
module {
...
single { doOnStartup }
}
}
fun initKoin(appDeclaration: KoinAppDeclaration = {}) = startKoin {
appDeclaration()
modules(
platformModule,
networkModule,
useCaseModules,
repositoryModule,
commonUtils,
)
}
Here is the usage:
fun testRepoAccess() {
ObjFactory.appStateRepository.test() // Ok, here we are in main thread
CoroutineScope(Dispatchers.Default).launch {
ObjFactory.appStateRepositoryDemo.test() // Ok
ObjFactory.appStateRepository.test() // Not Ok, throws exception (Must be main thread)
}
}
Expected behavior
It should work for iOS like it works Android
Koin 3.0.0-alpha-4
Additional moduleDefinition
Coroutines 1.4.2-native-mt
UPDATE
I was using wrong Koin library name, now I'm using :
io.insert-koin:koin-core:3.0.1
and now I have another exception:
kotlin.native.IncorrectDereferenceException: illegal attempt to access non-shared org.koin.core.context.GlobalContext.KoinInstanceHolder#1c02ca8 from other thread
Koin on Kotlin/Native requires injection on the main thread, to avoid freezing Koin state. As a result, you can't actually inject directly from a background thread.
Adding a special inject method that would allow you to inject by switching to the main thread was intended to be added to Koin, but I'm pretty sure that never wound up in the code. Primarily because it's very rare that anybody has needed it.
So, anyway, you can't do that with Koin. You could try Kodein, but I wrote Koin's implementation to throw precisely because touching Koin from another thread will freeze everything inside it, and that may not be what you intend to do.
I know nobody likes non-answers, but why is ObjectFactory #ThreadLocal? I assume to keep it mutable, but if appStateRepository is a single, it would be frozen anyway.
This two code run exactly same. What is different putting Job in CoroutineScope and launch?
private val job = CoroutineScope(Dispatchers.Main).launch(start = CoroutineStart.LAZY) {
for(i in 10 downTo 0) {
Log.d("test", ": $i")
delay(1000)
}
}
CoroutineScope(Dispatchers.Main+job).launch{ }
CoroutineScope(Dispatchers.Main).launch(job) { }
Technically, both result in the same behavior, but the main point is that neither is a good way to use the CoroutineScope() factory. This is the idiomatic way to write the same thing:
GlobalScope.launch(Dispatchers.Main+job) { ... }
If this raises your eyebrows ("Don't use GlobalScope!"), that's because it should — your examples are just another way to make the same mistake, with more verbose code. You construct a CoroutineScope without holding a reference to it, resulting in exactly the same unbounded and non-cancellable scope as the GlobalScope singleton.
In addition, the way you use a Job instance that is a handle to an actual coroutine, is also wrong: the job associated with a coroutine scope should be a standalone instance returned from either Job() or SupervisorJob(). Its only purpose is to serve as the central point from which to cancel the entire scope or inspect its state.
It doesn't seem like there are many differences between the two, looking at the source code. The operator fun plus documentation states
Returns a context containing elements from this context and elements
from other context. The elements from this context with the same key
as in the other one are dropped.
which explains how your first test works. For the second, calling launch with a context parameter, calls into CoroutineScope.newCoroutineContext
Creates a context for the new coroutine. It installs
Dispatchers.Default when no other dispatcher or
ContinuationInterceptor is specified, and adds optional support for
debugging facilities (when turned on).
and looking at the source code of it:
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
We can see it also ends up using the operator fun plus.
There are some other differences that seem negligable to mention here, but ultimately the context of the launched coroutine looks to be the same in both of your tests.
I have a Kotlin function with this signature:
fun registerDisposer(obj: Any, disposer: Closeable)
What the function does is attach disposer to a phantom reference and arrange it to be closed when obj is garbage-collected (i.e. when the phantom reference object is enqueued). The class of obj is supposed to call it something like this:
class Holder(private val res1: Closeable, private val res2: Closeable) {
init {
registerDisposer(this, object: Closeable {
private val res1 = this#Holder.res1
private val res2 = this#Holder.res2
override fun close() {
res1.close()
res2.close()
}
})
}
}
(Let’s ignore whether this is a good idea to rely on this with general Closeables; the actual resource in question is a pointer managed by native/JNI code – I am trying to follow Hans Boehm’s advice. But all of this is not particularly relevant for this question.)
I am worried that this design makes it too easy to inadvertently pass an object that captures this from the outer scope, creating a reference loop and preventing the object from being garbage-collected at all:
registerDisposer(this, Closeable {
this.res1.close()
this.res2.close()
})
Is there an annotation I can add to the disposer parameter that will trigger a warning in this situation?
As of this writing, the answer seems to be: probably not.
It turns out a registerDisposer function already exists as the register method of java.lang.ref.Cleaner, and it has no such annotation.
In Android, there is a similar annotation for android.os.AsyncTask, but that simply warns at any anonymous object having AsyncTask as its base class, whether it captures this or not. (This makes sense in Java, where anonymous classes always capture this, but not in Kotlin.)
I'm new to Kotlin Coroutines and I want to call the API for each of my employees in asynchronous way. But I faced the problem that iside the new coroutine I'm unable to retrieve authentication from the SecurityContextHolder.getContext.
Can anybody explain please why SecurityContextHolder.getContext().authentication becomes equal null inside GlobalScope.async{...} block in Kotlin? Does a new coroutine have a separate security context? And how do I solve this issue? I there a way to avoid passing the authentication from the calling perform() function to the callApi() function?
Below you can find the code snippet:
fun perform() {
// SecurityContextHolder.getContext().authentication contains some value!!!
val deferred = employeesRepository.getEmployees().map { callApi(it) }
runBlocking {
deferred.forEach { it.await() }
}
}
fun callApi(employee: EmployeeModel) = GlobalScope.async {
// SecurityContextHolder.getContext().authentication is null here!!!
}
If I recall correctly the SecurityContextHolder.getContext() holds a thread-local reference to the authentication object. Using coroutines you actually switch to another thread (which does not have a thread-local authentication object).
I think passing the authentication object could work, that was my first idea too when i started to read your question. Why do you want to avoid this?
Perhaps you can create a coroutine context with auth object (or is there an existing one for this purpose?), but it's only a guess from me, i have no real experience with coroutines yet.
edit:
By a quick search i found this. You can find interesting ideas in this thread:
https://github.com/Kotlin/kotlinx.coroutines/issues/119
I started using corotuines when it was still in experimental. With anko, I had something like
async(UI) {
val request = bg { sendRequest() }
val result = request.await()
// UI work
}
I really like how it is structured and it does provide cleaner code vs callback hell imo. I just realized coroutines is now in stable channel and couldn't wait to try it out. I updated my kotlin and anko and now I have this
doAsync {
val result = sendRequest()
uiThread {
// ui work
}
}
Am I doing it correctly? This structure seems ugly to me. Although it might be more readable but I still like the old way of calling await(). Or maybe I miss something here? I remember one of the selling points when coroutines was introduced is less curly braces.
You don't need Anko to get good code with coroutines. Also, you don't need async and in fact you should avoid it for cases like yours, where you just want to make a non-blocking call and don't want to launch several such calls concurrently. Your basic idiom should be
myScope.launch {
val result = sendRequest()
// UI work
}
where sendRequest() is
suspend fun sendRequest() = withContext(Dispatchers.IO) { ... body ... }
If you are calling this from an Android Activity, then myScope can be just the implicit this, and your activity must implement CoroutineScope:
class MyActivity : AppCompatActivity, CoroutineScope {
override val coroutineContext = SupervisorJob() + Dispatchers.Main
...
}
To get more insights, Explicit Concurrency by Roman Elizarov is highly recommended reading.