It might be a strange question, but I will shoot i...
# kotlin-native
i
It might be a strange question, but I will shoot it anyway. When it’s “safe” to use
TransferMode.UNSAFE
for
Worker
transfer mode? Can we say if we are 100% sure that no one is going to mutate the thing we passed from the producer to the job, then it’s ok to use unsafe mode (and avoid freezing)? And second question what consequences to expect if smth goes wrong? (one thing I can think about is mem corruption issue, is there anything to watch out?)
o
it’s not about mutation, it’s about holding any reference to transferred object anywhere in the current thread/worker. If
SAFE
shows that you need freezing, using
UNSAFE
will lead to random crashes
k
I explain a bit of the detail on TransferMode here: https://medium.com/@kpgalligan/kotlin-native-stranger-threads-ep-2-208523d63c8f. Unfrozen state is intended to be thread confined, which means it’s state doesn’t need to be synchronized in any way. That includes the reference count value. I have an example you can run in that blog post to see what happens. I pass immutable, but not frozen, data classes around multiple threads. It will crash, or at least a memory warning.
i
Agh, completely forgot about ARC, yeah that makes sense. Thanks guys for the explanation.
One more question (sorry for bothering, trying to understand in deep workers and concurrency in KN), I saw the approach how to avoid callback to be frozen via ThreadLocal bound to main thread (https://github.com/JetBrains/kotlin-native/blob/2d882d1b6cb3d475d41b0b36b677aed4f8b323de/samples/objc/src/objcMain/kotlin/Async.kt#L58). Can the same be done via passing CPointer, or I’m missing smth:
Copy code
assert(NSThread.isMainThread())
val callbackRef = StableRef.create(callback).asCPointer()
worker.execute(
    mode = TransferMode.SAFE,
    producer = { (callbackRef to { execute() }).freeze() },
    job = {
        val callbackRef = it.first
        val result = it.second()
        dispatch_async_f(
            queue = NSOperationQueue.mainQueue.underlyingQueue,
            context = DetachedObjectGraph { (callbackRef to result).freeze() }.asCPointer(),
            work = staticCFunction { it ->
                val callbackRefAndResult = DetachedObjectGraph<Pair<COpaquePointer, R>>(it).attach()
                val callbackRef = callbackRefAndResult.first.asStableRef<(R) -> Unit>()
                val callback = callbackRef.get()
                val result = callbackRefAndResult.second
                callbackRef.dispose()
                callback(result)
            }
        )
    }
)
or freezing COpaquePointer will freeze the call back it referenced to as well?
k
Not sure, frankly. I have’t used StableRef in anything. Not sure how it works.
I assume StableRef makes sure whatever you get a pointer to is kept with a ref count > 0, so it won’t be dealloced, which means the graph it references does as well. If true, that seems like a reasonable alternative. We’ll need an opinion of somebody who’s done anything with it, though.
o
stable reference is not that different to regular Kotlin reference, just compatible with C
void*
and holds the reference (by incrementing reference counter). So the answer is yes, if you want to use it in the thread object is attached to, and no otherwise.
k
Deep diving into the native runtime, it looks like that’s exactly what it does. AddRef: https://github.com/JetBrains/kotlin-native/blob/master/runtime/src/main/cpp/Memory.cpp#L1600
@olonho Was asking about same thread, and you edited your response
o
yes, I realized that it could be understood differently, and clearly stated that.
k
That’s simpler than keeping in a thread local, although need to make sure ref is disposed
i
Thx!