I have a `MutableSet` where I add some elements. M...
# coroutines
l
I have a
MutableSet
where I add some elements. Meanwhile I'm starting a coroutine with a backend call. When the call succeeds, the
MutableSet
is checked if it contains the element from the backend call, and then cleared. Adding to the set is one method,
contains()
and
clear()
is another method. Do I run the risk of
ConcurrentModificationException
? Code example in thread.
Copy code
class CartRepository {
    val lastScannedItems = mutableSetOf<String>()

    fun onNewProductScanned(id: String) {
        lastScannedItems.add(id)
    }

    fun evaluateCart() {
        CoroutineScope(SupervisorJob()).launch {
            val cart = api.evaluateCart() // suspending function
            if (cart.items.any { lastScannedItems.contains(it.id) }) playSound()
            lastScannedItems.clear()
        }
    }
e
yes you do have that risk, and also you should not create temporary `CoroutineScope`s
l
The temporary CoroutineScope is just a shortcut, in reality it's injected
Can I mitigate the risk with creating a Set from
ConcurrentHashMap
?
e
as in
Collections.newSetFromMap()
? you shouldn't encounter CME with the set then
l
With
ConcurrentHashMap.newKeySet()
e
you're still racing the check and clear with the add, it'll just have some nondeterministic result instead of being a crash
l
Yes, I'm fine with the race
e
ah I didn't realize that shortcut existed
l
Just want to avoid the crash
It's since Java 8
Thanks for the help!
I just tested something like this and it doesn't crash:
Copy code
fun main() {
    val set = generateSequence(0) { it + 1 }
        .take(1000_000)
        .toMutableSet()

    thread {
        repeat(1000_000) {
            set.contains(it)
            println("Contains $it: ${set.contains(it)}")
        }
    }

    thread {
        repeat(1000_000) {
            set.add(1000_000 + it)
            println("Added ${1000_000 + it}")
        }
    }
}
This example should actually more or less behave like the one above
d
If you want in all circonstances to avoid a potential concurrent exception, try to use Mutex() with the method withLock at each interaction with the set
But you need to be in a suspend function
👍 1
e
FWIW I dug through the source code of
java.util.HashSet
(which delegates to
java.util.HashMap
) and its
contains
method never throws CME. it looks like it might NPE if you got lucky enough with concurrent operations, but it's rather tricky to hit
(as a reminder, lack of CME does not mean the operations are safe, it just means that a concurrent modification was not detected by the code)
l
Thank you for researching! I think it doesn't actually use iterators under the hood.
But yes, it's risky e.g. should the implementation of
contains()
ever change.