Jérémy CROS
06/26/2025, 1:31 PMprivate val list = mutableListOf<T>()
private val mutex = Mutex()
override suspend fun addData(data: List<T>) {
mutex.withLock {
list.add(data)
}
}
Every once in a while, we're saving this data to a file in a string format so we have:
private fun getLog(data: List<T>): String = data.mapNotNull { it.format() }.joinToString(" ")
(here, we're passing the above mutable list as parameter data)
And this particular function sometimes (rarely) generates crashlytics reports with the exception ConcurrentModificationException
So, 2 questions really 😅 How can this function throw this particular exception when, as far as I can tell, it's not modifying anything?
And, what's the best way to make this more robust?
Thanks! 😊Sergey Dmitriev
06/26/2025, 1:46 PMmutableListOf
uses ArrayList
under the hood and its Iterator.next()
can throw ConcurrentModificationException
— and you are using the iterator when mapNotNull
Make a copy of the list before writing it to file
val newList = ArrayList<String>(originalList)
Jérémy CROS
06/26/2025, 1:54 PMSergey Dmitriev
06/26/2025, 1:56 PMpublic ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
It looks safe 🙂ephemient
06/26/2025, 1:57 PMephemient
06/26/2025, 1:59 PMSergey Dmitriev
06/26/2025, 1:59 PMConcurrentModificationException
Jérémy CROS
06/26/2025, 2:10 PMval safeList = mutex.withLock { unsafeList.toList() }
val safeList = CopyOnWriteArrayList(unsafeList)
ephemient
06/26/2025, 2:11 PMArrayList(unsafeList)
. if you change the declaration itself to
private val list = CopyOnWriteArrayList<T>()
you will not need mutex
Sergey Dmitriev
06/26/2025, 2:12 PMgetLog
with mutex insteadSergey Dmitriev
06/26/2025, 2:13 PMprivate suspend fun getLog(data: List<T>): String {
return mutex.withLock {
data.mapNotNull { it.format() }.joinToString(" ")
}
}
Sergey Dmitriev
06/26/2025, 2:14 PMJérémy CROS
06/26/2025, 2:17 PMephemient
06/26/2025, 2:19 PMJérémy CROS
06/26/2025, 2:37 PMJoffrey
06/26/2025, 2:48 PMephemient
06/26/2025, 2:50 PMArjan van Wieringen
06/26/2025, 4:41 PMephemient
06/26/2025, 4:44 PMRonny Bräunlich
06/27/2025, 4:42 AMjava.util.Collections#synchronizedList(java.util.List<T>)
you could use.ephemient
06/27/2025, 1:06 PMephemient
06/27/2025, 1:08 PMsynchronized(list) {
list.mapNotNull { ... }
}
but you can't mix that and `suspend`ing functions