7122142 : (ann) Race condition between isAnnotationPresent and getAnnotations

Peter Levart peter.levart at gmail.com
Wed Jun 19 14:23:02 UTC 2013


Since the bulk of changes to annotations and reflection have stabilized, 
I'm bringing up a re-based batch that I have proposed some months ago:


The patch fixes a deadlock situation described in:


...and also a logical inconsistency when parsing mutually-recursive 
runtime annotations combined with change of @Retention and separate 
compilation (described in above thread).

Here's the webrev for the patch:


The deadlock occurs because two object monitors are tried to be acquired 
by two threads in different order. One object monitor is represented by 
static synchronized AnnotationType.getInstance(Class) method, the other 
is synchronized Class.initAnnotationsIfNecessary() instance method. 
There are various scenarios where these two methods can be entered by 
different threads in opposite order and for same j.l.Class instance.

AnnotationType.getInstance(Class) is synchronized, because it guards 
access to j.l.Class.annotationType field while AnnotationType instance 
for particular Class is being constructed. AnnotationType instance is 
published via Class.annotationType field in the middle of AnnotationType 
constructor so that annotation's meta-annotations can be obtained after 
that point as the final act of AnnotationType construction. While 
meta-annotations are being obtained, other meta-annotations (besides 
standard @Retention and @Inhereted) can be parsed for the 1st time, 
which triggers AnnotationType.getInstance(Class) call for those 
meta-annotation classes. If such meta-annotation classes are mutually 
recursive with the annotation class that AnnotationType is being 
constructed for, AnnotationType.getInstance(Class) can be re-entered for 
the same annotation class. To prevent infinite recursion, 
half-constructed AnnotationType instance is published in the middle of 
the AnnotationType constructor. Synchronized AnnotationType.getInstance 
prevents other threads to interfere while AnnotationType is being 
constructed, but allows re-entrancy from the same thread.

The presented patch solves this situation by removing the synchronized 
keyword from AnnotationType.getInstance(Class) static method, Making 
this method contention-free as a benefit. All other changes are a result 
of that removal:
- AnnotationType class is made immutable (all fields final)
- The instance is published only after the constructor for 
AnnotationType returns (in AnnotationType.getInstance() method). Because 
caching is idempotent and AnnotationType object is immutable, no 
synchronization is attempted and not even Class.annotationType filed is 
made volatile.
- To prevent infinite recursion while obtaining meta-annotations, the 
process of obtaining them was redesigned. Instead of calling 
getAnnotation(Retention.class) or isAnnotationPresent(Inherited.class) 
which triggers parsing of other meta-annotations too, special method was 
added to AnnotationParser which is used solely to parse select 
meta-annotations in the AnnotationType constructor and which does not 
need to evaluate AnnotationType.getInstance() method, thus making this 
parsing method recursion-free.

The benefit of not recursing while obtaining meta-annotations is also 
more correct logic when obtaining mutualy-recursive annotations for 
which one of them was changed (@Retention: RUNTIME -> CLASS) and 
separately compiled.

This patch is one angle of attack for bug 7122142. The other is, off 
course, removing the other synchronized keyword (on 
Class.initAnnotationsIfNecessary()). I'm planning to do that too, but in 
a more straight-forward manner.

Regards, Peter

More information about the core-libs-dev mailing list