-
Notifications
You must be signed in to change notification settings - Fork 7.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gracefully handle parse failure with locking #4114
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -212,38 +212,53 @@ private void validateServiceInterface(Class<?> service) { | |
} | ||
|
||
ServiceMethod<?> loadServiceMethod(Class<?> service, Method method) { | ||
// Note: Once we are minSdk 24 this whole method can be replaced by computeIfAbsent. | ||
Object lookup = serviceMethodCache.get(method); | ||
while (true) { | ||
// Note: Once we are minSdk 24 this whole method can be replaced by computeIfAbsent. | ||
Object lookup = serviceMethodCache.get(method); | ||
|
||
if (lookup instanceof ServiceMethod<?>) { | ||
// Happy path: method is already parsed into the model. | ||
return (ServiceMethod<?>) lookup; | ||
} | ||
if (lookup instanceof ServiceMethod<?>) { | ||
// Happy path: method is already parsed into the model. | ||
return (ServiceMethod<?>) lookup; | ||
} | ||
|
||
if (lookup == null) { | ||
// Map does not contain any value. Try to put in a lock for this method. We MUST synchronize | ||
// on the lock before it is visible to others via the map to signal we are doing the work. | ||
Object lock = new Object(); | ||
synchronized (lock) { | ||
lookup = serviceMethodCache.putIfAbsent(method, lock); | ||
if (lookup == null) { | ||
// On successful lock insertion, perform the work and update the map before releasing. | ||
// Other threads may be waiting on lock now and will expect the parsed model. | ||
ServiceMethod<Object> result = ServiceMethod.parseAnnotations(this, service, method); | ||
serviceMethodCache.put(method, result); | ||
return result; | ||
if (lookup == null) { | ||
// Map does not contain any value. Try to put in a lock for this method. We MUST synchronize | ||
// on the lock before it is visible to others via the map to signal we are doing the work. | ||
Object lock = new Object(); | ||
synchronized (lock) { | ||
lookup = serviceMethodCache.putIfAbsent(method, lock); | ||
if (lookup == null) { | ||
// On successful lock insertion, perform the work and update the map before releasing. | ||
// Other threads may be waiting on lock now and will expect the parsed model. | ||
ServiceMethod<Object> result; | ||
try { | ||
result = ServiceMethod.parseAnnotations(this, service, method); | ||
} catch (Throwable e) { | ||
// Remove the lock on failure. Any other locked threads will retry as a result. | ||
serviceMethodCache.remove(method); | ||
throw e; | ||
} | ||
serviceMethodCache.put(method, result); | ||
return result; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Either the initial lookup or the attempt to put our lock in the map has returned someone | ||
// else's lock. This means they are doing the parsing, and will update the map before releasing | ||
// the lock. Once we can take the lock, the map is guaranteed to contain the model. | ||
// Note: There's a chance that our effort to put a lock into the map has actually returned a | ||
// finished model instead of a lock. In that case this code will perform a pointless lock and | ||
// redundant lookup in the map of the same instance. This is rare, and ultimately harmless. | ||
synchronized (lookup) { | ||
return (ServiceMethod<?>) serviceMethodCache.get(method); | ||
// Either the initial lookup or the attempt to put our lock in the map has returned someone | ||
// else's lock. This means they are doing the parsing, and will update the map before | ||
// releasing | ||
// the lock. Once we can take the lock, the map is guaranteed to contain the model or null. | ||
// Note: There's a chance that our effort to put a lock into the map has actually returned a | ||
// finished model instead of a lock. In that case this code will perform a pointless lock and | ||
// redundant lookup in the map of the same instance. This is rare, and ultimately harmless. | ||
synchronized (lookup) { | ||
Object result = serviceMethodCache.get(method); | ||
if (result == null) { | ||
// The other thread failed its parsing. We will retry (and probably also fail). | ||
continue; | ||
} | ||
Comment on lines
+256
to
+259
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And here (plus the enclosing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am thinking about whether this situation is possible: |
||
return (ServiceMethod<?>) result; | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change is here