Skip to content

Commit 1c68403

Browse files
edalquistchingor13
authored andcommitted
Fix deadlock caused by concurrent class loading (#639)
When loading generated data classes concurrently the ProGuard protection which calls Data.nullOf in a static initializer can result in a deadlock on the Data.NULL_CACHE lock. The fix is to emulate ConcurrentMap.computeIfAbsent to protect against blocking during concurrent populate of the cache.
1 parent 1f28f54 commit 1c68403

File tree

1 file changed

+38
-27
lines changed
  • google-http-client/src/main/java/com/google/api/client/util

1 file changed

+38
-27
lines changed

google-http-client/src/main/java/com/google/api/client/util/Data.java

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -108,42 +108,53 @@ public class Data {
108108
* @return magic object instance that represents the "null" value (not Java {@code null})
109109
* @throws IllegalArgumentException if unable to create a new instance
110110
*/
111-
public static <T> T nullOf(Class<T> objClass) {
111+
public static <T> T nullOf(Class<?> objClass) {
112+
// ConcurrentMap.computeIfAbsent is explicitly NOT used in the following logic. The
113+
// ConcurrentHashMap implementation of that method BLOCKS if the mappingFunction triggers
114+
// modification of the map which createNullInstance can do depending on the state of class
115+
// loading.
112116
Object result = NULL_CACHE.get(objClass);
113117
if (result == null) {
114-
synchronized (NULL_CACHE) {
115-
result = NULL_CACHE.get(objClass);
116-
if (result == null) {
117-
if (objClass.isArray()) {
118-
// arrays are special because we need to compute both the dimension and component type
119-
int dims = 0;
120-
Class<?> componentType = objClass;
121-
do {
122-
componentType = componentType.getComponentType();
123-
dims++;
124-
} while (componentType.isArray());
125-
result = Array.newInstance(componentType, new int[dims]);
126-
} else if (objClass.isEnum()) {
127-
// enum requires look for constant with @NullValue
128-
FieldInfo fieldInfo = ClassInfo.of(objClass).getFieldInfo(null);
129-
Preconditions.checkNotNull(
130-
fieldInfo, "enum missing constant with @NullValue annotation: %s", objClass);
131-
@SuppressWarnings({"unchecked", "rawtypes"})
132-
Enum e = fieldInfo.<Enum>enumValue();
133-
result = e;
134-
} else {
135-
// other classes are simpler
136-
result = Types.newInstance(objClass);
137-
}
138-
NULL_CACHE.put(objClass, result);
139-
}
118+
// If nullOf is called concurrently for the same class createNullInstance may be executed
119+
// multiple times. However putIfAbsent ensures that no matter what the concurrent access
120+
// pattern looks like callers always get a singleton instance returned. Since
121+
// createNullInstance has no side-effects beyond triggering class loading this multiple-call
122+
// pattern is safe.
123+
Object newValue = createNullInstance(objClass);
124+
result = NULL_CACHE.putIfAbsent(objClass, newValue);
125+
if (result == null) {
126+
result = newValue;
140127
}
141128
}
142129
@SuppressWarnings("unchecked")
143130
T tResult = (T) result;
144131
return tResult;
145132
}
146133

134+
private static Object createNullInstance(Class<?> objClass) {
135+
if (objClass.isArray()) {
136+
// arrays are special because we need to compute both the dimension and component type
137+
int dims = 0;
138+
Class<?> componentType = objClass;
139+
do {
140+
componentType = componentType.getComponentType();
141+
dims++;
142+
} while (componentType.isArray());
143+
return Array.newInstance(componentType, new int[dims]);
144+
}
145+
if (objClass.isEnum()) {
146+
// enum requires look for constant with @NullValue
147+
FieldInfo fieldInfo = ClassInfo.of(objClass).getFieldInfo(null);
148+
Preconditions.checkNotNull(
149+
fieldInfo, "enum missing constant with @NullValue annotation: %s", objClass);
150+
@SuppressWarnings({"unchecked", "rawtypes"})
151+
Enum e = fieldInfo.<Enum>enumValue();
152+
return e;
153+
}
154+
// other classes are simpler
155+
return Types.newInstance(objClass);
156+
}
157+
147158
/**
148159
* Returns whether the given object is the magic object that represents the null value of its
149160
* class.

0 commit comments

Comments
 (0)