RFR: 8349091: Charts: exception initializing in a background thread [v6]

John Hendrikx jhendrikx at openjdk.org
Mon Mar 3 20:51:00 UTC 2025


On Mon, 3 Mar 2025 20:44:08 GMT, Andy Goryachev <angorya at openjdk.org> wrote:

>> I haven't been tracking these fixes for allowing initialization in background threads, but it seems to me that basically anything should be allowed as long as you're not part of a visible scene graph -- and I think there's also no expectation that all functionality of a control "works" as long as it is not yet part of such a graph (ie. the listeners are only needed once it is part of a scene graph).
>> 
>> If you make listeners conditional on being part of a scene graph, then I think you can handle these with a single code path.  Such a condition would be:
>> 
>>     ObservableValue<Boolean> partOfSceneGraph = node.sceneProperty()
>>       .flatMap(Scene::windowProperty)
>>       .flatMap(Window::showingProperty)
>>       .orElse(false);
>>       
>>       // The node here is "this", the chart, because as soon as it is part of the scene
>>       // graph, you want the listeners to function.
>>       
>> You can then safely install any listeners directly, regardless if they're on a background thread or not:
>> 
>>     someProperty().when(partOfSceneGraph).subscribe( ... );
>> 
>> Or:
>> 
>>     someProperty().when(partOfSceneGraph).addListener( ... );
>> 
>> Or with any mappings added, you can even pick where you put the `when`:
>> 
>>     someProperty().flatMap(xyz).when(partOfSceneGraph).addListener( ... );
>>     someProperty().when(partOfSceneGraph).flatMap(xyz).addListener( ... );
>> 
>> 
>> On a background thread, `partOfSceneGraph` will be `false`, and no listener gets installed (yet).  As soon as it becomes `true` all listeners with that same condition become active.  It becomes `true` automatically when the node has a scene belonging to a window that is visible.  Vice versa, if it ever becomes `false` again, (which it may when the Node is removed or replaced) all listeners using this condition are removed again.
>
> Thank you!
> 
> This is not what I asked though: my question was about using `Subscription` in one-off case, and more specifically, about whether it is even possible to unsubscribe from within the lambda.
> 
> What you are proposing is a slightly more involved change: for example, registering and unregistering listeners each time might introduce other issues (especially since we have no way to prioritize listeners/handlers).  It also complicates the management of skin (as skins can be added/removed), something I would rather avoid.

As for this current code:

                ObservableValue<Window> winProp = sceneProperty().flatMap(Scene::windowProperty);
                accessibilityActive = winProp; // keep the reference so it won't get gc
                Subscription sub = winProp.subscribe((win) -> {
                    if (win != null) {
                        if (accessibilityActive == winProp) {
                            accessibilityActive = null;
                        }
                        if (isAccessibilityActive()) {
                            handleAccessibilityActive(true);
                        }
                        //winProp.removeListener(this);
                        sub.unsubscribe(); <-- COMPILE ERROR
                    }
                });
                
What you want there is not possible in this way as you can't use `this` in a lambda, nor refer to it via a local.  You can achieve it only if you store the subscription in a field, not in a local.  Assigning the Lambda to a ChangeListener local, and using a ChangeListener may be simpler (unless you go for the `when` solution that may eliminate the need for 2 different code paths).

Note that there is no need to store `winProp`; `flatMap` does not use weak listeners (when in active use), and so as long as `sceneProperty()` exists, the derived property via `flatMap` will also exist, and also its listener.  The way the fluent mapping system works is that listeners are only present when something is actually listening:

    ObservableValue<Window> winProp = sceneProperty().flatMap(Scene::windowProperty);
    // ^^ nobody is listening, so no listeners installed and no hard references; it could GC but
    // it is currently in a local, so it won't
    
    ObservableValue<Window> winProp = sceneProperty().flatMap(Scene::windowProperty);
    winProp.subscribe(v -> { .. });
    // ^^ somebody is listening; all listeners get installed automatically, and now there are
    // hard references.  No risk of GC.
    
You can then write it as a single statement which won't be GC'd as long as the subscription is active:
    
    subscriptionFIeld = sceneProperty()
        .flatMap(Scene::windowProperty);
        .subscribe(v -> { 
            // use subscription field here if you want to unregister the lambda
            // Note: when you do, there will again be no listeners and thus no hard references and
            // thus the intermediate property created by flatMap can now be GC'd :)
        });

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

PR Review Comment: https://git.openjdk.org/jfx/pull/1697#discussion_r1978173310


More information about the openjfx-dev mailing list