djinni
djinni copied to clipboard
Proxy Cache and Proxy Object lifecycle
I am pushing for my company to use djinni to solve its cross-platform mobile dev woes, it's so elegant I just love it. I have a proof of concept working, the amount of redundant handwritten code that we are going to be able to ditch is mouth watering.
The one remaining question blocking full on adoption is around object lifecycle accross language boundaries through the Proxy Cache. Let's take the simple weatherservice listener example used in @j4cbo & @artwyman CppCon 2015 presentation :
https://youtu.be/K-k-axW2utc?t=2237
User implements and instanciates a listener on the java side, which is then passed to cpp using add_listener. Let's assume that call to add_listener kicks off a time consuming process on the cpp side. What happens if mobile devs forget to call remove_listener after they are done using the service, while the app lives on and does other stuff ? From my understanding of how the Proxy Cache works, the java side listener could very well be garbage collected without cpp becoming aware of it, so cpp client would keep on churning uselessly, and worse, could attempt to call back to a dangling pointer on the java side ?
https://youtu.be/K-k-axW2utc?t=2475
That scenario doesn't look far-fetched to me and I am curious as to why the Proxy Cache wasn't designed to have a strong ref to the proxy object instead of a weak ref, such that when the implementation object goes away, the proxy is cleaned up as well. That means of course that on the cpp side we should be using weak_ptr (and checking for them to become expired) to refer to the proxy object instead of the current shared_ptr.
Would love to hear thoughts about the feasability and desirability of that feature before hacking at it.
The C++ side gets a shared_ptr
that is a proxy to your Java object. As long as this shared_ptr
is kept around for the duration of the background task the Java object does not get garbage collected. The proxy cache is irrelevant for object lifetime as it only stores weak references. This is by design since it is up to you to determine when objects are no longer necessary. The proxy cache is a transparent optimization to make sure the same Java object results in the same shared_ptr
if passed to C++ multiple times.
So it cannot happen, that your C++ code ever has a "dangling" shared_ptr
that is not backed by a Java object. The code behind remove_listener
in C++ should simply assign nullptr
to its shared_ptr
and that allows the Java object to be reclaimed if it's the last shared_ptr
.
You cannot use weak_ptr
on the C++ side because there is no other shared_ptr
keeping it alive. It expires the moment the C++ function returns. Hence the necessity for remove_listener
to reset the shared_ptr
.
If the proxy cache kept a shared_ptr
to the object then you would have no way of ending its lifetime without direct interaction with the Djinni runtime. The point of Djinni is to hide the fact you're dealing with other languages from your code.
Thanks.
- I got mixed up with the strong/weak refs, I understand it's not possible to get a dangling pointer in that way.
- Having the cache hold a shared_ptr is indeed not a good idea.
- I also get the design motivation to be as transparent as possible when you look at those Implementation / Proxy objects from one language or the other.
Coming back to my original attempt of having an object in Java not tied to any strong refs on the cpp side, wouldn't it make sense to have a way of seeing the Implementation object as a weak ref to the proxy object ?
Under that new mode, the java object could indeed be gc'ed and cpp code would have to check whether the weak_ptr is expired before using it.
I would see that not as a way to abuse djinni goals but as an extension : being able to chose between weak/strong ref behavior, which is valid cpp/java. It would still be hiding the other language details etc.
In that mode the goal of the cache is still to ensure ptr uniqueness through various boundary crossing, the only difference is it can start holding null values when one side goes away, if that's the developper intent.
Hmmm, actually in what I describe above, the destruction of the java object would have to somehow to trigger the destruction of the cpp proxy we hold weak ref too (except maybe the Proxy Cache), which doesn't sound possible given we don't have any hooks in the java gc process, (let alone other languages I am not so familiar with).
Never mind then, thanks for your thoughts !
Yeah, I don't think you can easily get weak-ref semantics across languages in a portable way. If your C++ library kept a weak_ptr to the proxy object, it would work, but the weak_ptr would become null when the proxy was evicted from the cache, which might be earlier than the Java object is GCed. Importantly proxy cache isn't designed to ensure that one object only ever gets one proxy (I think that would require direct ownership of the proxy by the impl object). Instead it's designed to ensure that one object never has more than one proxy visible at the same time, while still creating proxies only on-demand.
I think if you want weak semantics, you're better off implementing them entirely in one language, then holding a strong reference across languages. For instance, you could have a Java "holder" object held strongly by C++, which holds a java.lang.WeakReference to whatever Java object you want to manage. Of course, that doesn't give you automatic cleanup of the "holder" object itself, so you'd need some way to GC those occasionally, or evict them in an LRU way similar to the proxy cache itself.