Skip to content

Commit db67c42

Browse files
Fixes #1625 issue by caching the field list and validation state in addition to the existing layoutInfo and fieldOrder caches.
1 parent 50b1cbf commit db67c42

File tree

2 files changed

+102
-26
lines changed

2 files changed

+102
-26
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Next Release (5.16.0)
77

88
Features
99
--------
10+
* [#1626](https://github.com/java-native-access/jna/pull/1626): Add caching of field list and field validation in `Structure` along with more efficient reentrant read-write locking instead of synchronized() blocks - [@BrettWooldridge](https://github.com/brettwooldridge)
1011

1112
Bug Fixes
1213
---------

src/com/sun/jna/Structure.java

+101-26
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import java.util.Map;
4949
import java.util.Set;
5050
import java.util.WeakHashMap;
51+
import java.util.concurrent.locks.ReentrantReadWriteLock;
5152
import java.util.logging.Level;
5253
import java.util.logging.Logger;
5354

@@ -155,8 +156,14 @@ private static class NativeStringTracking {
155156
//public static final int ALIGN_8 = 6;
156157

157158
protected static final int CALCULATE_SIZE = -1;
159+
static final ReentrantReadWriteLock layoutInfoLock = new ReentrantReadWriteLock();
160+
static final ReentrantReadWriteLock fieldOrderLock = new ReentrantReadWriteLock();
161+
static final ReentrantReadWriteLock fieldListLock = new ReentrantReadWriteLock();
162+
static final ReentrantReadWriteLock validationLock = new ReentrantReadWriteLock();
158163
static final Map<Class<?>, LayoutInfo> layoutInfo = new WeakHashMap<>();
159164
static final Map<Class<?>, List<String>> fieldOrder = new WeakHashMap<>();
165+
static final Map<Class<?>, List<Field>> fieldList = new WeakHashMap<>();
166+
static final Map<Class<?>, Boolean> validationMap = new WeakHashMap<>();
160167

161168
// This field is accessed by native code
162169
private Pointer memory;
@@ -1015,36 +1022,68 @@ protected void sortFields(List<Field> fields, List<String> names) {
10151022
* this {@link Structure} class.
10161023
*/
10171024
protected List<Field> getFieldList() {
1018-
List<Field> flist = new ArrayList<>();
1019-
for (Class<?> cls = getClass();
1020-
!cls.equals(Structure.class);
1021-
cls = cls.getSuperclass()) {
1022-
List<Field> classFields = new ArrayList<>();
1023-
Field[] fields = cls.getDeclaredFields();
1024-
for (int i=0;i < fields.length;i++) {
1025-
int modifiers = fields[i].getModifiers();
1026-
if (Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
1027-
continue;
1028-
}
1029-
classFields.add(fields[i]);
1025+
Class<?> clazz = getClass();
1026+
// Try to read the value under the read lock
1027+
fieldListLock.readLock().lock();
1028+
try {
1029+
List<Field> fields = fieldList.get(clazz);
1030+
if (fields != null) {
1031+
return fields; // Return the cached result if found
10301032
}
1031-
flist.addAll(0, classFields);
1033+
} finally {
1034+
fieldListLock.readLock().unlock();
1035+
}
1036+
1037+
// If not found, compute the value under the write lock
1038+
fieldListLock.writeLock().lock();
1039+
try {
1040+
// Double-check if another thread has computed the value before we do
1041+
return fieldList.computeIfAbsent(clazz, (c) -> {
1042+
List<Field> flist = new ArrayList<>();
1043+
List<Field> classFields = new ArrayList<>();
1044+
for (Class<?> cls = clazz;
1045+
!cls.equals(Structure.class);
1046+
cls = cls.getSuperclass()) {
1047+
for (Field field : cls.getDeclaredFields()) {
1048+
int modifiers = field.getModifiers();
1049+
if (Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
1050+
continue;
1051+
}
1052+
classFields.add(field);
1053+
}
1054+
flist.addAll(0, classFields);
1055+
classFields.clear();
1056+
}
1057+
return flist;
1058+
});
1059+
} finally {
1060+
fieldListLock.writeLock().unlock();
10321061
}
1033-
return flist;
10341062
}
10351063

10361064
/** Cache field order per-class.
10371065
* @return (cached) ordered list of fields
10381066
*/
10391067
private List<String> fieldOrder() {
10401068
Class<?> clazz = getClass();
1041-
synchronized(fieldOrder) {
1042-
List<String> list = fieldOrder.get(clazz);
1043-
if (list == null) {
1044-
list = getFieldOrder();
1045-
fieldOrder.put(clazz, list);
1069+
// Try to read the value under the read lock
1070+
fieldOrderLock.readLock().lock();
1071+
try {
1072+
List<String> order = fieldOrder.get(clazz);
1073+
if (order != null) {
1074+
return order; // Return the cached result if found
10461075
}
1047-
return list;
1076+
} finally {
1077+
fieldOrderLock.readLock().unlock();
1078+
}
1079+
1080+
// If not found, compute the value under the write lock
1081+
fieldOrderLock.writeLock().lock();
1082+
try {
1083+
// Double-check if another thread has computed the value before we do (see JavaDoc)
1084+
return fieldOrder.computeIfAbsent(clazz, (c) -> getFieldOrder());
1085+
} finally {
1086+
fieldOrderLock.writeLock().unlock();
10481087
}
10491088
}
10501089

@@ -1159,8 +1198,11 @@ static int size(Class<? extends Structure> type) {
11591198
*/
11601199
static <T extends Structure> int size(Class<T> type, T value) {
11611200
LayoutInfo info;
1162-
synchronized(layoutInfo) {
1201+
layoutInfoLock.readLock().lock();
1202+
try {
11631203
info = layoutInfo.get(type);
1204+
} finally {
1205+
layoutInfoLock.readLock().unlock();
11641206
}
11651207
int sz = (info != null && !info.variable) ? info.size : CALCULATE_SIZE;
11661208
if (sz == CALCULATE_SIZE) {
@@ -1183,8 +1225,11 @@ int calculateSize(boolean force, boolean avoidFFIType) {
11831225
int size = CALCULATE_SIZE;
11841226
Class<?> clazz = getClass();
11851227
LayoutInfo info;
1186-
synchronized(layoutInfo) {
1228+
layoutInfoLock.readLock().lock();
1229+
try {
11871230
info = layoutInfo.get(clazz);
1231+
} finally {
1232+
layoutInfoLock.readLock().unlock();
11881233
}
11891234
if (info == null
11901235
|| this.alignType != info.alignType
@@ -1196,7 +1241,8 @@ int calculateSize(boolean force, boolean avoidFFIType) {
11961241
this.structFields = info.fields;
11971242

11981243
if (!info.variable) {
1199-
synchronized(layoutInfo) {
1244+
layoutInfoLock.readLock().lock();
1245+
try {
12001246
// If we've already cached it, only override layout if
12011247
// we're using non-default values for alignment and/or
12021248
// type mapper; this way we don't override the cache
@@ -1205,8 +1251,18 @@ int calculateSize(boolean force, boolean avoidFFIType) {
12051251
if (!layoutInfo.containsKey(clazz)
12061252
|| this.alignType != ALIGN_DEFAULT
12071253
|| this.typeMapper != null) {
1254+
// Must release read lock before acquiring write lock (see JavaDoc lock escalation example)
1255+
layoutInfoLock.readLock().unlock();
1256+
layoutInfoLock.writeLock().lock();
1257+
12081258
layoutInfo.put(clazz, info);
1259+
1260+
// Downgrade by acquiring read lock before releasing write lock (again, see JavaDoc)
1261+
layoutInfoLock.readLock().lock();
1262+
layoutInfoLock.writeLock().unlock();;
12091263
}
1264+
} finally {
1265+
layoutInfoLock.readLock().unlock();
12101266
}
12111267
}
12121268
size = info.size;
@@ -1250,9 +1306,28 @@ private void validateField(String name, Class<?> type) {
12501306

12511307
/** ensure all fields are of valid type. */
12521308
private void validateFields() {
1253-
List<Field> fields = getFieldList();
1254-
for (Field f : fields) {
1255-
validateField(f.getName(), f.getType());
1309+
// Try to read the value under the read lock
1310+
validationLock.readLock().lock();
1311+
try {
1312+
if (validationMap.containsKey(getClass())) {
1313+
return; // Return because this Structure has already been validated
1314+
}
1315+
} finally {
1316+
validationLock.readLock().unlock();
1317+
}
1318+
1319+
// If not found, perform validation and update the cache under the write lock
1320+
validationLock.writeLock().lock();
1321+
try {
1322+
// Double-check if another thread has computed the value before we do (see JavaDoc)
1323+
validationMap.computeIfAbsent(getClass(), (cls) -> {
1324+
for (Field f : getFieldList()) {
1325+
validateField(f.getName(), f.getType());
1326+
}
1327+
return true;
1328+
});
1329+
} finally {
1330+
validationLock.writeLock().unlock();
12561331
}
12571332
}
12581333

0 commit comments

Comments
 (0)