Skip to content
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

An impossible race condition #3888

Closed
westnordost opened this issue Mar 19, 2022 · 8 comments
Closed

An impossible race condition #3888

westnordost opened this issue Mar 19, 2022 · 8 comments
Labels
bug help wanted help by contributors is appreciated; might be a good first contribution for first-timers

Comments

@westnordost
Copy link
Member

westnordost commented Mar 19, 2022

I got this crash report recently:

java.util.NoSuchElementException: Key
       nodes, ways, relations with
         historic = wayside_shrine
         and !religion
         and access !~ private|no
    is missing in the map.
at kotlin.collections.MapsKt__MapWithDefaultKt.getOrImplicitDefaultNullable(MapWithDefault.kt:24)
at kotlin.collections.MapsKt__MapsKt.getValue(Maps.kt:346)
at kotlin.collections.MapsKt.getValue(Unknown Source:0)
at de.westnordost.streetcomplete.data.osm.mapdata.MapDataXtKt.getElementFilterExpression(MapDataXt.kt:30)

But the code in qestion is

private val filterExpCache: MutableMap<String, ElementFilterExpression> = HashMap()

private fun getElementFilterExpression(expr: String): ElementFilterExpression {
    if (!filterExpCache.containsKey(expr)) {
        synchronized(filterExpCache) {
            if (!filterExpCache.containsKey(expr)) {
                filterExpCache[expr] = expr.toElementFilterExpression()
            }
        }
    }
    return filterExpCache.getValue(expr)
}

Anyone, any idea how this is possible? I.e. how it is possible that the key does not exist in the map at the return statement? (String.toElementFilterExpression() returns an ElementFilterExpression, not nullable)

@westnordost westnordost added bug help wanted help by contributors is appreciated; might be a good first contribution for first-timers labels Mar 19, 2022
@kmpoppe
Copy link
Contributor

kmpoppe commented Mar 19, 2022

Could this be some weird threading issue? Would it help to put the whole if-block into synchronized?

@westnordost
Copy link
Member Author

westnordost commented Mar 19, 2022

I think it must be, but the synchronized should prevent this. And the whole if block is already in the synchronized block, i fyou look closely.

@kmpoppe
Copy link
Contributor

kmpoppe commented Mar 19, 2022

Well the first line of getElementFilterExpression is if (!filterExpCache.containsKey(expr)) {.
I see that the very same line is within synchronized again, but I wonder if the first access to the Cache being outside of synchronized causes the issue (because it checks for the contents of the Map before the sync gets initialized)?

@westnordost
Copy link
Member Author

Why/how? This could only be a potential problem if elements from the filterExpCache are ever removed (but they are not), the way I see it.

@tapetis
Copy link
Contributor

tapetis commented Mar 19, 2022

As far as I understand the code of Java's HashMap, the insertion of a new element into the map is a complex process that can result in a rearranging of the map's internal tree structure. This rearranging process is only started after the new element is added to an empty position in the tree. At this time, containsKey returns true.

During rearranging, however, elements may be temporarily removed from the tree. This can result in previously inserted keys not being in the map during the iteration of the map's nodes and thus in getValue returning null.

@kmpoppe
Copy link
Contributor

kmpoppe commented Mar 20, 2022

That sound like a damn good explanation of the problem @tapetis 👍

@westnordost
Copy link
Member Author

Oh wow, I never thought of that. One lives and learns. Makes me wonder what would happen if one adds another value (in another thread) while the rearranging process is being done. Anyway, so either I have to use synchronized everywhere when accessing a HashMap or use ConcurrentHashMap.

@westnordost
Copy link
Member Author

I've used this pattern in 2 more places, they should be fixed now. Thank you @tapetis !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug help wanted help by contributors is appreciated; might be a good first contribution for first-timers
Projects
None yet
Development

No branches or pull requests

3 participants