djinni icon indicating copy to clipboard operation
djinni copied to clipboard

Proxy Cache and Proxy Object lifecycle

Open Guillaume227 opened this issue 9 years ago • 5 comments

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 :

weather service example 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 ?

djinni proxy cache 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.

Guillaume227 avatar Jan 21 '16 10:01 Guillaume227

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.

mknejp avatar Jan 21 '16 12:01 mknejp

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.

mknejp avatar Jan 21 '16 12:01 mknejp

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.

Guillaume227 avatar Jan 21 '16 14:01 Guillaume227

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 !

Guillaume227 avatar Jan 21 '16 14:01 Guillaume227

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.

artwyman avatar Jan 21 '16 22:01 artwyman