Marco Garofalo
03/04/2024, 4:48 PMpackage com.example.gameplay
class RotatingSecrets(private val secrets: List<String>) : Secrets {
private var position = 0
override fun next(): String {
return secrets[position++ % secrets.size]
}
}
This is what I have tried so far:
package com.example.gameplay
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.coroutines.CoroutineStart.LAZY
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class RotatingSecretsTests {
@Test
fun `is thread safe`() = runTest {
val concurrentAccesses = 100
val secrets = (1..concurrentAccesses).map { "secret-$it" }
val rotatingSecrets = RotatingSecrets(secrets)
val jobs = secrets.map {
async (start = LAZY) {
rotatingSecrets.next()
}
}
val seenSecrets = jobs.awaitAll()
seenSecrets.sorted() shouldBeEqual secrets.sorted()
}
}
I have also tried increasing the concurrency up to 10_000, but no luck.
Any clever idea?Peter Farlow
03/04/2024, 5:15 PMMarco Garofalo
03/04/2024, 5:17 PMPeter Farlow
03/04/2024, 5:19 PMMarco Garofalo
03/04/2024, 5:22 PMvar x = 0
val y = x++
=>
val y = x
x = x + 1
That said, I have the suspicion that Kotlin might compile that class in someway that prevents the race condition, since is just a very simple code.Peter Farlow
03/04/2024, 5:24 PMPeter Farlow
03/04/2024, 5:24 PMPeter Farlow
03/04/2024, 5:25 PMMarco Garofalo
03/04/2024, 5:25 PMPeter Farlow
03/04/2024, 5:26 PMMarco Garofalo
03/04/2024, 5:26 PMMarco Garofalo
03/04/2024, 5:26 PM@Test
fun `is thread safe`() = runTest {
val concurrentAccesses = 100
val secrets = (1..concurrentAccesses).map { "secret-$it" }
val rotatingSecrets = RotatingSecrets(secrets)
val threads = newFixedThreadPoolContext(10, "rotating-secrets-test")
val jobs = secrets.map {
async (start = LAZY, context = threads) {
delay((100..200L).random())
println("[${Thread.currentThread().name}] running")
rotatingSecrets.next()
}
}
val seenSecrets = jobs.awaitAll()
seenSecrets.sorted() shouldBeEqual secrets.sorted()
}
Marco Garofalo
03/04/2024, 5:27 PMephemient
03/04/2024, 6:15 PMrunTest
is not helping you here at all - its goal is to make tests fast and repeatable. might as well dump itephemient
03/04/2024, 6:15 PMMarco Garofalo
03/04/2024, 7:29 PMclass RotatingSecretsTests {
private val rotatingSecrets = RotatingSecrets(listOf("first", "second"))
@Operation
fun nextSecret() = rotatingSecrets.next()
@Test
fun `is thread safe`() = StressOptions()
.threads(2)
.iterations(10)
.check(this::class)
}