Hi! I recently encountered a strange behavior. I h...
# coroutines
f
Hi! I recently encountered a strange behavior. I have a
HashMap
which is only accessed and modified in a single coroutine context:
Copy code
val pushNotificationGeneratorThread = newSingleThreadContext(name = "Push Notification Generator Thread")
suspend fun clear() = run(pushNotificationDispatcherThread) {
    lastNotificationDates.clear()
  }
suspend fun put(key: String, value: Any) = run(pushNotificationDispatcherThread) {
    lastNotificationDates.put(key, value)
  }
However sometimes I still get a
ConcurrentModificationException
. In my understanding this should be threadsafe.
Here is the stacktrace:
Copy code
java.util.ConcurrentModificationException
 	at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
 	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
 	at xxx$startCleanupThread$1.doResume(PushModule.kt:114)
 	at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
 	at kotlin.coroutines.experimental.jvm.internal.CoroutineImpl.resume(CoroutineImpl.kt:54)
 	at kotlinx.coroutines.experimental.CancellableContinuationImpl.afterCompletion(CancellableContinuation.kt:318)
 	at kotlinx.coroutines.experimental.JobSupport.completeUpdateState(Job.kt:426)
 	at kotlinx.coroutines.experimental.JobSupport.updateState(Job.kt:382)
 	at kotlinx.coroutines.experimental.AbstractCoroutine.resume(CoroutineScope.kt:80)
 	at kotlinx.coroutines.experimental.CancellableContinuationImpl.resumeUndispatched(CancellableContinuation.kt:287)
 	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
 	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
 	at java.lang.Thread.run(Thread.java:748)
 	at kotlinx.coroutines.experimental.ResumeUndispatchedRunnable.run(Executors.kt:79)
 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
 	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
b
are you iterating over it somewhere else?
f
yes, I have a cleanup function which removes some entries.
Copy code
suspend fun cleanup() = run(pushNotificationGeneratorThread) {
    while (true) {
      val tenSecondsAgo = Date()
      delay(time = 10, unit = TimeUnit.SECONDS)
      lastNotificationDates.forEach { (userId, dateMap) ->
        dateMap.forEach { (payloadId, lastNotificationDate) -> remove() }
        if (dateMap.isEmpty()) remove()
      }
    }
  }
it looks somewhat like this
b
I mean where is it read/used
f
yes but that function also runs on
pushNotificationGeneratorThread
b
what does the
remove()
function do? does it modify the list?
f
yes, it removes one element from the map
b
isn't that the problem? modifying the map while iterating over it
f
yeah so that was exactly the problem 😀 it turns out this had nothing to do with coroutines 😀 I tried way too hard to understand the coroutine magic, so I didn't saw the obvious. Thanks for the help!