Re: RFR: 8277848 Binding and Unbinding to List leads to memory leak [v3]

2022-01-05 Thread Michael Strauß
On Wed, 5 Jan 2022 18:14:44 GMT, Florian Kirmaier  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 incrementally with one 
> additional commit since the last revision:
> 
>   JDK-8277848
>   Further optimization based code review.
>   This Bugfix should now event improve the performance

modules/javafx.base/src/main/java/javafx/beans/property/ListPropertyBase.java 
line 338:

> 336: public void onChanged(Change change) {
> 337: ListPropertyBase ref = get();
> 338: if(ref != null) {

Minor: space after `if`

modules/javafx.base/src/main/java/javafx/beans/property/MapPropertyBase.java 
line 339:

> 337: public void onChanged(Change change) {
> 338: MapPropertyBase ref = get();
> 339: if(ref != null) {

Minor: space after `if`

modules/javafx.base/src/main/java/javafx/beans/property/SetPropertyBase.java 
line 341:

> 339: public void onChanged(Change change) {
> 340: SetPropertyBase ref = get();
> 341: if(ref != null) {

Minor: space after `if`

-

PR: https://git.openjdk.java.net/jfx/pull/689


Re: RFR: 8267059: Gradle :clean and :apps tasks fail on Windows if ANT_HOME contains spaces [v2]

2022-01-05 Thread Kevin Rushforth
On Fri, 4 Jun 2021 05:04:17 GMT, Michael Strauß  wrote:

>> This PR fixes an issue when building OpenJFX on Windows and command-line 
>> arguments contain paths with spaces.
>> 
>> The problem is that on Windows, `ant` is invoked via `cmd`, which leads to 
>> quotes being interpreted twice. This can be fixed with the option `cmd /s`, 
>> which prevents interpreting quotes on the rest of the command line. The 
>> result is as if the rest of the command line had been typed verbatim at the 
>> command prompt.
>
> Michael Strauß 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 two additional 
> commits since the last revision:
> 
>  - Merge branch 'master' into fixes/JDK-8267059
>  - Use cmd /s option when building on Windows

@johanvos or @tiainen can you comment as to whether you have any concerns with 
this? It seems a safe fix to me.

-

PR: https://git.openjdk.java.net/jfx/pull/499


Re: RFR: 8277848 Binding and Unbinding to List leads to memory leak [v2]

2022-01-05 Thread Florian Kirmaier
On Fri, 31 Dec 2021 12:53:54 GMT, Michael Strauß  wrote:

>> Florian Kirmaier has updated the pull request incrementally with one 
>> additional commit since the last revision:
>> 
>>   JDK-8277848
>>   Added missing change
>
> Why are the new listener imlementations called `BaseChangeListener` and 
> `BaseInvalidationListener`, i.e. why the _Base_?
> 
> Also, if you're going to the trouble of refactoring the existing listener 
> implementation, have you considered merging the very similar implementations 
> into a single class? You can then re-use the listener instance and save 
> another object allocation in this way:
> 
> 
> private static class Listener extends WeakReference>
> implements InvalidationListener, ListChangeListener, WeakListener {
> Listener(ListPropertyBase ref) {
> super(ref);
> }
> 
> @Override
> public boolean wasGarbageCollected() {
> return get() == null;
> }
> 
> @Override
> public void onChanged(Change change) {
> ListPropertyBase ref = get();
> if(ref != null) {
> ref.invalidateProperties();
> ref.invalidated();
> ref.fireValueChangedEvent(change);
> }
> }
> 
> @Override
> public void invalidated(Observable observable) {
> ListPropertyBase ref = get();
> if (ref == null) {
> observable.removeListener(this);
> } else {
> ref.markInvalid(ref.value);
> }
> }
> }

@mstr2 
Great point. The chosen name was just because I needed a name. I switched now 
to the name "Listener".

I've now merged the ChangeListener with the Invalidation Listener, as you've 
suggested.
The PR should now improve the performance while fixing a bug. It might even be 
quite relevant because these classes are used very often.

-

PR: https://git.openjdk.java.net/jfx/pull/689


Re: RFR: 8277848 Binding and Unbinding to List leads to memory leak [v3]

2022-01-05 Thread Florian Kirmaier
> 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 incrementally with one additional 
commit since the last revision:

  JDK-8277848
  Further optimization based code review.
  This Bugfix should now event improve the performance

-

Changes:
  - all: https://git.openjdk.java.net/jfx/pull/689/files
  - new: https://git.openjdk.java.net/jfx/pull/689/files/f9b7009b..ec90b3d1

Webrevs:
 - full: https://webrevs.openjdk.java.net/?repo=jfx=689=02
 - incr: https://webrevs.openjdk.java.net/?repo=jfx=689=01-02

  Stats: 106 lines in 3 files changed: 15 ins; 63 del; 28 mod
  Patch: https://git.openjdk.java.net/jfx/pull/689.diff
  Fetch: git fetch https://git.openjdk.java.net/jfx pull/689/head:pull/689

PR: https://git.openjdk.java.net/jfx/pull/689


Re: RFR: 8267059: Gradle :clean and :apps tasks fail on Windows if ANT_HOME contains spaces [v2]

2022-01-05 Thread Michael Strauß
On Fri, 27 Aug 2021 13:39:13 GMT, Kevin Rushforth  wrote:

>> Michael Strauß 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 two additional 
>> commits since the last revision:
>> 
>>  - Merge branch 'master' into fixes/JDK-8267059
>>  - Use cmd /s option when building on Windows
>
> @tiainen or @arapte can one of you be the second reviewer on this?

@kevinrushforth
I think this is a very useful fix that has been tested by three people in the 
last months (of which only your review counts for Skara). Can you lower the 
number of required reviews to 1 in order for this PR to move forward to 
integration?

-

PR: https://git.openjdk.java.net/jfx/pull/499


Re: RFR: 8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue [v3]

2022-01-05 Thread John Hendrikx
On Tue, 4 Jan 2022 03:28:57 GMT, Nir Lisker  wrote:

> Unrelated to the review, will it makes sense in the future to make all 
> bindings lazily register listeners like `LazyObjectBinding`, perhaps when we 
> introduce `Subscription`?

That would need to be very well tested. There are some noticeable differences 
in behavior vs the standard bindings:

1) Standard bindings can more easily be GC'd (accidentally usually, but it 
could be intentional), take for example:


 textProperty.addListener((obs, old, current) -> 
System.out.println(current));
 textProperty.concat("A").addListener((obs, old, current) -> 
System.out.println(current));


These behave very different.  The first listener keeps working as you'd expect, 
while the second one can stop working as soon the GC runs. This is because 
`concat` results in an `ObservableValue` that is weakly bound.  Compare this to:

 textProperty.map(x -> x + "A").addListener((obs, old, current) -> 
System.out.println(current));

This keeps working and will not be GC'd by accident.

2) Standard bindings will always cache values. This means that when `getValue` 
is called, it will just return the cached value instead of calling 
`computeValue`.  If `computeValue` is really expensive (unwise since this 
happens on the FX thread) then this cost is paid each time for Lazy bindings, 
at least when they're not bound to anything else (unobserved) and you are just 
using `getValue`. Furthermore, for a chain of Lazy bindings that is unobserved, 
this would propagate through the entire chain.  As soon as they're observed 
though, they become non-lazy and values are cached.

In effect, Lazy bindings behave the same as standard bindings when they're 
observed but their behavior changes when not observed: they never become valid 
and they stop caching values

This has pros and cons:

Pro: Lazy bindings can be garbage collected when not referenced and not 
actively being used without the use of weak listeners.  See the first example 
where the binding keeps working when used by `println` lambda.  This is in 
contrast to traditional bindings which can be garbage collected when 
unreferenced by user code even if actively used(!!).  This is a huge gotcha and 
one of the biggest reasons to use the lazy model.

Pro: Lazy bindings don't register unnecessary listeners to be aware of changed 
or invalidated values that are not used by anyone. A good example is the 
problems we saw about a year ago where `Node` had created an `isShowing` 
property which bounds to its `Scene` and `Window`.  This looks harmless, until 
you realize that a listener is created on these properties for each `Node` in 
existence.  If a `Scene` has tens of thousands of `Node`s then that means that 
the `Scene#windowProperty` will have a listener list containing tens of 
thousands of entries.  This list is not only expensive to change (when a node 
is added or removed) but also expensive to act on (when the scene, window or 
its showing state changes).  And for what?  Almost nobody was actually using 
this property, yet listeners had to be added for each and every `Node`.  In 
effect with lazy bindings, it is much cheaper now to create properties that 
create convenient calculated values which will only register listeners or 
compute
  their values when in actual use. 

Con: lazy bindings never become valid when not observed.  This means that 
`getValue` will always have to recompute the value as the value is not cached.  
However, if you register an invalidation listener the binding becomes observed 
and it will start behaving like a traditional binding sending invalidation 
events and caching the current value.  A small "pro" here could be that this 
means that intermediate values in a long binding chain don't take up memory 
(and are prevented from GC), however that goes away as soon as the binding is 
observed.

In summary: I think lazy bindings are far superior in the experience that they 
offer, but it does come at a cost that values may need to be recomputed every 
time when the bindings are unobserved. However, doing substantial work in 
`computeValue` is probably unwise anyway as it blocks the FX thread so making 
lazy binding the default behavior in well behaving code could be of only minor 
impact.

-

PR: https://git.openjdk.java.net/jfx/pull/675


Re: RFR: 8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue [v3]

2022-01-05 Thread Michael Strauß
On Wed, 5 Jan 2022 12:08:01 GMT, John Hendrikx  wrote:

>> modules/javafx.base/src/main/java/javafx/beans/value/ObservableValue.java 
>> line 188:
>> 
>>> 186:  * {@code ObservableValue} given by applying {@code mapper} on 
>>> the value
>>> 187:  * held by this {@code ObservableValue}, and is {@code null} 
>>> when this
>>> 188:  * {@code ObservableValue} holds {@code null}, never null
>> 
>> an {@code ObservableValue} that holds the value of the {@code 
>> ObservableValue} resulting
>> from applying a mapping on the value held by this {@code ObservableValue}; 
>> never {@code null} itself
>
> How about:
> 
> an {@code ObservableValue} holding the value of an {@code ObservableValue}
> resulting from a mapping of this {@code ObservableValue}'s value or
> holds {@code null} when the value is {@code null}; never returns {@code 
> null}
> 
> They are tough to describe; if you don't like the `or holds {@code null}` 
> parts I can remove those from all the functions.

Here's another attempt:

a new {@link ObservableValue} instance that holds the value that was obtained by
applying the mapping function on the value held by this {@code ObservableValue}.
If this {@code ObservableValue} holds {@code null}, the new {@code 
ObservableValue}
will also hold {@code null}.

-

PR: https://git.openjdk.java.net/jfx/pull/675


Re: RFR: 8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue [v3]

2022-01-05 Thread Nir Lisker
On Wed, 5 Jan 2022 09:52:29 GMT, John Hendrikx  wrote:

>> modules/javafx.base/src/main/java/javafx/beans/binding/ObjectBinding.java 
>> line 204:
>> 
>>> 202:  *
>>> 203:  * @return {@code true} if this binding is allowed to become 
>>> valid, otherwise
>>> 204:  * {@code false}
>> 
>> Typo: overriden -> overridden
>> 
>> I would add a a first-sentence summary and an explanation as to why a 
>> binding would not allow it. I would write something like
>> 
>> Checks if the binding is allowed to become valid. Overriding classes can 
>> prevent a binding from becoming
>> valid. This is useful when .
>> 
>> The default implementation always allows bindings to become valid.
>
> I've made your suggested changes and added this explanation: "This is useful 
> in subclasses which do not always listen for invalidations of their 
> dependencies and prefer to recompute the current value instead. This can also 
> be useful if caching of the current computed value is not desirable."
> 
> Furthermore, I noticed I forgot to make the code changes that prevent caching 
> of the value when the binding is invalid -- bindings currently cache their 
> value even when invalid, which could lead to situations where something is 
> still being referenced in an invalid binding that should have been GC'd.

Something like that was previously discussed in 
https://github.com/javafxports/openjdk-jfx/pull/110.

-

PR: https://git.openjdk.java.net/jfx/pull/675


Re: RFR: 8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue [v3]

2022-01-05 Thread John Hendrikx
On Wed, 5 Jan 2022 09:45:21 GMT, John Hendrikx  wrote:

>> modules/javafx.base/src/main/java/javafx/beans/binding/ObjectBinding.java 
>> line 193:
>> 
>>> 191:  *
>>> 192:  * @return {@code true} when this binding currently has one or more
>>> 193:  * listeners, otherwise {@code false}
>> 
>> Maybe the method description
>> `Checks if the binding has at least one listener registered on it.`
>> is more straightforward, especially since it is a first-sentence summary. 
>> The `@return` description can contain the info on what is returned. As for 
>> that, maybe
>> `{@code true} if this binding currently has one or more listeners registered 
>> on it, otherwise {@code false}`
>> is more precise.
>> I'm not sure if "registered on" or "registered to" is better, not that I 
>> think it matters.
>> 
>> I would also like to see an explanation of how the method should/can be used 
>> by subclasses, but it looks to be tied to `Subscription`, which isn't added 
>> yet, so I don't have a good idea on this.
>
> It is not strictly tied to `Subscription`, the method is required to 
> determine when `LazyObjectBinding` must register listeners on its 
> dependencies and when it can remove them again (basically when it stops being 
> lazy or when it can become lazy again).

I've added "This is useful for subclasses which want to conserve resources when 
not observed."

-

PR: https://git.openjdk.java.net/jfx/pull/675


Re: RFR: 8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue [v3]

2022-01-05 Thread John Hendrikx
On Sun, 2 Jan 2022 20:18:02 GMT, Nir Lisker  wrote:

>> John Hendrikx has updated the pull request incrementally with one additional 
>> commit since the last revision:
>> 
>>   Upgrade tests to JUnit 5
>
> modules/javafx.base/src/main/java/javafx/beans/binding/ObjectBinding.java 
> line 193:
> 
>> 191:  *
>> 192:  * @return {@code true} when this binding currently has one or more
>> 193:  * listeners, otherwise {@code false}
> 
> Maybe the method description
> `Checks if the binding has at least one listener registered on it.`
> is more straightforward, especially since it is a first-sentence summary. The 
> `@return` description can contain the info on what is returned. As for that, 
> maybe
> `{@code true} if this binding currently has one or more listeners registered 
> on it, otherwise {@code false}`
> is more precise.
> I'm not sure if "registered on" or "registered to" is better, not that I 
> think it matters.
> 
> I would also like to see an explanation of how the method should/can be used 
> by subclasses, but it looks to be tied to `Subscription`, which isn't added 
> yet, so I don't have a good idea on this.

It is not strictly tied to `Subscription`, the method is required to determine 
when `LazyObjectBinding` must register listeners on its dependencies and when 
it can remove them again (basically when it stops being lazy or when it can 
become lazy again).

> modules/javafx.base/src/main/java/javafx/beans/binding/ObjectBinding.java 
> line 204:
> 
>> 202:  *
>> 203:  * @return {@code true} if this binding is allowed to become valid, 
>> otherwise
>> 204:  * {@code false}
> 
> Typo: overriden -> overridden
> 
> I would add a a first-sentence summary and an explanation as to why a binding 
> would not allow it. I would write something like
> 
> Checks if the binding is allowed to become valid. Overriding classes can 
> prevent a binding from becoming
> valid. This is useful when .
> 
> The default implementation always allows bindings to become valid.

I've made your suggested changes and added this explanation: "This is useful in 
subclasses which do not always listen for invalidations of their dependencies 
and prefer to recompute the current value instead. This can also be useful if 
caching of the current computed value is not desirable."

Furthermore, I noticed I forgot to make the code changes that prevent caching 
of the value when the binding is invalid -- bindings currently cache their 
value even when invalid, which could lead to situations where something is 
still being referenced in an invalid binding that should have been GC'd.

> modules/javafx.base/src/main/java/javafx/beans/value/MappedBinding.java line 
> 34:
> 
>> 32: 
>> 33: class MappedBinding extends LazyObjectBinding {
>> 34: private final ObservableValue source;
> 
> We usually have an empty line below the class declaration. Not sure if this 
> is enforced. Same for the other classes.

I've added them everywhere.

> modules/javafx.base/src/main/java/javafx/beans/value/ObservableValue.java 
> line 146:
> 
>> 144:  * Returns an {@link ObservableValue} which provides a mapping of 
>> the value
>> 145:  * held by this {@code ObservableValue}, and provides {@code null} 
>> when this
>> 146:  * {@code ObservableValue} holds {@code null}.
> 
> No need to `@link` since we are in the same class already.
> 
> While this description is very similar to that of ReactFX, I would prefer a 
> shorter summary of what the method does as its first sentence, and this will 
> allow to unpack the details more cleanly. Maybe something like:
> 
> Creates an {@code ObservableValue} that holds the result of applying a 
> mapping on the value held
> by this {@code ObservableValue}. The result is updated (lazily) when the 
> value held by this
> {@code ObservableValue} changes. If this value is {@code null}, the resulting 
> value is also {@code null}
> (the mapping is not applied).
> 
> For example, mapping a lower case string to an uppercase string, and to a 
> check if the string is blank:
> 
> var lowercase = new SimpleStringProperty("abcd");
> ObservableValue uppercase = lowercase.map(String::toUpperCase); 
> // "ABCD"
> ObservableValue blank = lowercase.map(String::isBlank);
> // false
> lowercase.set(" ");
> uppercase.getValue(); // " "
> blank.getValue(); // true
> 

Copied these suggestions with some slight modifications, and I simplified the 
example a little bit.

> modules/javafx.base/src/main/java/javafx/beans/value/ObservableValue.java 
> line 149:
> 
>> 147:  *
>> 148:  * @param  the type of values held by the resulting {@code 
>> ObservableValue}
>> 149:  * @param mapper a {@link Function} which converts a given value to 
>> a new value, cannot be null
> 
> No need to `@link` since its linked in the method argument list when the docs 
> are generated.
> `null` should be in `@code`.
> 
> Worth thinking about adding the types of the 

Re: RFR: 8274771: Map, FlatMap and OrElse fluent bindings for ObservableValue [v4]

2022-01-05 Thread John Hendrikx
> This is an implementation of the proposal in 
> https://bugs.openjdk.java.net/browse/JDK-8274771 that me and Nir Lisker 
> (@nlisker) have been working on.  It's a complete implementation including 
> good test coverage.  
> 
> This was based on https://github.com/openjdk/jfx/pull/434 but with a smaller 
> API footprint.  Compared to the PoC this is lacking public API for 
> subscriptions, and does not include `orElseGet` or the `conditionOn` 
> conditional mapping.
> 
> **Flexible Mappings**
> Map the contents of a property any way you like with `map`, or map nested 
> properties with `flatMap`.
> 
> **Lazy**
> The bindings created are lazy, which means they are always _invalid_ when not 
> themselves observed. This allows for easier garbage collection (once the last 
> observer is removed, a chain of bindings will stop observing their parents) 
> and less listener management when dealing with nested properties.  
> Furthermore, this allows inclusion of such bindings in classes such as `Node` 
> without listeners being created when the binding itself is not used (this 
> would allow for the inclusion of a `treeShowingProperty` in `Node` without 
> creating excessive listeners, see this fix I did in an earlier PR: 
> https://github.com/openjdk/jfx/pull/185)
> 
> **Null Safe**
> The `map` and `flatMap` methods are skipped, similar to `java.util.Optional` 
> when the value they would be mapping is `null`. This makes mapping nested 
> properties with `flatMap` trivial as the `null` case does not need to be 
> taken into account in a chain like this: 
> `node.sceneProperty().flatMap(Scene::windowProperty).flatMap(Window::showingProperty)`.
>   Instead a default can be provided with `orElse`.
> 
> Some examples:
> 
> void mapProperty() {
>   // Standard JavaFX:
>   label.textProperty().bind(Bindings.createStringBinding(() -> 
> text.getValueSafe().toUpperCase(), text));
> 
>   // Fluent: much more compact, no need to handle null
>   label.textProperty().bind(text.map(String::toUpperCase));
> }
> 
> void calculateCharactersLeft() {
>   // Standard JavaFX:
>   
> label.textProperty().bind(text.length().negate().add(100).asString().concat(" 
> characters left"));
> 
>   // Fluent: slightly more compact and more clear (no negate needed)
>   label.textProperty().bind(text.orElse("").map(v -> 100 - v.length() + " 
> characters left"));
> }
> 
> void mapNestedValue() {
>   // Standard JavaFX:
>   label.textProperty().bind(Bindings.createStringBinding(
> () -> employee.get() == null ? ""
> : employee.get().getCompany() == null ? ""
> : employee.get().getCompany().getName(),
> employee
>   ));
> 
>   // Fluent: no need to handle nulls everywhere
>   label.textProperty().bind(
> employee.map(Employee::getCompany)
> .map(Company::getName)
> .orElse("")
>   );
> }
> 
> void mapNestedProperty() {
>   // Standard JavaFX:
>   label.textProperty().bind(
> Bindings.when(Bindings.selectBoolean(label.sceneProperty(), "window", 
> "showing"))
>   .then("Visible")
>   .otherwise("Not Visible")
>   );
> 
>   // Fluent: type safe
>   label.textProperty().bind(label.sceneProperty()
> .flatMap(Scene::windowProperty)
> .flatMap(Window::showingProperty)
> .orElse(false)
> .map(showing -> showing ? "Visible" : "Not Visible")
>   );
> }
> 
> Note that this is based on ideas in ReactFX and my own experiments in 
> https://github.com/hjohn/hs.jfx.eventstream.  I've come to the conclusion 
> that this is much better directly integrated into JavaFX, and I'm hoping this 
> proof of concept will be able to move such an effort forward.

John Hendrikx has updated the pull request incrementally with one additional 
commit since the last revision:

  Apply changes suggested in review and updated copyright years to 2022

-

Changes:
  - all: https://git.openjdk.java.net/jfx/pull/675/files
  - new: https://git.openjdk.java.net/jfx/pull/675/files/312fb506..30e8ceac

Webrevs:
 - full: https://webrevs.openjdk.java.net/?repo=jfx=675=03
 - incr: https://webrevs.openjdk.java.net/?repo=jfx=675=02-03

  Stats: 182 lines in 11 files changed: 127 ins; 13 del; 42 mod
  Patch: https://git.openjdk.java.net/jfx/pull/675.diff
  Fetch: git fetch https://git.openjdk.java.net/jfx pull/675/head:pull/675

PR: https://git.openjdk.java.net/jfx/pull/675