Skip to content

Conversation

bendk
Copy link

@bendk bendk commented Sep 26, 2025

I used essentially the same pattern than validateFields uses.

@bendk
Copy link
Author

bendk commented Sep 26, 2025

I tried adding tests for this, but I couldn't figure out a good way to reliable trigger the issue.

Copy link
Member

@matthiasblaesing matthiasblaesing left a comment

Choose a reason for hiding this comment

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

I don't see the solution in sortFields. That method does exactly what it tells us. It also does not access global state, so there is no need for locking inside sortFields. The problem is that the list returned by getFieldList in line 1128 is modified via the call to sortFields in line 1161 and that list is shared.

protected List<Field> getFields(boolean force) {
List<Field> flist = getFieldList();
Set<String> names = new HashSet<>();
for (Field f : flist) {
names.add(f.getName());
}
List<String> fieldOrder = fieldOrder();
if (fieldOrder.size() != flist.size() && flist.size() > 1) {
if (force) {
throw new Error("Structure.getFieldOrder() on " + getClass()
+ (fieldOrder.size() < flist.size()
? " does not provide enough"
: " provides too many")
+ " names [" + fieldOrder.size()
+ "] ("
+ sort(fieldOrder)
+ ") to match declared fields [" + flist.size()
+ "] ("
+ sort(names)
+ ")");
}
return null;
}
Set<String> orderedNames = new HashSet<>(fieldOrder);
if (!orderedNames.equals(names)) {
throw new Error("Structure.getFieldOrder() on " + getClass()
+ " returns names ("
+ sort(fieldOrder)
+ ") which do not match declared field names ("
+ sort(names) + ")");
}
sortFields(flist, fieldOrder);
return flist;
}

sortFields is only called by getFields and that is only called by deriveLayout and that is called by calculateSize. For "normal" structures caching kicks in, only structures containing arrays will not be cached. So from my POV the most obvious fix would be to only change the original line 1128 to:

    protected List<Field> getFields(boolean force) {
         // line 1128:
        List<Field> flist = new ArrayList<>(getFieldList());
        Set<String> names = new HashSet<>();

That way there is no global state and we need no extra layers.

For anything more complex I want to see numbers.

@matthiasblaesing
Copy link
Member

And the important thing i forgot: thanks for taking care 👍

@brettwooldridge
Copy link
Contributor

brettwooldridge commented Sep 27, 2025

I think I agree with @matthiasblaesing. Ultimately, because the LayoutInfo is cached globally by calculateSize(), the result is deriveLayout() -> getFields() -> sortFields() is only called once for "normal" structures, and therefore there seems no need to cache the sorted fields in a separate global cache.

The only arguable reason to do this would be to speed-up calculating the size of "variable" Structures (LayoutInfo.variable), which are not cached, but the overhead of @matthiasblaesing suggestion of...

    protected List<Field> getFields(boolean force) {
         // line 1128:
        List<Field> flist = new ArrayList<>(getFieldList());
        Set<String> names = new HashSet<>();

...is miniscule in comparison to the work that deriveLayout() already has to do in that case, and therefore of almost no benefit.

At least that's my understanding at present.

@bendk
Copy link
Author

bendk commented Sep 28, 2025

@matthiasblaesing thanks for the suggestion, I'm very happy to go with the simplified version. I didn't understand the caching strategy/requirements when I added that extra code. I'll push a new version up tomorrow.

Copy the fields array from `getFieldList`, this avoids the possibility
of 2 threads trying to mutate it at once in the call `sortFields` at the
bottom of the function.
@bendk bendk changed the title Protect sortFields with a lock (#1686) Avoid threading issues in sortFields (#1686) Sep 29, 2025
@bendk
Copy link
Author

bendk commented Sep 29, 2025

What do you think of making a 5.18.1 release for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants