On Wed, 10 Jan 2024 20:05:16 GMT, Andy Goryachev <ango...@openjdk.org> wrote:
>> No, the explicit goal of this construction is to set active to false (in >> case it exists) so that existing listeners can be released; followed by >> creating a new active property that is by default set to true until >> `setMenus` is called again (e.g. after unfocus/focus events). >> I noticed however that it is not as simple as that, and the current PR >> introduces regression, as the `SystemMenuBarTest.testMemoryLeak` test is now >> failing. Listeners are not only registered when `setMenus` is called, but >> also when menu(Item)s are added to menus. I'm not sure if a single `active` >> property can address this, as we don't want to remove all listeners for all >> menuitems in case a particular menuitem is added to a particular menu. >> @hjohn does that make sense? > > thank you for clarification! > > perhaps we ought to add a comment explaining why: it's one thing to create a > single chain of conditions linked to a property, but here we keep creating > new property instance, so the side effects are not that obvious. > I noticed however that it is not as simple as that, and the current PR > introduces regression, as the SystemMenuBarTest.testMemoryLeak test is now > failing. Listeners are not only registered when setMenus is called, but also > when menu(Item)s are added to menus. I'm not sure if a single active property > can address this, as we don't want to remove all listeners for all menuitems > in case a particular menuitem is added to a particular menu. @hjohn does that make sense? So, if I understood correctly, that test is dealing not with the entire menu bar disappearing (for which the `active` property ensures clean up), but with a submenu getting cleared, and then a new item added. Any old items that were added should be garbage collectable after clearing. This is a bit tricky. Basically I think it means that the listeners on a certain `MenuBase` wrapper should be removed in two cases: 1. When the entire menu is no longer needed (what we have now) 2. When the `MenuBase` in question is removed from its parent I think this may be resolved in one of two ways: 1. Create a specific active condition for `when` that includes a check on parent and on the top level `active` property 2. Gather all listeners added into a single `Subscription` (currently the `Subscription` that is returned is discarded, track this somewhere (probably easiest in a the user property map) taking care not to create a new hard reference (not sure if that will be an issue). The `Subscription` can then be obtained and unsubscribed early. Now I think option 1 can be achieved by doing something like the code below. **Note:** completely untested, and did not check yet if we aren't creating any new problematic hard references; I can test this out over the weekend if you like. // given the global `active` property... // and a MenuBase mb, for which we can discover if it is a root menu or not... // and which exposes a new property hasParentProperty (you can derive this property // if you like by doing `parentMenuProperty.map(Objects::nonNull)`) BooleanProperty forWhen = active; if (mb "is not a root menu") { // important we don't do this for root // include "and" condition that it also must have a parent to be considered "active" forWen = active.flatMap(b -> b ? mb.hasParentProperty() : active); } // then use `forWhen` in your `when` conditions instead of `active` as before. What the above does is that using `flatMap` it creates a listener on `hasParentProperty`. If it changes, the entire expression result may change. In normal Java code it would simply be: boolean forWhen = active && mb.hasParent(); ... except of course that the above code would not react to changes. The logical and (`&&`) is achieved with the ternary expression. If `b` is `true` then we need to check the second part of the `&&` which is `hasParentProperty`. If `b` was already `false`, then the there is no need to check further. In any case, a new **property** must be returned from `flatMap`, so that will either be `hasParentProperty` in case `active` is `true` or the `active` property (which then must be `false`). Such scenario's are rare, but I've been considering adding specific support on `ObservableValue` for this. There is also the `and` function on `ObservableBooleanValue` -- you can test this one as well, but I fear that it may be using a weak listener in there and that it may get collected early if you don't reference the intermediate expressions. There is no such risk with `flatMap`. I haven't looked further into option 2. ------------- PR Review Comment: https://git.openjdk.org/jfx/pull/1283#discussion_r1448099725