@@ -212,38 +212,53 @@ private void validateServiceInterface(Class<?> service) {
212212 }
213213
214214 ServiceMethod <?> loadServiceMethod (Class <?> service , Method method ) {
215- // Note: Once we are minSdk 24 this whole method can be replaced by computeIfAbsent.
216- Object lookup = serviceMethodCache .get (method );
215+ while (true ) {
216+ // Note: Once we are minSdk 24 this whole method can be replaced by computeIfAbsent.
217+ Object lookup = serviceMethodCache .get (method );
217218
218- if (lookup instanceof ServiceMethod <?>) {
219- // Happy path: method is already parsed into the model.
220- return (ServiceMethod <?>) lookup ;
221- }
219+ if (lookup instanceof ServiceMethod <?>) {
220+ // Happy path: method is already parsed into the model.
221+ return (ServiceMethod <?>) lookup ;
222+ }
222223
223- if (lookup == null ) {
224- // Map does not contain any value. Try to put in a lock for this method. We MUST synchronize
225- // on the lock before it is visible to others via the map to signal we are doing the work.
226- Object lock = new Object ();
227- synchronized (lock ) {
228- lookup = serviceMethodCache .putIfAbsent (method , lock );
229- if (lookup == null ) {
230- // On successful lock insertion, perform the work and update the map before releasing.
231- // Other threads may be waiting on lock now and will expect the parsed model.
232- ServiceMethod <Object > result = ServiceMethod .parseAnnotations (this , service , method );
233- serviceMethodCache .put (method , result );
234- return result ;
224+ if (lookup == null ) {
225+ // Map does not contain any value. Try to put in a lock for this method. We MUST synchronize
226+ // on the lock before it is visible to others via the map to signal we are doing the work.
227+ Object lock = new Object ();
228+ synchronized (lock ) {
229+ lookup = serviceMethodCache .putIfAbsent (method , lock );
230+ if (lookup == null ) {
231+ // On successful lock insertion, perform the work and update the map before releasing.
232+ // Other threads may be waiting on lock now and will expect the parsed model.
233+ ServiceMethod <Object > result ;
234+ try {
235+ result = ServiceMethod .parseAnnotations (this , service , method );
236+ } catch (Throwable e ) {
237+ // Remove the lock on failure. Any other locked threads will retry as a result.
238+ serviceMethodCache .remove (method );
239+ throw e ;
240+ }
241+ serviceMethodCache .put (method , result );
242+ return result ;
243+ }
235244 }
236245 }
237- }
238246
239- // Either the initial lookup or the attempt to put our lock in the map has returned someone
240- // else's lock. This means they are doing the parsing, and will update the map before releasing
241- // the lock. Once we can take the lock, the map is guaranteed to contain the model.
242- // Note: There's a chance that our effort to put a lock into the map has actually returned a
243- // finished model instead of a lock. In that case this code will perform a pointless lock and
244- // redundant lookup in the map of the same instance. This is rare, and ultimately harmless.
245- synchronized (lookup ) {
246- return (ServiceMethod <?>) serviceMethodCache .get (method );
247+ // Either the initial lookup or the attempt to put our lock in the map has returned someone
248+ // else's lock. This means they are doing the parsing, and will update the map before
249+ // releasing
250+ // the lock. Once we can take the lock, the map is guaranteed to contain the model or null.
251+ // Note: There's a chance that our effort to put a lock into the map has actually returned a
252+ // finished model instead of a lock. In that case this code will perform a pointless lock and
253+ // redundant lookup in the map of the same instance. This is rare, and ultimately harmless.
254+ synchronized (lookup ) {
255+ Object result = serviceMethodCache .get (method );
256+ if (result == null ) {
257+ // The other thread failed its parsing. We will retry (and probably also fail).
258+ continue ;
259+ }
260+ return (ServiceMethod <?>) result ;
261+ }
247262 }
248263 }
249264
0 commit comments