diff --git a/retry/README.md b/retry/README.md new file mode 100644 index 0000000..7717074 --- /dev/null +++ b/retry/README.md @@ -0,0 +1,4 @@ +Retry +================ + +En dingseboms som retryer ting diff --git a/retry/build.gradle.kts b/retry/build.gradle.kts new file mode 100644 index 0000000..d583cc6 --- /dev/null +++ b/retry/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") +} \ No newline at end of file diff --git a/retry/src/main/kotlin/com/github/navikt/tbd_libs/retry/Retry.kt b/retry/src/main/kotlin/com/github/navikt/tbd_libs/retry/Retry.kt new file mode 100644 index 0000000..d890832 --- /dev/null +++ b/retry/src/main/kotlin/com/github/navikt/tbd_libs/retry/Retry.kt @@ -0,0 +1,33 @@ +package com.github.navikt.tbd_libs.retry + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import java.time.Duration +import java.time.Duration.ofMillis + +suspend inline fun retry( + utsettelser: Iterator = DefaultUtsettelser(), + avbryt: (throwable: Throwable) -> Boolean = { false }, + block: () -> T +): T { + while (utsettelser.hasNext()) { + try { return block() } catch (t: Throwable) { + if (t is CancellationException || avbryt(t)) throw t + } + delay(utsettelser.next().toMillis()) + } + return block() +} + +inline fun retryBlocking( + utsettelser: Iterator = DefaultUtsettelser(), + crossinline avbryt: (throwable: Throwable) -> Boolean = { false }, + crossinline block: () -> T +) = runBlocking { retry(utsettelser, avbryt, block) } + +class DefaultUtsettelser: Iterator { + private val utsettelser = mutableListOf(ofMillis(200), ofMillis(600), ofMillis(1200)) + override fun hasNext() = utsettelser.isNotEmpty() + override fun next(): Duration = utsettelser.removeAt(0) +} \ No newline at end of file diff --git a/retry/src/test/kotlin/com/github/navikt/tbd_libs/retry/RetryTest.kt b/retry/src/test/kotlin/com/github/navikt/tbd_libs/retry/RetryTest.kt new file mode 100644 index 0000000..4a98fb1 --- /dev/null +++ b/retry/src/test/kotlin/com/github/navikt/tbd_libs/retry/RetryTest.kt @@ -0,0 +1,72 @@ +package com.github.navikt.tbd_libs.retry + +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +internal class RetryTest { + + @Test + fun `Først en test av dingsen`() { + val dings = DingsSomFunkerPåForsøk(4) + assertEquals("En feil på forsøk 1", assertThrows { dings.gjørtingBlocking() }.message) + assertEquals("En feil på forsøk 2", assertThrows { dings.gjørtingBlocking() }.message) + assertEquals("En feil på forsøk 3", assertThrows { dings.gjørtingBlocking() }.message) + assertEquals("Det gikk jo bra på førsøk 4 da", dings.gjørtingBlocking()) + } + + @Test + fun `Når noe funker med en gang`() { + val dings = DingsSomFunkerPåForsøk(1) + assertEquals("Det gikk jo bra på førsøk 1 da", retryBlocking { dings.gjørtingBlocking() }) + } + + @Test + fun `Når noe funker med på førsøk 3`() { + val dings = DingsSomFunkerPåForsøk(3) + assertEquals("Det gikk jo bra på førsøk 3 da", retryBlocking { dings.gjørtingBlocking() }) + } + + @Test + fun `Når noe feiler 4 ganger og vi ikke prøver noe mer`() { + val dings = DingsSomFunkerPåForsøk(5) + assertEquals("En feil på forsøk 4", assertThrows { retryBlocking { dings.gjørtingBlocking() } }.message) + } + + @Test + fun `Om det er en spesiell feil som vi ikke vil retrye på så fortsetter vi ei`() { + val dings = DingsSomFunkerPåForsøk(10, feil = { EnfeilViIkkeVilRetrye(it) }) + assertEquals( + "For dette her fikses ikke av en retry. Så derfor feiler vi på forsøk 1", + assertThrows { retryBlocking(avbryt = { it is EnfeilViIkkeVilRetrye} ) { dings.gjørtingBlocking() } }.message + ) + } + + @Test + fun `Når noe funker med på førsøk 3 suspendable`() = runBlocking { + val dings = DingsSomFunkerPåForsøk(3) + assertEquals("Det gikk jo bra på førsøk 3 da", retry { dings.gjørting() }) + } + + private class DingsSomFunkerPåForsøk( + private val forsøk: Int, + private val feil: (nåværendeForsøk: Int) -> Throwable = { IllegalStateException("En feil på forsøk $it") } + ) { + init { check(forsøk >= 1) } + private var nåværendeForsøk = 1 + + fun gjørtingBlocking(): String { + if (nåværendeForsøk == forsøk) return "Det gikk jo bra på førsøk $nåværendeForsøk da" + throw feil(nåværendeForsøk++) + } + suspend fun gjørting(): String { + delay(1) + return gjørtingBlocking() + } + + } + + private class EnfeilViIkkeVilRetrye(nåværendeForsøk: Int): IllegalStateException("For dette her fikses ikke av en retry. Så derfor feiler vi på forsøk $nåværendeForsøk") +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index eff3504..7343499 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,2 @@ rootProject.name = "tbd-libs" -include("azure-token-client", "azure-token-client-default", "mock-http-client", "minimal-sts-client", "minimal-soap-client", "postgres-testdatabaser") +include("azure-token-client", "azure-token-client-default", "mock-http-client", "minimal-sts-client", "minimal-soap-client", "postgres-testdatabaser", "retry")