is there any way to encapsulate manual memory mana...
# kotlin-native
k
is there any way to encapsulate manual memory management? The only thing I can think of is to hook into the GC cycle and free memory when an object is getting collected. This is essentially a finalizer though, which are evil. Has anyone figured out a better way to do this so manual memory management doesn't pollute a whole codebase?
a
I think you’ve seen this (https://github.com/JetBrains/kotlin-native/issues/2327) GH discussion and the SO answer inside. Can you explain, why the approach from that answer is not usable for you?
k
This is the first time I'm seeing this. Thanks 🙂
so my suspicion is true, that you need to essentially have some sort of automatic resource cleanup via
use
-like functions.
a
Use functions work only if the lifetime of a resource is bound to the function invocation. However this is not always the case. E.g. you have an object that uses K/N lock internally. This object (and so the lock) is accessed by multiple threads. You can't dispose the lock because you never know how many threads are still using your object. And a deinit function is the only way to safely dispose the lock, when the object holding the lock is not referenced by anyone. I'm really missing this sort of functionality. And to be more specific: e.g. you allocated this object in a screen, the screen may have its destroy method. But you can't dispose the object there, since it is may be in use. Even stopping all the threads does not guarantee they won't access the object.
s
@Arkadii Ivanov it is still not clear for me why are you avoiding the approach I proposed before: https://kotlinlang.slack.com/archives/C3SGXARS6/p1565353444212800?thread_ts=1565340275.206700
a
I'm using because there is no other way. But this is just a workaround that pollutes the code and the API. I would prefer explicit deinit. We could forbid it leaking
this
from the deinit block. E.g. show warning same was as it does when leaking
this
from init block. Or even error.
Also you mentioned that with automatic memory management there is no guarantee when deinit will be called. I don't need any guarantee. I actually don't care when it will be called, immediately when there are no references or only on memory pressure. I just need the deinit block to be called at some point when there are strictly no references to my object.
s
But this is just a workaround that pollutes the code and the API.
How does it pollute the API? Also this approach clearly demonstrates that your statement above is not entirely correct:
You can’t dispose the lock because you never know how many threads are still using your object. And a deinit function is the only way to safely dispose the lock,
The example shows how to know exactly how many threads are still using the lock, and how to safely dispose the lock without
deinit
function. You also can properly encapsulate this to a lock class, thus making usages of the lock clean.
a
Well your particular example is not usable. Since the object holding the lock may be destroyed while being transferred to an another thread. And when another thread will call the
put
method it may crash. So you have to expose ref counting methods as API and call
increment
before transferring the object.
Anyway you still need to implement counting by your self. And I believe this is even more error pron than allow the
deinit
block with forbidden
this
leaking.
s
Since the object holding the lock may be destroyed while being transferred to an another thread.
Could you clarify? What exactly do you mean by “transferred to an another thread”? What do you mean by “object … may be destroyed”? What is “object holding the lock”?
And when another thread will call the
put
method it may crash.
What exactly may crash in
put
and how?
a
I will provide my real life use case later, thanks
As of the provided example. If you pass the MyRepository instance to a background thread, then this thread will crash if it call the
put
method when MyRepository is already destroyed. Let say you have a view controller (or a screen) that has onDestroy method. You will create MyRepository in view controller's constructor and call MyRepository.destroy() in view controller's onDestroy method. This call is unsafe since the instance of MyRepository may be used by a background thread. This thread will crash on next call of MyRepository.put(). You can change the implementation of the MyRepository so it won't crash. Then you probably will do nothing. This is as bad as crashing.
s
I see. So your case is not actually about destroying the lock while it is taken, but about destroying the lock that can potentially be taken? This particular case still doesn’t sound like something that is not implementable safely with K/N. Do you have more cases that are not locks?
a
I will try to recall something not related to locks. But at the moment I have the following real case. DelayQueue. You can find its sources here https://github.com/badoo/Reaktive/blob/master/reaktive/src/nativeCommonMain/kotlin/com/badoo/reaktive/utils/DelayQueue.kt So it uses locks and conditions. Of course the DelayQueue itself becomes a resource and has destroy method. In our particular cases we always have many writer threads and only one reader thread. We added additional terminate method to the DelayQueue which switches the queue to terminated state. After that the queue does not accept any new data. And the reader thread immediately reads null which considered as termination. The reader thread then destroys the queue. Plus if there were more than one reader thread then it would be impossible to safely destroy the queue. Deinit block would make it much simpler.
k
Something that I'm questioning. When Java interops with native code, how do they handle manual memory management? Since Kotlin native is going to have large portions primarily written to interact with C (eg. native Kotlin IO), aren't we going to have this memory management issue persisted all the way up even from first class libraries? I am by no means a fan of a
deinit
block, as finalizers are evil. They've been removed from Java for a reason. If for whatever reason an Exception is thrown in them, you're SOL. I'm just wondering how this explicit choice is going to persist into things that are fairly standard for a language. Eg. IO
s
@Arkadii Ivanov I see, thank you. These examples actually look like motivation to provide locks and condition variables that aren’t resources.
a
@svyatoslav.scherbina that would save my life, thanks!
i
@svyatoslav.scherbina, I would like to go back to the initial comment of this thread made by @kevin.cianfarini, regarding ways of disposing memory when using the C interop. I don’t believe the proposed solution to @Arkadii Ivanov’s example helps with that, unless I am mistaken. My group is interested in creating Kotlin libraries that can be integrated with Swift/ObjC, Java, Kotlin, JS, C++ and C# applications. The lack of a hook to properly destruct things - just in the cinterop case - has a big impact on these libraries. For the majority of the languages we can (for example/in particular) pass in a lambda from the client to an async library function, and that lambda can capture state, and everything will properly be disposed, eventually, if/when garbage collected. In C-land, however, we need to set the function and user data, and we need to ensure that all code paths (including ones outside of our control) properly dispose of this user data using manual reference counting techniques. This then has implications on all of the implementation of the library, and then pollutes every single other integration. In the case of Swift/ObjC, Kotlin/Native is “integrated with Objective-C/Swift reference counting”, as the doc says, which involves callbacks in the garbage collection to send the appropriate messages to e.g. dispose associated objects. Why can’t there be a similar hook in the C interop? It seems this one language is crippled for a reason that somehow was not enforced with Objective-C.
@svyatoslav.scherbina would there be a way to have a
Copy code
lib_symbols()->associateMetaData(NativePtr *pinned, void *data, void (*dispose)(void*))
function, that would call dispose(data) when pinned is garbage collected? This would give the same flexibility as objective C and I believe would be enough for me to manually handle lambdas without resorting to adding addref/release’s in the Kotlin code.
s
Why can’t there be a similar hook in the C interop? It seems this one language is crippled for a reason that somehow was not enforced with Objective-C.
Objective-C interop doesn’t provide any particular memory management hook by itself.
would there be a way to have a
lib_symbols()->associateMetaData(NativePtr *pinned, void *data, void (*dispose)(void*))
function
This approach doesn’t seem to be implementable efficiently. I understand your demand for “finalizers” to simplify using C libraries. However, according to our experience, any similar machinery in language should be carefully designed first. Also consider all issues mentioned in previous discussions.
i
Thanks for answering Svyatoslav. I was hoping it would be as simple as adding a C equivalent to Kotlin_ObjCExport_releaseAssociatedObject in Memory.cpp. I thought that this was the mechanism that was used to signal the Objective-C memory management. How does Objective-C know it can garbage-collect lambdas? If it is not using an explicit signal/hook from kotlin/native, perhaps I can use a similar mechanism. Weakrefs and a garbage collection pass? Note I am not trying to use C libraries. I am trying to use a Kotlin library, within C++. Ironically, the C++ code does not require manual memory tracking, while the Kotlin code does…
s
I was hoping it would be as simple as adding a C equivalent to Kotlin_ObjCExport_releaseAssociatedObject in Memory.cpp.
This approach is not scalable efficiently. Also there is a huge difference between design and implementation. Controversial complicated features like finalizers shouldn’t be derived from the implementation.
How does Objective-C know it can garbage-collect lambdas?
Do you mean Objective-C blocks or Kotlin lambdas?
Note I am not trying to use C libraries. I am trying to use a Kotlin library, within C++.
Then why exactly do you need to hook Kotlin object deallocation? What is your case, what exactly are you trying to bind to Kotlin object lifetime?
i
I’m talking about non-Kotlin lambdas being passed into a Kotlin function, for example, a continuation for an async operation. I pass in a lambda to Kotlin, it returns immediately, but it holds on to the lambda to callback when the async work is done. In all platforms except C, this is supported and afaict cleanly handled with garbage collection. I assumed Swift/ObjC was only able to do this based on a hint from Kotlin. The Kotlin libraries we would like to build are heavily async, and I want to avoid every one of these calls having to manually manage the lifetime of callbacks, especially given that all of this lifetime code is unnecessary on every other platform/language.
Afaict, ObjCExport.mm handles all of this, based on
Kotlin_ObjCExport_releaseAssociatedObject
being called by the Kotlin/Native garbage collector. This means that Kotlin libraries can be written using Kotlin idioms, and used cleanly in Swift etc.. Yes, I understand that it opens a door - we now essentially have finalizers when using it in ObjectiveC (I can associate an object in ObjectiveC to a Kotlin-wrapped object, and do whatever I want in the ‘dispose’ callback). However, as the ObjectiveC implementation also shows, you have very little choice if you want to avoid forcing error-prone, manual reference-counting patterns into the Kotlin code.
s
a continuation for an async operation.
In this case you can call your deallocation code from the continuation itself. Do you have other cases that can’t be handled with this approach?
we now essentially have finalizers when using it in ObjectiveC (I can associate an object in ObjectiveC to a Kotlin-wrapped object, and do whatever I want in the ‘dispose’ callback).
Not exactly. E.g. this doesn’t give you access to a Kotlin object which is being/going to be reclaimed, unlike finalizers/destructors/etc.
i
In this case you can call your deallocation code from the continuation itself. Do you have other cases that can’t be handled with this approach?
This assumes the continuation is called.
Not exactly. E.g. this doesn’t give you access to a Kotlin object which is being/going to be reclaimed, unlike finalizers/destructors/etc.
Good point, although this is fine with me; I would not expect to have access to this. I’m not trying to add a Kotlin finalizer, I’m trying to gc my related structures.
s
This assumes the continuation is called.
Exactly. That’s why I’m asking “Do you have other cases that can’t be handled with this approach?”
i
Well, it assumes that the lambda you are passing in is only being called once. So this approach can “work”, for the continuation case, but not in general for lambdas being passed in. It is essentially reference counting where you assume the count is one, and suffers from all the errors that can occur with manual reference counting. (Does the objective-C approach use this assumption? I can’t say I really tested this across a garbage collection event; if lambdas are not properly internally reference-counted in Objective-C I will have issues there as well.)
s
That’s not what I’m asking. Yes, this approach doesn’t handle arbitrary lambdas. Your initial requests features specific case — continuations. That’s why I proposed a solution for this specific case. Do you have other cases massively occurring in your code base?
i
Hmm, the initial example case was for handling lambdas, continuations were an example usage. Another situation is passing in are callbacks (e.g. an http callback), which would be held by the Kotlin code and used multiple times. My group and other groups will be writing continuation-heavy code (since it is a heavily async code base). Overall, the initial question of ‘why is it different for Objective-C?’ still intrigues me. Why, for this platform, do we need to look for specific workarounds for common programming scenarios? e.g., on a Mac, I can integrate the objective-C easily into a C++ application, with proper handling of all of these scenarios. However, on Windows, I’m now forced to write my Kotlin code differently, polluting it with reference counting, as Kevin initially pointed out.
s
why is it different for Objective-C?
Because Objective-C has automated memory management, while C doesn’t.
i
Because Objective-C has automated memory management, while C doesn’t.
How does Objective-C automatically dispose of memory associated with Kotlin without Kotlin’s help? e.g., if a lambda is passed into Kotlin, how does Objective-C know it is safe to dispose it? Is this a pattern I can apply as a user of the K/N C library? Is the Objective-C integration written relative to the C library? True, C does not have automated memory management, but without this hook, it seems to me it is very difficult for library users to put this automation in for higher-level programming languages/applications.
s
How does Objective-C automatically dispose of memory associated with Kotlin without Kotlin’s help? e.g., if a lambda is passed into Kotlin, how does Objective-C know it is safe to dispose it?
Because Kotlin `retain`s the lambda when gets it, creates Kotlin wrapper for it, and `release`s the lambda it when the wrapper is reclaimed.
Is this a pattern I can apply as a user of the K/N C library?
No.
Is the Objective-C integration written relative to the C library?
Could you clarify?
but without this hook, it seems to me it is very difficult for library users to put this automation in for higher-level programming languages/applications.
I get it. This doesn’t make me implement it immediately though.