RFR: 8319779: SystemMenu: memory leak due to listener never being removed [v10]

John Hendrikx jhendrikx at openjdk.org
Wed Mar 6 17:48:54 UTC 2024


On Tue, 5 Mar 2024 08:55:41 GMT, Jose Pereda <jpereda at openjdk.org> wrote:

>> This could be a problem normally, but I think in this case you won't be able to get this to produce incorrect results.
>> 
>> `parseText` is accessing the property (and it calls `isMnemonicParsing`), but only if the text is not empty.  It's sort of "safe" as an empty text can't contain a mnemonic anyway, and you'd need to change the text at a minimum first to see the effect of turning mnemonic parsing on/off.  Changing the text would trigger the `textProperty` listener, which will call `parseText` and read the mnemonic property.
>> 
>> A change listener would be more obviously correct, but it is not strictly needed here.
>> 
>> (not all `subscribe`s use change listener, the one that takes a `Runnable` uses invalidation)
>
> @hjohn There is an important issue using `when()`, because the listener is added to a new property, not to the original property. 
> 
> Therefore, using an invalidation listener doesn't trigger any more notifications if you are changing values more than once to the original property.
> 
> This can happen in this SystemMenuBar case with the disabled property of any given `Menu` or `MenuItem`, if you simply change it twice or more times, while the application is active. You will notice that it will remain always with the same first state.
>  
> It is hard to add a system test in this case because the public Menu/MenuItem and the private peers MenuBase/MenuBaseItem are in sync, and only the communication to the native part is missing. But it can be easily viewed if you run it manually.
> 
> Let me clarify it with a very simple test, that reproduces the case of the disabledProperty:
> 
> mb.disableProperty().when(active).addListener(valueModel -> glassMenu.setEnabled(!mb.isDisable()));
> 
> 
> Simply run this manually:
> 
>         BooleanProperty b = new SimpleBooleanProperty();
>         BooleanProperty active = new SimpleBooleanProperty(true);
> 
>         b.addListener(o -> System.out.println("b: " + b.get()));
>         b.when(active).addListener(o -> System.out.println("when: " + b.get()));
>         b.set(true);
>         b.set(false);
> 
> 
> while you probably would be expecting:
> 
> 
> b: true
> when: true
> b: false
> when: false
> 
> 
> the test prints:
> 
> 
> b: true
> when: true
> b: false
> 
> 
> so using `when()` we miss the changes after the first invalidation.
> 
> I could go into details of why this happens, but just notice that we have a _new property_ after all, and if you are not calling `when.getValue()`, it doesn't get validated anymore after the first change.
> 
> I see three possible fixes to this issue.
> 
> 1. If you change the sample to:
> 
>         BooleanProperty b = new SimpleBooleanProperty();
>         BooleanProperty active = new SimpleBooleanProperty(true);
> 
>         b.addListener(o -> System.out.println("b: " + b.get()));
>         ObservableValue<Boolean> c = b.when(active);
>         c.addListener(o -> System.out.println("when: " + c.getValue()));
>         b.set(true);
>         b.set(false);
> 
> then you get the expected result:
> 
> 
> b: true
> when: true
> b: false
> when: false
> 
> 
> 2. Since this change implies some more refactoring, that is why I suggested using the change listener instead:
> 
> 
>         BooleanProperty b = new SimpleBooleanProperty();
>         BooleanProperty active = new SimpleBooleanProperty(true);
> 
>         b.addListener(...

Thanks, you are right.  The property generated by `when` is not getting revalidated, because its specific `getValue` is never called when using an invalidation listener on it. This seems to be a bit of pitfall that applies to all such mapped properties (not exclusive to `when`).

I'm fine with any of the options.  Option 3 certainly is the most convenient.

I'm more wondering if there should be some action taken in for the `map`, `flatMap`, `when`, `orElse` constructs to make them work better with invalidation listeners, or if that's not possible, a note explaining that adding an invalidation listener to such a construct probably is not doing what you think it is doing.

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

PR Review Comment: https://git.openjdk.org/jfx/pull/1283#discussion_r1514914613


More information about the openjfx-dev mailing list