-
Notifications
You must be signed in to change notification settings - Fork 38.4k
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
Avoid race condition in ConcurrentReferenceHashMap #31008
Comments
Sorry this got overlooked @davmac314 I agree with your assessment. There are other possible cases of race conditions, for example I think this is unfortunate but in line with the general approach here: references come and go depending on restructure operations, memory being GC'd, etc. I've checked here that no I'm closing this issue a result, let me know if you can think of a way to solve this particular issue without breaking the runtime model for this class. Thanks for your contribution! |
Hi @bclozel , Having reported the issue I feel I've done my civic duty and if you choose to close the issue that is in your hands. I would suggest that at least the documentation (javadoc for the class) should be updated to reflect this known limitation, lest the class be used by others (either withing the Spring project our outside it) with the expectation that values that are definitely in the map will be able to looked up successfully, but that is up to you. We fixed this in our modified version by, in the case of a restructure using the same array, simply modifying the linked list of entries in each array element "in place" rather than nulling the list first. There is no need to lock for the read operation. Unfortunately I cannot share the code which was written as part of my employment. I looked at the I understand that for a cache this may not matter (other than being a minor performance issue) but once again I would strongly suggest that the class be documented as only being suitable for use as a cache if the issue isn't going to be fixed. |
Thanks for your feedback @davmac314 This is an interesting tradeoff for the project:
I can give it a shot still, would you be opened to reviewing my changes? |
Hi @bclozel, |
Hey @davmac314 - what do you think about this change: https://github.com/bclozel/spring-framework/tree/gh-31008 Thanks for your input! |
@bclozel looks good to me! |
Affects: 6.0.11 (current HEAD) and probably earlier versions
The
ConcurrentReferenceHashMap
used by Spring has a race condition which can cause it to incorrectly return null for a get operation where they key is in the map.In (inner class)
Segment.getReference(...)
, line 500 (method starts at line 493), we see:(This is called from
ConcurrentWeakHashMap.getEntry(...)
, itself called by various methods including get(...)). At this point no lock is held. The comment is the clue: it takes a local reference to the existingreferences
array (this.references
) in order to protect against issues caused by other threads re-assigning this field.Taking a local reference however doesn't protect against other threads writing to the same array. That is exactly what can happen, in certain (rare) circumstances, within the
Segment.restructure(...)
method (line 605+ below, method starts at line 580):Notice that the restructure uses the original array if it is not resizing (line marked with "same array" comment) and that it temporarily assigns each entry in turn with null ("write HERE!"). The entry is restored later on in the loop, but this moment when it is null is the problem, since it may cause
Segment.getReference(...)
(first method posted above), if being executed concurrently in another thread, to retrieve the null value when looking up the entry:This causes it to return null, as if the map does not contain an entry with the given key, when it fact it may do so.
In practice the issue may be very difficult to reproduce and I'm sure it is uncommon "in the wild" but I was able to reproduce the issue by inserting some sleep calls at key points within the ConcurrentReferenceHashMap code and executing operations in parallel from two different threads, while also ensuring that a key in the map became garbage collected before the call to the 2nd operation so that it would perform a
restructure
after the first (get) operation had taken its reference tothis.references
but before it retrieved the entry. This was based on an already slightly modified version of the class which we are using internally but I have inspected the version in Spring carefully and I'm fairly sure the same issue is present.The text was updated successfully, but these errors were encountered: