I've needed to get this out of my system for a whi...
# kotlin-native
s
I've needed to get this out of my system for a while now. I just published "Why the Kotlin/Native memory model cannot hold". https://link.medium.com/3qoLYItchZ
🌶️ 5
🍿 8
👍 9
m
I must agree on claim that the K/N memory model is hurting the multiplatform dream, it just adds one more barrier for entry to JVM/iOS(/other) devs
o
does Kotlin/JS memory model hurts MPP?
m
Honestly, I didn't look into it 😄 I've focused on Android/iOS with potential JVM backend scenario.
s
@olonho The Kotlin/JS memory model is actually the JS model. There are no threading possible in JS. This is not Kotlin/JS : it's JS. All languages that are used for iOS development allow for threading, and they rarely crash at runtime enforcing their memory model.
➕ 3
Furthermore, the MPP story between Android & iOS is hurt when semantics is not the same in common code.
👍 2
n
Hi Salomon, nice summary, I do agree that point 3 could be the challenging area regarding adoption, we had the same experience at Realm where we tried to explain our memory model based on Thread confinement https://academy.realm.io/posts/threading-deep-dive/ still, there were push backs from some users, which are unfamiliar with this model. K/N pushes the envelope even further with frozen objects (I’d be also interested to see in your benchmark the memory cost of using immutable objects/frozen graphs which are usually the tradeoffs of this approach). I think performing static analysis of the threading model similar to https://clang.llvm.org/docs/ThreadSafetyAnalysis.html effort could help reduce the complexity/paradigm shift.
👍 1
s
Point 3 is by far the most destructive. However, I don't think static analysis can do enough without embedding immutability into the type system. I mean, given
class Foo(var bar: Int)
, the function
fun mutate(val foo: Foo) { foo.bar = 42 }
could crash if
foo
is frozen, and there is no way static analysis can know if it is. However, if
const
was in the type system, we could have declare
const class Foo
, which would have forbidden the use of
var
in it (which renders the use of the
freeze
API obsolete).
d
Bravo 👏 Most insightful article and a highly constructive criticism.
s
@darkmoon_uk Thanks a lot 🙂
If some want to give this story a bit more visibility 😉 https://twitter.com/salomonbrys/status/1163382279336615941
👍 3
k
Still working through this. I think there are good points, but I disagree on the degree that KN is “pushing programmers away”. Most of the anti-Kotlin talk I get is from iOS people and focused on interop and performance issues. No structs, that kind of thing.
Crashing at runtime can be unpleasant, but the counter argument would be concurrency issues in the JVM won’t crash at runtime, which is the problem. Is the freeze thing the way to solve it? That’s a debate, but runtime immutability checks aren’t a crazy unheard of thing. I do wish there was a way to run the JVM in a similar manner for consistency’s sake. However, I personally don’t find writing multiplatform code that difficult once you understand it.
Granted, it’s essentially all I’m doing now, so I’m way too close to it for any real perspective.
I do think if “relaxed mode” happens, and is generally available (not just privileged libraries), “strict mode” will not make much sense. Most apps need libraries, and most libraries will assume relaxed mode, so in practice, very few will use strict mode, but we’ll need to see how all that progresses.
l
I think it's too early to talk about relaxed mode. It could be not so relaxed, or could be fine grained, and could be reverted if it turns out to not be so great as Kotlin/Native is still in "moving fast" mode. And even after, it could be marked as experimental if you can enable it on a per-module basis, or in a finer grained way.
k
I agree, too early. As coded, it looks like it would make everything sharable, but again, could change or be restricted (there’s been a
shared
flag for a while, but it’s not public: https://github.com/JetBrains/kotlin-native/blob/1161e2c6b460002d7b854b032a6cfe07cae74df7/runtime/src/main/kotlin/kotlin/native/concurrent/MutableData.kt#L22). However, I was doing some more aggressive refactoring around Stately, but put that on the back burner until there’s more clarity. Plenty of other things to work on.
l
I kind of like the
const
keyword idea, but I'm afraid it'd be seen everywhere and consequently adding significant noise. I though about the contrary: all classes
const
by default, and a
mutable
or
var
modifier, but I think it'd be worse. Plus, what do you do with
interface
or
open
classes that could have mutable implementations/subclasses? I am wondering if Rust code readers suffer from noise coming from concurrency constructs of the language and its stdlib… 🤔 (I almost don't know Rust)
k
I don’t hate
const
classes, except that to achieve the goal you’d only be able to “share”
const
classes between threads. All other state would be permanently thread-confined. That would be significantly more confusing and frustrating than the current situation. If the idea is that
const
is optional and all state can be shared, OK, but I fail to understand what’s been solved. As for Rust-like language constructs, well, all for that if we could figure it out. Well, conceptually all for it anyway.
l
Would it be doable to let non owner threads read mutable state, but not edit it? cc @olonho That's not ideal in its own for the case of top level declarations or `object`s since the first thread to access it is implicit the owner, something that you might not always want, but it's enough to instantiate a dependency graph in a mobile app on the main thread. This, with working dispatcher switching in coroutines would allow to confine shared state mutation to UI thread. For me its enough to get a jump in iOS/Android code sharing with less hacks or platform specific code that is there only to satisfy Kotlin/Native runtime.
k
“Would it be doable to let non owner threads read mutable state, but not edit it?” I think not, as mutable state to multiple threads needs to be synchronized so that changes are properly visible to other threads. The compiler makes a lot of optimizations depending on if you’re synchronizing in some way. Could be wrong here, but that’s my guess.
We confine a lot of init to the main thread, then have global access to service objects. They’re “lightly mutable” with atomics. Once you “get it” and have some library support, I just haven’t found it to be that difficult (assuming you ignore the occasional “how the hell did that get frozen” issues).
l
Can't threads access the same memory, just like on the JVM, with only guards for when you attempt to mutate data? In other words, I'm proposing a frozen view for other threads that don't own the object graph.
I didn't understand how AtomicReference works. I tried to replace `var`s with it, but was still getting exceptions when the object enclosing the `AtomicReference`s was frozen.
k
For
AtomicReference
, what kind of errors?
s
I agree that
const
is not sufficient by itself. We also need a way to prevent non const static data.
k
@louiscad I may have been reading what you wrote incorrectly. Are you saying all state should be of this form (mutable by one thread, globally readable), or just like a special frozen/mutable delegate structure? I had played with that early on, but just mutating frozen with atomics was simpler. The issue is, immutability needs to be enforced on the other threads, so in the current paradigm, that means you have a mutable source and a frozen copy. You could do this pretty easily today with a delegate property (I think). If you mean all state should be of the form that the main thread can edit and other threads can read, essentially everything has do be the equivalent of
volatile
, which would I think be not great. Otherwise the compiler may decide the thread’s data is local and won’t be in a rush to make it available in main memory (among other issues)
@salomonbrys “We also need a way to prevent non const static data.” Do you mean prevent non-const data from being accessed from multiple threads?
s
Yep
k
So, the same as now except “frozen” happens in the language, by way of
const
, not the runtime?
In concept, that’s kind of what Swift structs is, yes? (with the added difference that “edits” don’t crash, they create a new copy)
s
I don't really know shift... I guess ???
l
@kpgalligan Would be nice as an opt-in to have a mutable by one thread, globally readable option (that's what I meant). Could be an annotation to opt-in, so by default, you don't get
volatile
equivalent, which means increased performance for these objects.
n
The Worker system isn't the only way to do concurrency in Kotlin Native. Coroutines are a option however the KotlinX Coroutines library has limited Kotlin Native support, which means the Kotlin Native version ( https://github.com/Kotlin/kotlinx.coroutines#native ) doesn't have feature parity with the Kotlin JVM, Android, and JS versions 😦.
Strongly disagree with the statement made in the article that Kotlin Native doesn't have its place. Already there are places where Kotlin Native would make a good fit. Areas that involve developing a client based program (GTK app for Linux, CLI apps etc) are a good one, provided there are no critical performance requirements (hard or firm real time performance - https://stackoverflow.com/questions/17308956/differences-between-hard-real-time-soft-real-time-and-firm-real-time ), and no bare metal access is needed (direct access to hardware, incl CPU like access to registers for instance).
➕ 1
Lack of multi-threading in Kotlin Native is a issue, however for back-end developers this is more of an annoyance than a major pain, which can be worked around. Put it this way there is a Kotlin Native back-end sample ( https://github.com/JetBrains/kotlin-native/tree/master/samples/nonBlockingEchoServer ) that works just fine with Coroutines instead of using the Worker system.
k
The worker and coroutines systems aren't mutually exclusive. Just saying
n
Does that mean Coroutines can be used in Workers? If so is there a sample that shows this in action?
l
@napperley There's a library maintained by @basher that allows you to run coroutines on workers: https://github.com/Autodesk/coroutineworker
👋 1
👍 2
k
We've had some in the Droidcon app for a while. They're not "multithread coroutines" in the sense you probably want, but you can do background work from a suspending function https://github.com/touchlab/DroidconKotlin/blob/master/sessionize/lib/src/commonMain/kotlin/co/touchlab/sessionize/platform/Functions.kt#L12
Haven't used Ben's library yet but it's on the Todo list
❤️ 1
l
Ben's/Autodesk's library seems to allow suspending functions to be called inside the worker coroutine, which can have use cases going a little beyond background work, good if you want or need it.
n
Strange that Ben did the initial commit for the Coroutine Worker library ( https://github.com/Autodesk/coroutineworker ) even though he doesn't work for AutoDesk.
d
May be a silly question; but is there anything to stop one using threading directly via the
pthread
API available on Posix native systems? Besides, of course, being back in an 'unsafe' place.
l
@napperley Ben definitely works for Autodesk!
n
Found the connection. Ben works for Plan Grid which is a subsidiary of AutoDesk.
k
Plangrid and Autodesk are in the same company (I think)
👌 2
l
@darkmoon_uk Nothing is preventing you from doing so, but `Worker`s in Kotlin/Native allow you to transfer object graphs, which is more desirable than mutability exceptions 😄
👍 1
k
@darkmoon_uk yes, you could do pthread
Worker uses pthread under the hood I believe
b
PlanGrid is owned by Autodesk. I work at Autodesk officially
Sorry haven't updated by github email yet
PlanGrid was acquired end of last year
n
In theory with the pthread API multi-threading would be possible on Linux x64/armv7 as a Kotlin Native target.
l
(off-topic) I wish they'd buy the company behind Shapr3D so we get first class Apple Pencil support on Fusion 360, and transfer/handoff between light iPad/tablet app and dektop/laptop app (plus an education/hobbyist/early-stage-startup license)
@napperley If you need it, you should try compiling a fork on your machine first, and if it works, submit a PR. Adding a Kotlin/native target is quite easy, usually a single line of code in the gradle script is enough as long as you're not relying on target specific APIs or depend on libraries not supporting these targets.
b
@napperley yep I just hadn't added those targets. not hard to add them. feel free to open an issue!
l
@kpgalligan
For
AtomicReference
, what kind of errors?
MutabilityException telling me the `AtomicReference`s themselves were frozen when I attempted to mutate their
value
var
member.
k
@louiscad Code sample? Here’s a block…
Copy code
import kotlin.native.concurrent.AtomicReference
import kotlin.native.concurrent.freeze

class AtomicThingBox(t:Thing){
    private val atom = AtomicReference(t.freeze())
    
    init {
        freeze()
    }
    
    var myThing:Thing
    get() = atom.value
    set(value) {
        atom.value = value.freeze()
    }
}

data class Thing(val s: String)
Anything going into the atomic reference should be frozen (it’ll throw if not. I’m not super happy about that design, but that’s what it does). If you’re getting an exception setting the
val
for the
AtomicReference
, the container may be frozen while init is happening and throw an error. Init order is vertical, AFAIK, so if you moved that init block to the top I think that would crash. I’m not running these samples now, so will need to confirm later (one of those busy days)
For the Droidcon app, I’ve been using frozen delegates:
Copy code
internal class FrozenDelegate<T>{
    private val delegateReference = AtomicReference<T?>(null)
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T = delegateReference.get()!!

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        delegateReference.set(value.freeze())
    }
}
Copy code
var sessionizeApi:SessionizeApi by ThreadLocalDelegate()
    var analyticsApi: AnalyticsApi by FrozenDelegate()
    var notificationsApi:NotificationsApi by FrozenDelegate()
You can just set them like regular JVM vars, but need to understand that what goes in gets frozen.
ThreadLocalDelegate
Is the counterpart to that. Whatever goes in there never leaves the thread you assigned it in, presumably the main thread.
They’re unnecessarily nullable. That’s going on the todo list
l
The issue was probably that I didn't
freeze
manually what went into the
AtomicReference
, thanks for the info! I'll reconsider what I reverted and test it in `object`s.
k
Yeah, post if issues. Can help (we may want to move the thread, though).
b
@salomonbrys You may want to correct a few of the first statements that are in large print near the top of your article:
Top-level objects are frozen by default.
This is only true for
object
(both
object
and
companion object
) vals. It's not true for globals. You have to mark them as
@SharedImmutable
if you want that behavior.
Top-level variables are thread-local by default.
That's not true. You have to mark them as
@ThreadLocal
if you want that behavior.
👍 1
j
I agree with 1,3. The inconsistency and runtime exceptions make developing common code challenging. I think second point is largely lack of documentation and good examples
l
@kpgalligan Should I use plain `AtomicReference`s and freeze their content through a delegate as you showed, or should I use
FreezableAtomicReference
?
k
I haven’t used
FreezableAtomicReference
. In theory it’s a good option. I think I’ve avoided it because I knew if the state was going to be frozen or not.
FreezableAtomicReference
makes sense if you don’t know the context in which the state will be used, but personally I try to be super clear on that.
I’ve also developed much of my “habits” before that was available, so kind of forgot about it.
l
Alright, I wrote this delegate:
Copy code
import kotlin.native.concurrent.FreezableAtomicReference
import kotlin.native.concurrent.freeze
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

internal class FrozenDelegate<T>(initialValue: T) : ReadWriteProperty<Any?, T> {

    private val reference = FreezableAtomicReference<T>(initialValue.freeze())

    init {
        freeze()
    }

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return reference.value
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        reference.value = value.freeze()
    }
}
and I also freezed the whole object that uses these in its own
init
block. Works like a charm, thank you very much for the help Kevin! Now, I'm wondering if I should always freeze what can be frozen. I assume yes, but please let me know if that's not the case.
k
" I also freezed the whole object that uses these in its own
init
block” I think
FreezableAtomicReference
does a couple extra checks internally on each access. Since you’re freezing anyway, there’s no benefit to
FreezableAtomicReference
. The extra checks aren’t going to dramatically impact performance, but you should probably just use
AtomicReference
in this case. If you don’t freeze the
FreezableAtomicReference
automatically, it’s a different situation.