Hi, is there any good example of transferring owne...
# kotlin-native
a
Hi, is there any good example of transferring ownership in K/N? We're struggling to pass object hierarchy created in one thread to another one. It will be mutated there so just .freeze() doesn't work.
r
You might look at Stately and see if that helps your use case https://github.com/touchlab/Stately But note that Kotlin/Native is strongly opinionated about not passing mutable state between threads so you might want to reconsider what you're trying to do.
k
It’s definitely doable but difficult https://link.medium.com/ImLf4V4MJW see “Transferring Data”. Post some code about what the trouble is
Background thread calls a function arg b(), then that value is detached and sent to main thread
Detaching l O-N, because it looks at each element to make sure there are no external references. That can be significant
For example, if you implement a shared cache with a map, and detach on each operation, instead of constant time lookup you'll get O-N, which defeats the purpose of a map. More concretely, if you had a cache with 100k elements, looking at one would be quick, but then detaching heeds to visit all 100k
o
It really depends, high performing shared cache can store opaque reference (https://github.com/JetBrains/kotlin-native/blob/0bdb14e5972c3c3885fdb89983649e2d1680b215/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/StableRef.kt#L30) to an object, and only materialize it in one thread, for example. It's possible to do it smarter, in general.
k
Totally agree. There are smarter ways to do it. Just wanted to be clear that detaching isn’t free. I’ve had multiple conversations where the plan was to keep a map like that and I had to explain why that’ll ruin the “hash” benefit in a hashmap. To understand what you’re thinking for “high performing shared cache”, you mean the cache only appears in one thread, and operations on it are passed by thread message (with a worker, presumably)? So, ‘get’ would send the key to the worker, and return the value as a stable ref?
a
Thanks a lot for the explanations! Sounds like detaching is not a way to go for us due to the mentioned performance impact. In our particular use case (iOS platform) we process GPS locations via iOS GCD queue sequentially, put each processed location result in a list and at some point store to a db. Effectively we don't change data concurrently, just the list needs to be passed to the next processing thread from a GCD pool. Prob we can use some data structure from Stately, but overall sounds like the K/N rigid concurrency checks just don't make sense here. UNSAFE mode looks legitimate then.
o
hmm, your data seems naturaly immutable, why cannot you just share frozen data. Also note, that another approach for sharing non-structural data is https://github.com/JetBrains/kotlin-native/blob/0bdb14e5972c3c3885fdb89983649e2d1680b215/runtime/src/main/kotlin/kotlin/native/concurrent/MutableData.kt#L22
k
If the GCD thread is always the same, you could just store the values in a standard mutable collection, but freeze them, then on whatever db schedule you have, send them off to a worker or another GCD queue. I don’t think freezing or detaching in your case is going to be performance prohibitive. I was talking worst cases (don’t try to keep a map and detach on each operation). Stately has a frozen linked list, but if you are worried about performance, it’s not a great option, and if your GCD queue is serial, not really necessary.
Also, I noticed “UNSAFE mode”. Not sure what the context would be, but it’s probably not a good idea. You could get race condition related crashes that will be very difficult to debug.
a
Ok, we prob can make it working with SAFE mode by having a single thread to mutate the list: worker gets frozen data, processes location and returns a result, then smth outside mutates the list. So list is not shared in that case, instead some frozen immutable copy of it is passed to the worker.
But still what's wrong with UNSAFE approach if we're sure that we'll never access the shared data from parallel threads, only sequentially, just from different threads?
k
Presumably you’re passing to a worker…
data class SomeData(val a:String) private val w = Worker.start() fun crashy(){ val someData = SomeData(“Hello”) w.execute(TransferMode.SAFE, {someData}){ //whatever } }
Now, you never reference
someData
after you pass it to
execute
, so it can be unsafe, right? Nope.
The runtime is doing reference counting. When the method
crashy
ends, the runtime subtracts 1 on the ref count for
someData
, but it’s possible that the worker has started, which means 2 threads may be modifying the ref count. Non-sharable data does non-atomic ref counting, so you can wind up deallocating data you’re still referencing.
TL;DR, you should probably never use UNSAFE
p
What we noticed to be quite fast is to use StableRef and ensure only a single thread is mutating the reference data. It internally generates a heap pointer and is originally used to share data with interops. The clear disadvantage, one has to hold on the StableRef and explicitly call dispose. We do not use Worker’s but rather DispatchQueues on iOS for Android Loopers.
k
How do you ensure?
I mean, guess it’s not important. Just that it can get tricky. I have to look at stable ref more. I thought you had to explicitly detach to create that, but maybe not?
p
The interesting part is one can freeze a StableRef as it’s only internal member var is a COpaquePointer. Hence, there’s no need to do a complex graph traversal to freeze or detach. Usage example can be found here https://github.com/JetBrains/kotlin-native/blob/3e10e15f97f95f80b598b7bb0996d68575d8209a/runtime/src/main/kotlin/kotlin/native/concurrent/Continuation.kt https://github.com/JetBrains/kotlin-native/blob/d483990c7ef2de2c369bfb05b37296084ea6281b/runtime/src/main/cpp/Memory.cpp#L1892
If I understood it correctly, StableRef is like a manual reference count increase, but only if your memory container is not stack based, e.g. not a local variable in a function. We try to attach the StableRef as frozen property to managed objects and try to ensure manually to invoke dispose. This happens for us only on performance critical parts. One should use it really carefully as memory leaks are easy to create that way!
a
StableRef looks like a promising workaround. It hides a shared data from K/N ARC (so no concurrent access to the ref count) and allows to mutate it from a worker. All these hacks make me thinking that K/N saner concurrency at this point doesn't handle some basic use cases, like I don't have concurrent access (refcount is surely not something I want to care about explicitly) but still need to fight with K/N concurrency checks.
k
“It hides a shared data from K/N ARC” it definitely won’t do that. You’ll be responsible for making sure multiple workers aren’t concurrently accessing
“All these hacks make me thinking that K/N saner concurrency at this point doesn’t handle some basic use cases” I would characterize it that K/N forces you to think differently about architecture. You are attempting to hack it out of the gate, as stated
To be fair, I have no idea what your use case actually looks like
The basic rule is you can’t mutate state from multiple threads with out some gymnastics. Java developers are very used to doing that, but that’s not true in all languages, and can certainly be a source of trouble. Personally, I’ve found K/N rules to make more sense once you internalize them, but trying to work around them to follow existing patterns will definitely feel like swimming against the current
(In my experience. This is not a universal opinion)
a
If I need to mutate a data from multiple threads sequentially (mutate in thread 1, then in thread 2, etc.) would you state that K/N rules still should be applied? Like what wrong could happen if not talking about internals like refcount?
k
I guess I want to unpack this a bit. “If I need to mutate a data from multiple threads sequentially”. I don’t know your situation, but an alternative here would be to have a single thread that gets passed that data rather than self-managing concurrency. You may not “need” to architect it that way. Having said that, it does look like you can use stable ref and locks, but you also need to be really careful about leaking references (which is what detach would be looking for). “would you state that K/N rules still should be applied?” Are you asking if I think K/N rules are still a good idea? Yes. I think it should be difficult and rare that you manage concurrency directly. It’s error prone. You can definitely work around the rules, but I don’t think that should be the first thing to try to do, unless you really need to, and except for very critical code, you probably don’t. It’s not really for “you”. It’s for 2 years from now when a different developer decides they need to hold on to a reference from your shared mutable state, and your tests pass, but now you have a weird race condition (spoiler, it’ll be very hard to track down).
Having said all that, that’s what Stately is about, at least some of it. Tools to work around the rules. In general you should avoid using them, but if you need them, there they are. A lock, for example: https://github.com/touchlab/Stately/blob/master/stately/src/commonMain/kotlin/co/touchlab/stately/concurrency/Lock.kt
s
StableRef looks like a promising workaround. It hides a shared data from K/N ARC (so no concurrent access to the ref count) and allows to mutate it from a worker.
It is not supposed to work that way. Unfrozen objects are bound to their threads until explicitly transferred using corresponding APIs.
StableRef
will likely have proper checks and forbid such an abuse with accessing from wrong thread.
a
@svyatoslav.scherbina i see, thanks for the info! Should we expect that UNSAFE mode will be deprecated in the short/middle term as well?
s
Not sure about that, but I recommend avoiding UNSAFE mode anyway.
I’d think it would be better to make UNSAFE at least less visible and more difficult to call, but in any case, best not to use.