Page MenuHomePhabricator

C#: Attach wrapper lifetime to the Eo instance
Open, HighPublic

Description

Currently, when you do something like var item = new Button(parent), the lifetime of the C# wrapper is not attached to the Eo instance. This means that the C# wrapper may die when going out of scope even though its eo is alive due to the parent.

This may cause examples like P283 to fail when the individual list items wrappers are collected, not transmitting the C events into C# land.

lauromoura triaged this task as High priority.
vitor.sousa added a comment.EditedFri, Apr 12, 3:32 PM

This will require some changes in the way that our wrappers handle their lifetime.
In case a Dispose happens on an object and it still has references in C, it will need to resurrect itself if it holds any registered delegates or if its C# state is important.

@felipealmeida and I discussed and concluded that we will need the following changes:

  • In a way similar to what is already done for the C# inherited types, the regular types (the ones that are simply generated) will also need to store a handler for the C# wrapper in the C object. We can use efl_key_data_set for that.
  • Whenever an Eo object needs to be wrapped, the presence of that handle is checked, and if it is there, then a reference to the existing wrapper is returned. This way we ensure a kind of one to one relationship between an Eo object and its C# wrapper.
  • Destruction of C# wrappers should check the refcount of the object and resurrect the wrapper if it has more refs than the ones C# holds (which is granted to be just one with the previous feature). This process can be skipped if the wrapper state has no relevance to the object anymore (i.e. if it is not a inherited object and if it does not have any registered event). The object is then kept alive by pinning its GCHandle, now it will be in an "owned by C" state.
  • If for some reason the C# wrapper regains a reference, it will need to be unpinned and become available for garbage collection again. So, this condition must be verified in every place an Eo object can be wrapped.
  • Objects that are in an "owned by C" state should also be unpinned and become available for garbage collection if its Eo* counterpart is destructed. Receiving a del event could be a good indicator for when to perform this process.

We think this should be enough to ensure the correct behavior regarding the lifetime of C# wrappers.

It is worth of note that those changes affect many aspects of the current implementation, that will require adjustments in all of them, including:

  • Events in general
  • Marshaling of objects from C to C#
  • Storage and access of the C# managed pointer in the private handle for inherited types
  • Registration of Inherited types when passed as System.Type to an Efl.Class parameter
  • Handling of private dynamic C classes

Another task that we should take into account together with this is T7785, when C-land uses efl_add/add_ref/new to create instances of C#-derived classes (like in widget factories).

So we looked into the problems of finalizers and dispose and we found out that using that is not possible. It is possible the GC collects members of a class that we decide later to ressucitate. That surely wouldn't work, because the members would not have the same state when the class is revived.

So, the only solution, and likely even easier to understand and implement, is to hook Eo for single-ownership and shared-ownership. Just like we have del intercept, we can hook when reference count goes above 1 and when it goes back to 1. That way we can make C code own C# wrapper when the object is in shared-ownership state, and not own C# wrapper when it is in single-ownership state.

This guarantees that the wrapper will be kept alive whenever the Eo pointer is still alive by C but that it will be released (together with Eo object) when wrapper is finally, completely, unused (by C# and C).

We are already working on this solution.

@felipealmeida @lauromoura
If you have working branch to handle this task, would you share the branch name here ?