Hi, In Kotlin/Native is there a pattern that could...
# kotlin-native
n
Hi, In Kotlin/Native is there a pattern that could be used to get notified about the object that is being collected through Reference Counting? here’s my use case: Let’s say you have a Kotlin class that calls (using C-interop) some dynamic library which perform dynamic allocation on the heap, the pointer of this allocation is stored in the Kotlin class as a
Long
so it can be passed later to make some invocations via the C-interop (the
Long
value is
reinterpret_cast<CppObj*>(pointer)
to whatever the original C/C++ object is), usually we want to deallocate explicitly this pointer when the object get GC’ed via the reference counter. I’m aware of the existing memory management approaches (memScope/arena etc.) but in this scenario we can not wrap every instance of the Kotlin class inside a memScope/arena, these are instances passed to the user of the API so we don’t control their lifecycles, this is why deallocating the C++ resources should be tied to the collection of the original Kotlin instance (to draw a parallel, this is the pattern used in Java with Phantom reference & a referenceQueue to close native pointers, see CrazyBob presentation

https://www.youtube.com/watch?v=KTC0g14ImPc)

2
a
Another use case is when a class allocates some native resources in its init section and is used by multiple threads. You could add destory method to the class, but you can't even call it since you never know whether it's still being used or not. I really would like some sort of deinit section.
a
a
Already read, yeah this is what we have, sad panda. Even if you have a life cycle, you still can't guarantee that an object is not being used by e.g. another thread any more. Like in Android if an activity is destoryed (its onDestroy method is called) you can't just free up all the native resources, since you never know who using them. This is error prone and unsafe, since you might get segmentation faults etc.
Here is the exact use case from my practice: you are implementing a repository class that is supposed to be used by multiple threads. In order to synchronize threads you need to allocate POSIX mutex using pthread_mutex_init in repository's init section. And perhaps some other native resources. Then you add destroy method to the repository where you free the allocated resources. At which point of time you gonna call destroy?
s
.destroy()
should make all subsequent resource usage attempts fail in a predictable explicit way. If you actually have multiple independent users of the resource in your code, then consider implementing manual-RC-style management for the resource.
I really would like some sort of deinit section.
Speaking of how
deinit
section helps to make your code more safe. Getting segmentation fault in Swift:
Copy code
var topLevel: Any? = nil

class Test {
    deinit {
        topLevel = self
    }
}

Test()
print(topLevel)
Pure Swift, without native resources or even “unsafe” language concepts like
!
.
a
Yeah manual RC is exactly what I did and dislike it. Since you can't call destroy while someone is inside mutex (acquired it). Your example is so weird I even never thought about such a usage. This is much harder and much more rare than releasing a resource being used by another thread. Think about BlockingQueue implementation, how would you release its resources?
Then remove !! please, because someone can use it incorrectly.
n
Hi Svyatoslav, thanks for your inputs I think we all agree that the usage of finalizers could be problematic (these are the same issues present in Java - see Effective Java item 7) hence the presence of the Reference API that solves the aforementioned use case gracefully. I’m not asking about implementing a finalizers or reference API a-la Java but a mechanism to address https://github.com/JetBrains/kotlin-native/issues/2327 without introducing manual RC (which somehow leak the abstraction because it forces the user to “think” about releasing a resource he/she shouldn’t be aware it exists first place). One workaround I thought about could be to use the the introduced
WeakReference
to build an equivalent of a “ReferenceQueue”, which will be populated by a weak ref to newly created objects, then regularly traverse this queue to check for GC’ed element (by calling
instance.ref.get() == null
we can then safely close the native resource associated with it. However this requires accessing/mutating this queue in a worker thread which is not possible I think with the threading model of K/N?
s
Since you can’t call destroy while someone is inside mutex (acquired it).
Exactly. So with manual RC approach you can make “someone” a resource user too, incrementing the RC before acquiring the mutex and decrementing after releasing the mutex.
Your example is so weird
I believe that it is possible to make an example which will not be “so weird” but will still be unsafe.
deinit
-like approach is fundamentally dangerous and broken, that’s why I’m trying to demonstrate.
Then remove !! please, because someone can use it incorrectly.
There is a huge difference between
!!
(which can throw easily findable error with obvious immediate cause) and segmentation fault caused by the unobvious and hardly discoverable fact that you can’t use some operations in
deinit
.
I think we all agree that the usage of finalizers could be problematic (these are the same issues present in Java - see Effective Java item 7) hence the presence of the Reference API that solves the aforementioned use case gracefully.
Reference-like API is somewhat more safe than Swift-like
deinit
. However implementing such an API has an impact to memory manager, and that of Kotlin/Native is still being actively developed and heavily reworked quite often. So implementing reference-like API now would be harmful at least to the evolution of Kotlin/Native memory manager and concurrency model.
a
So is not it a good idea to provide a mechanism to automatically release allocated resources at the end of life cycle (without client interaction)? Not exactly deinit section, any kind would work. So you will have less segmentation faults due to inappropriate usage of your class? Things are even worse if you are modifying existing class that is used in many many places, adding resources that must be released.
Hope at some point we will get something that would help. Manual RC is the only way at the moment.
n
@svyatoslav.scherbina I would be interested to know your thoughts about the workaround I mentioned? I’m trying to validate my assumptions here
s
So is not it a good idea to provide a mechanism to automatically release allocated resources at the end of life cycle (without client interaction)?
This is disputable. See e.g. the links above.
So you will have less segmentation faults due to inappropriate usage of your class?
If you get segmentation faults due to inappropriate usage of your class, then your class is probably implemented wrong since it fails to properly abstract an unsafe resource.
One workaround I thought about could be to use the the introduced
WeakReference
to build an equivalent of a “ReferenceQueue”, which will be populated by a weak ref to newly created objects, then regularly traverse this queue to check for GC’ed element (by calling
instance.ref.get() == null
we can then safely close the native resource associated with it. However this requires accessing/mutating this queue in a worker thread which is not possible I think with the threading model of K/N?
If you resources are shared in terms of K/N threading model (e.g. native handles or frozen wrappers for them), then you can implement a shared queue and process it in the worker.
n
So each time I need to process the queue I need to freeze a copy of it & send it to a worker thread
s
No. You can make the original queue shared using
AtomicReference
.
👍 1
a
@svyatoslav.scherbina Please consider the following class, how would you destroy it safely?
Copy code
class MyRepository {
    private val arena = Arena()
    private val mutex = arena.alloc<pthread_mutex_t>()

    init {
        pthread_mutex_init(mutex.ptr, null)
    }

    fun destroy() {
        arena.clear()
    }
    
    // Can be called from different threads concurrently
    fun put(value: String) {
        pthread_mutex_lock(mutex.ptr)
        try {
            // Put the value somewhere
        } finally {
            pthread_mutex_unlock(mutex.ptr)
        }
    }
}
s
@Arkadii Ivanov why not use one of well-known techniques? E.g.
Copy code
class MyRepository {
    private val arena = Arena()
    private val mutex = arena.alloc<pthread_mutex_t>()

    private val refCount = AtomicInt(1)
    private val destroyed = AtomicInt(0)

    private fun decrementRefCount() {
        if (refCount.addAndGet(-1) == 0) {
            arena.clear()
        }
    }

    init {
        pthread_mutex_init(mutex.ptr, null)
    }

    fun destroy() {
        if (!destroyed.compareAndSet(0, 1)) error("Already destroyed")
        decrementRefCount()
    }

    // Can be called from different threads concurrently
    fun put(value: String) {
        do {
            val currentRefCount = refCount.value
            if (currentRefCount <= 0) error("Already destroyed")
        } while (!refCount.compareAndSet(currentRefCount, currentRefCount + 1))

        pthread_mutex_lock(mutex.ptr)
        try {
            // Put the value somewhere
        } finally {
            pthread_mutex_unlock(mutex.ptr)
            decrementRefCount()
        }
    }
}
Have not tested. May vary depending on what behaviour you need for destroy-while-in-use etc. Btw, your code lacks
pthread_mutex_destroy
.
✔️ 1
a
There are race conditions in the provided code, if one thread leaves
put
method it will possibly release the resources. At the same time another thread may execute
put
and will crash with
Already disposed
error. But I did something similar, there were two additional methods like
incRefCount
and
decRefCount
and they were called from client side. That's why I dislike it, it should not be clients' responsibility.
s
here are race conditions in the provided code, if one thread leaves
put
method it will possibly release the resources. At the same time another thread may execute
put
and will crash with
Already disposed
error
This can happen only after someone called
destroy
. Throwing an error if the resource is used after destroy is not a race condition, it is the correct behaviour in this case. That’s what manual resource management is. Also your original complain was about segmentation faults. Can this code cause a segmentation fault?
But I did something similar, there were two additional methods like
incRefCount
and
decRefCount
and they were called from client side. That’s why I dislike it, it should not be clients’ responsibility.
Well, in my approach the clients aren’t responsible for reference counting here.