diff --git a/libs/commons/build.gradle.kts b/libs/commons/build.gradle.kts index ef67056a62..0b8b278d19 100644 --- a/libs/commons/build.gradle.kts +++ b/libs/commons/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation(libs.bundles.telemetry) implementation(libs.flipt) + implementation(libs.openfeign) testImplementation("org.springframework.boot:spring-boot-starter-data-ldap") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/retry/Retry.kt b/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/retry/Retry.kt index cf38551e1c..03e74eb34f 100644 --- a/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/retry/Retry.kt +++ b/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/retry/Retry.kt @@ -1,13 +1,24 @@ package uk.gov.justice.digital.hmpps.retry -fun retry(maxRetries: Int, code: () -> T): T { - var throwable: Throwable? = null - (1..maxRetries).forEach { _ -> +import kotlin.reflect.KClass + +fun retry(maxRetries: Int, exceptions: List> = listOf(Exception::class), code: () -> T): T { + var throwable: Throwable? + (1..maxRetries).forEach { count -> try { return code() } catch (e: Throwable) { - throwable = e + val matchedException = exceptions.firstOrNull { it.isInstance(e) } + throwable = if (matchedException != null && count < maxRetries) { + null + } else { + e + } + + if (throwable != null) { + throw throwable!! + } } } - throw throwable!! + throw RuntimeException("unknown error") } diff --git a/libs/commons/src/test/kotlin/uk/gov/justice/digital/hmpps/retry/RetryTest.kt b/libs/commons/src/test/kotlin/uk/gov/justice/digital/hmpps/retry/RetryTest.kt index ed0455696c..68b7da7701 100644 --- a/libs/commons/src/test/kotlin/uk/gov/justice/digital/hmpps/retry/RetryTest.kt +++ b/libs/commons/src/test/kotlin/uk/gov/justice/digital/hmpps/retry/RetryTest.kt @@ -2,9 +2,12 @@ package uk.gov.justice.digital.hmpps.retry import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo +import org.json.JSONException import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.springframework.dao.OptimisticLockingFailureException import java.rmi.UnexpectedException +import java.sql.SQLException import java.util.concurrent.atomic.AtomicInteger internal class RetryTest { @@ -29,4 +32,29 @@ internal class RetryTest { } assertThat(result, equalTo(1)) } + + @Test + fun `when optimistic lock exception thrown retry until max retries`() { + val counter = AtomicInteger(0) + assertThrows { + retry(3, listOf(OptimisticLockingFailureException::class, JSONException::class)) { + counter.incrementAndGet() + throw OptimisticLockingFailureException("OLE") + } + } + assertThat(counter.get(), equalTo(3)) + } + + @Test + fun `when SQL exception thrown no retries`() { + val counter = AtomicInteger(0) + assertThrows { + retry(3, listOf(OptimisticLockingFailureException::class)) { + counter.incrementAndGet() + throw SQLException("SQLE") + } + } + assertThat(counter.get(), equalTo(1)) + } } + diff --git a/libs/messaging/src/main/kotlin/uk/gov/justice/digital/hmpps/listener/AwsNotificationListener.kt b/libs/messaging/src/main/kotlin/uk/gov/justice/digital/hmpps/listener/AwsNotificationListener.kt index d1e739cd3b..d0640b98f9 100644 --- a/libs/messaging/src/main/kotlin/uk/gov/justice/digital/hmpps/listener/AwsNotificationListener.kt +++ b/libs/messaging/src/main/kotlin/uk/gov/justice/digital/hmpps/listener/AwsNotificationListener.kt @@ -12,6 +12,7 @@ import org.springframework.context.annotation.Conditional import org.springframework.stereotype.Component import uk.gov.justice.digital.hmpps.config.AwsCondition import uk.gov.justice.digital.hmpps.messaging.NotificationHandler +import uk.gov.justice.digital.hmpps.retry.retry import java.util.concurrent.CompletionException @Component @@ -25,7 +26,12 @@ class AwsNotificationListener( @WithSpan(kind = SpanKind.CONSUMER) fun receive(message: String) { try { - handler.handle(message) + retry(3,listOf(ObjectOptimisticLockingFailureException::class, + ConstraintViolationException::class, + RetryableException::class, + CannotCreateTransactionException::class, + FeignException.NotFound::class, + CannotGetJdbcConnectionException::class,)) { handler.handle(message) } } catch (e: Throwable) { Sentry.captureException(unwrapSqsExceptions(e)) throw e