Skip to content

ArrayIndexOutOfBoundsException in LeftRight.waitForReaders #19

@kkris

Description

@kkris

First of all, thanks for a nice concurrent hashmap implementation, was glad to find one I could use in my multiplatform app!

Today I got a crash report from production which I narrowed down to a simple reproducing example attached and pasted below. I am not quite sure what is going on, but it reproduces every time so I guess this is some kind of edge case and not a race condition.
There are two examples which I think are related:

  • using suspendingWorker an exception is thrown (see below)
  • using blockingWorker the workers never finish even though one would expect them to do

Exception:

Exception in thread "DefaultDispatcher-worker-48" java.lang.ArrayIndexOutOfBoundsException: Index 64 out of bounds for length 64
	at io.github.charlietap.leftright.LeftRight.waitForReaders(LeftRight.kt:60)
	at io.github.charlietap.cachemap.InternalCacheMap.put(InternalCacheMap.kt:165)
	at org.example.AppKt.suspendingWorker(App.kt:42)
	at org.example.AppKt$suspendingWorker$1.invokeSuspend(App.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:101)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:113)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:589)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:823)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:720)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:707)
	Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@1b38f3d3, Dispatchers.Default]

Example project to reproduce the issue. Use ./gradlew run to execute.
cachemap-bug-reproducer.zip

@OptIn(DelicateCoroutinesApi::class)
fun main(args: Array<String>) {
    val cache: CacheMap<String, String> = cacheMapOf()

    repeat(64) {
        GlobalScope.launch {
            withContext(Dispatchers.IO) {
                suspendingWorker(cache)

                // Note: Alternatively, using this worker seems to unexpectedly block at some point
                // blockingWorker(cache)
            }
        }
    }

    Thread.sleep(100_000)
}

suspend fun suspendingWorker(cache: CacheMap<String, String>) {
    while (true) {
        val key = Random.nextInt(1000).toString()

        val existing = cache.get(key)
        if (existing == null) {
            val value = GlobalScope.async {
                UUID.randomUUID().toString()
            }.await()

            cache.put(key, value)
        }

        if (cache.size >= 998) {
            break
        }
    }
}

fun blockingWorker(cache: CacheMap<String, String>) {
    while (true) {
        val key = Random.nextInt(1000).toString()
        val existing = cache.get(key)
        if (existing == null) {
            cache.put(key, UUID.randomUUID().toString())
        }

        if (cache.size >= 998) {
            // Note: code never reaches this break even though one would expect the cache to fill up at some point
            break
        }

        println(cache.size)
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions