Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArrayIndexOutOfBoundsException in LeftRight.waitForReaders #19

Open
kkris opened this issue Nov 9, 2024 · 0 comments
Open

ArrayIndexOutOfBoundsException in LeftRight.waitForReaders #19

kkris opened this issue Nov 9, 2024 · 0 comments

Comments

@kkris
Copy link

kkris commented Nov 9, 2024

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)
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant