简体   繁体   中英

Under which circumstances can toSet throw an java.lang.IllegalArgumentException?

Based on our Crashlytics logs it seems that we're running into the following exception from time to time:

Fatal Exception: java.lang.IllegalArgumentException
Illegal initial capacity: -1
...
java.util.HashMap.<init> (HashMap.java:448)
java.util.LinkedHashMap.<init> (LinkedHashMap.java:371)
java.util.HashSet.<init> (HashSet.java:161)
java.util.LinkedHashSet.<init> (LinkedHashSet.java:146) 
kotlin.collections.CollectionsKt___CollectionsKt.toSet (CollectionsKt___CollectionsKt.java:1316) 

But we're not sure when it is possible that this exception is actually thrown. The relevant code for this statement looks something like this:

private val markersMap = mutableMapOf<Any, Marker>()
...
synchronized(markersMap) {
    val currentMarkers = markersMap.values.toSet() //it crashes here
    // performing some operation on the markers
}

Right now we're suspecting multithreading to cause the issue as the markersMap is modified in multiple places, but as the map is already initialized by default we're not really sure how it can end up in less than an empty state. We also took a look at the toSet implementation:

if (this is Collection) {
    return when (size) {
        0 -> emptySet()
        1 -> setOf(if (this is List) this[0] else iterator().next())
        else -> toCollection(LinkedHashSet<T>(mapCapacity(size)))
    }
}

Based on this, we'd assume that mapCapacity(size) returns -1 , but we weren't able to find the actual implementation of mapCapacity to verify when this can happen.

Does anybody know when -1 is returned here, which in turn causes the constructor to fail?

Java collections are not synchronized and if you need to access a Map or any collection from multiple threads then you are required to take care of synchonization. as stated in LinkedHashMap 's header

Note that this implementation is not synchronized.If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally.

My guess is that you are probably performing structural modifications(mix of put and remove ) on the Map without synchronization, which can cause this issue. for example

fun main(){
    val markersMap = mutableMapOf<Any, Any>()
    (1..1000).forEach { markersMap.put(it, "$it") }
    val t1 = Thread{
        (1..1000).forEach { markersMap.remove(it)
            if(markersMap.size < 0){
                print("SIZE IS ${markersMap.size}")
            }
        }
    }

    val t2 = Thread{
        (1..1000).forEach {
            markersMap.remove(it)
            if(markersMap.size < 0){
               print("SIZE IS ${markersMap.size}")
            }
        }
    }
    t1.start()
    t2.start()
}

On my machine this code prints SIZE IS -128 , SIZE IS -127 and lot many other negative values and when I added markersMap.values.toSet() inside one of the if blocks, this happened

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM