Skip to content

Conversation

@hjohn
Copy link
Collaborator

@hjohn hjohn commented Aug 23, 2025

Ensures proper propagation of layout flags when using forceParentLayout = true.

This was the root cause of issue #1874

Note

Apparently it is still quite easy to mess up the layout flags. Basically, the layout flag tracked by Parent should always either be CLEAN for any scene graph branch, or !CLEAN + a layout pulse is scheduled on the corresponding Scene.

However, with careful use of the public API requestLayout one can get these flags in a bad state still:

Let's say I have a branch A (root node under Scene) -> B -> C, and a layout is in progress, and we're currently in the layoutChildren method of C. The flag performingLayout will be true for all nodes in the branch A -> B -> C. The layout method will set the layout flag to CLEAN as its first action, so when we're at C::layoutChildren, all flags have been reset to CLEAN already. See the Parent::layout method for how all this works.

Now, to mess up the flags, all you need to do is call requestLayout on B or C from the layoutChildren of C (or indirectly by changing something and something is listening to this and schedules a layout on something somewhere in this branch); note that requestLayout is not documented to be illegal to call during layout, and some classes in FX will do so (ScrollPaneSkin, NumberAxis, etc..) risking the flags getting in a bad state... -- usually you get away with this, as there are many ways that layout is triggered, and eventually the flags will get overwritten and reset to a consistent state.

The bad state occurs because this code path is followed (all code from Parent):

public void requestLayout() {
    clearSizeCache();
    markDirtyLayout(false, forceParentLayout);
}

Calls to markDirtyLayout(false, false):

private void markDirtyLayout(boolean local, boolean forceParentLayout) {
    setLayoutFlag(LayoutFlags.NEEDS_LAYOUT);
    if (local || layoutRoot) {
        if (sceneRoot) {
            Toolkit.getToolkit().requestNextPulse();
            if (getSubScene() != null) {
                getSubScene().setDirtyLayout(this);
            }
        } else {
            markDirtyLayoutBranch();
        }
    } else {
        requestParentLayout(forceParentLayout);
    }
}

Before going into the else (as none of the nodes is a layout root, and local was set to false) it will do setLayoutFlag(LayoutFlags.NEEDS_LAYOUT) -- this will set a flag on some node; to eventually end up in a consistent state, it must mark all ancestors as well and schedule a pulse (with Toolkit.getToolkit().requestNextPulse())... but:

void requestParentLayout(boolean forceParentLayout) {
    if (!layoutRoot) {
        final Parent p = getParent();
        if (p != null && (!p.performingLayout || forceParentLayout)) {

            /*
             * The forceParentLayout flag must be propagated to mark all ancestors
             * as needing layout. Failure to do so while performingLayout is true 
             * would stop the propagation mid-tree. This leaves some nodes as needing 
             * layout, while its ancestors are clean, which is an inconsistent state.
             */

            p.requestLayout(forceParentLayout);
        }
    }
}

Here there is a guard !p.isPerformingLayout, blocking propagation up the tree. As said, this flag is true for all nodes during a layout of the same branch. The end result thus is that some nodes have their layout flag changed to NEEDS_LAYOUT, but it was not propagated, nor was a pulse scheduled...


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8360940: Layout stops updating when using Parent#setNeedsLayout(true) due to incorrect state management (Bug - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1879/head:pull/1879
$ git checkout pull/1879

Update a local copy of the PR:
$ git checkout pull/1879
$ git pull https://git.openjdk.org/jfx.git pull/1879/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1879

View PR using the GUI difftool:
$ git pr show -t 1879

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1879.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Aug 23, 2025

👋 Welcome back jhendrikx! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Aug 23, 2025

@hjohn This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8360940: Layout stops updating when using Parent#setNeedsLayout(true) due to incorrect state management

Reviewed-by: kcr, angorya

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 37 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot changed the title JDK-8360940 Layout stops updating when using (public api) Parent#setNeedsLayout(true) due to incorrect state management 8360940: Layout stops updating when using (public api) Parent#setNeedsLayout(true) due to incorrect state management Aug 23, 2025
@hjohn hjohn marked this pull request as ready for review August 23, 2025 17:59
@openjdk openjdk bot added the rfr Ready for review label Aug 23, 2025
@mlbridge
Copy link

mlbridge bot commented Aug 23, 2025

Webrevs

@andy-goryachev-oracle
Copy link
Contributor

/reviewers 2

@openjdk
Copy link

openjdk bot commented Aug 25, 2025

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@kevinrushforth kevinrushforth self-requested a review September 5, 2025 14:21
@kevinrushforth
Copy link
Member

The fix looks correct. Can you provide an automated test?

@kevinrushforth
Copy link
Member

I recommend removing (public api) from the title of the JBS bug and this PR -- we don't typically do that and it seems superfluous here.

@hjohn hjohn changed the title 8360940: Layout stops updating when using (public api) Parent#setNeedsLayout(true) due to incorrect state management 8360940: Layout stops updating when using Parent#setNeedsLayout(true) due to incorrect state management Sep 5, 2025
Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix looks good. I confirm that the manual test program from the JBS bug fails without the fix and passes with the fix.

Would it be possible to turn it into an automated test?

@kevinrushforth kevinrushforth self-requested a review September 30, 2025 17:10
@hjohn
Copy link
Collaborator Author

hjohn commented Oct 1, 2025

@kevinrushforth I think I managed a suitable unit test. It fails without the fix, and passes with the fix.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I confirm that the new test fails without the fix and passes with the fix.

* layout, while its ancestors are clean, which is an inconsistent state.
*/

p.requestLayout(forceParentLayout);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very, very minor suggestion to makes it more compact (it's ok to leave things as is):


                // The forceParentLayout flag must be propagated to mark all ancestors
                // as needing layout. Failure to do so while performingLayout is true
                // would stop the propagation mid-tree. This leaves some nodes as needing
                // layout, while its ancestors are clean, which is an inconsistent state.
                p.requestLayout(forceParentLayout);

@openjdk openjdk bot added the ready Ready to be integrated label Oct 1, 2025
@hjohn
Copy link
Collaborator Author

hjohn commented Oct 1, 2025

Thanks for the quick reviews @andy-goryachev-oracle @kevinrushforth

@andy-goryachev-oracle
Copy link
Contributor

sorry for the long delay...

@hjohn
Copy link
Collaborator Author

hjohn commented Oct 2, 2025

/integrate

@openjdk
Copy link

openjdk bot commented Oct 2, 2025

Going to push as commit 5682424.
Since your change was applied there have been 39 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Oct 2, 2025
@openjdk openjdk bot closed this Oct 2, 2025
@openjdk openjdk bot removed ready Ready to be integrated rfr Ready for review labels Oct 2, 2025
@openjdk
Copy link

openjdk bot commented Oct 2, 2025

@hjohn Pushed as commit 5682424.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

4 participants