On Fri, 19 Aug 2022 10:40:01 GMT, Florian Kirmaier <[email protected]> 
wrote:

>> Making the initial listener of the ListProperty weak fixes the problem.
>> The same is fixed for Set and Map.
>> Due to a smart implementation, this is done without any performance drawback.
>> (The trick is to have an object, which is both the WeakReference and the 
>> Changelistener)
>> By implying the same trick to the InvalidationListener, this should even 
>> improve the performance of the collection properties.
>
> Florian Kirmaier has updated the pull request with a new target base due to a 
> merge or a rebase. The incremental webrev excludes the unrelated changes 
> brought in by the merge/rebase. The pull request contains seven additional 
> commits since the last revision:
> 
>  - Merge remote-tracking branch 'origjfx/master' into 
> JDK-8277848-list-binding-leak
>  - JDK-8277848
>    Added tests to ensure no premature garbage collection is happening.
>  - JDK-8277848
>    Added 3 more tests to verify that a bug discussed in the PR does not 
> appear.
>  - JDK-8277848
>    Added the 3 requests whitespaces
>  - JDK-8277848
>    Further optimization based code review.
>    This Bugfix should now event improve the performance
>  - JDK-8277848
>    Added missing change
>  - JDK-8277848
>    Fixed memoryleak, when binding and unbinding a ListProperty. The same was 
> fixed for SetProperty and MapProperty.

My question is the following. Isn't this expected behavior?

Let's say I have this program:

     X x = new X();
     ObjectProperty<X> a = new SimpleObjectProperty<>(x);
     ObjectProperty<X> b = new SimpleObjectProperty<>();

     b.bind(a);   // b takes on the value of a (x)
     b.unbind(); // b keeps last value (x)

     a = null;
     x = null;

Should I expect `x` here to be unreferenced? No, that's how properties work, 
`b` will keep its reference to `x`.

Now replace the above program with `X` being an `ObservableList` and the 
`ObjectProperty`s being `ListProperty`s.  Should the expected behavior change?

I think there is a far more fundamental issue with how `ListProperty`s behave 
that's poorly defined and documented.  While investigating, I found the main 
culprit to the `bind`/`unbind` test (`testBindingLeak`) to be the copy that is 
made in the `unbind` method:

    @Override
    public void unbind() {
        if (observable != null) {
            value = observable.getValue();   // <<< cause of "memory leak"
            observable.removeListener(listener);
            observable = null;
        }
    }

That line makes total sense from a property perspective; when a property is 
unbound, it copies the value of its source as the last known value.  Should 
this perhaps make a deep copy?  Perhaps it should, but these properties are so 
poorly defined that it is anyone's guess at what it should be doing here.

IMHO there is a conflict between the property like behavior of `ListProperty` 
and the list like behavior of the `ObservableList` that it wraps.  On the one 
hand, a list property is a reference to a list, and on the other hand we expect 
it to behave as only referring to the contents of the list.  The property like 
behavior that it inherits (perhaps it shouldn't have done that) has been 
subverted in that adding a change listener will pretend that a change to the 
list is the same as replacing the entire list object, so it is not quite a 
property any more.  You can see this in the fact that the "old value" of a 
change listener is incorrect:

        ObservableList<Object> list = FXCollections.observableArrayList();
        SimpleListProperty<Object> simpleListProperty = new 
SimpleListProperty<>(list);

        simpleListProperty.addListener((obs, old, current) -> 
System.out.println("Changed: " + old + " -> " + current));

        list.add("B");
        simpleListProperty.set(FXCollections.observableArrayList("C"));

Prints:

     Changed: [B] -> [B]    // huh? so nothing changed then? but it did...
     Changed: [B] -> [C]

This is frustrating as change listener has very specific semantics that code 
would like to rely on, but here, again, the old value is just a best effort 
guess (there are other bugs in this area).

I think that because of these poorly defined semantics, one will keep finding 
surprises in the behavior of these properties, depending on your perspective 
(property or list).  I think therefore that the first thing that's need to be 
done is to clearly define how these observable wrapper properties are supposed 
to work before fixing what may or may not be bugs in this area.  I fear it may 
not be possible to have `ListProperty` behave property and list like at the 
same time, and if so this should be clearly marked in the documentation that 
`ListProperty` does not obey the general contract of `Property`.

-------------

PR: https://git.openjdk.org/jfx/pull/689

Reply via email to