Skip to content

Avoid threading issues in sortFields (#1686)#1687

Merged
matthiasblaesing merged 1 commit into
java-native-access:masterfrom
bendk:push-yyprxvvrrttk
Sep 30, 2025
Merged

Avoid threading issues in sortFields (#1686)#1687
matthiasblaesing merged 1 commit into
java-native-access:masterfrom
bendk:push-yyprxvvrrttk

Conversation

@bendk

@bendk bendk commented Sep 26, 2025

Copy link
Copy Markdown
Contributor

I used essentially the same pattern than validateFields uses.

@bendk

bendk commented Sep 26, 2025

Copy link
Copy Markdown
Contributor Author

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

@matthiasblaesing matthiasblaesing left a comment

Copy link
Copy Markdown
Member

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
Copy Markdown
Member

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

@brettwooldridge

brettwooldridge commented Sep 27, 2025

Copy link
Copy Markdown
Contributor

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

bendk commented Sep 28, 2025

Copy link
Copy Markdown
Contributor Author

@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

bendk commented Sep 29, 2025

Copy link
Copy Markdown
Contributor Author

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

@matthiasblaesing matthiasblaesing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Based on description this should fix the issue. Thank you.

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