/
githubmirror
/
ydb-java-dialects
Обзор
Документация
Войти
/
githubmirror
/
ydb-java-dialects
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
spring-ydb-retry/src/test/java/tech/ydb/retry/YdbTransactionalConfigOverrideTest.java
390 строк
16 KB
Kirill Kurdyukov
Add spring-ydb-retry module (v0.10.0) (#226)
15 июн 2026, 14:09
Не верифицирован
15 июн 2026, 14:09
3e8a6c8
Код
Авторство
О чём код?
package tech.ydb.retry; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static tech.ydb.core.StatusCode.ABORTED; import static tech.ydb.core.StatusCode.BAD_SESSION; import static tech.ydb.core.StatusCode.CLIENT_CANCELLED; import static tech.ydb.core.StatusCode.CLIENT_INTERNAL_ERROR; import static tech.ydb.core.StatusCode.CLIENT_RESOURCE_EXHAUSTED; import static tech.ydb.core.StatusCode.OVERLOADED; import static tech.ydb.core.StatusCode.SESSION_BUSY; import static tech.ydb.core.StatusCode.SESSION_EXPIRED; import static tech.ydb.core.StatusCode.TIMEOUT; import static tech.ydb.core.StatusCode.TRANSPORT_UNAVAILABLE; import static tech.ydb.core.StatusCode.UNDETERMINED; class YdbTransactionalConfigOverrideTest extends InterceptorTestSupport { @Test void shouldOverrideMaxRetriesFromAnnotation() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 2, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(ABORTED), "ok"); Object result = interceptor.invoke(invocationFor("ydbCustomRetry")); assertEquals("ok", result); assertEquals(1, interceptor.retries()); assertEquals(2, interceptor.allInvocations()); } @Test void shouldUseConfigMaxRetriesWhenAnnotationNotSet() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 3, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(BAD_SESSION), "ok"); Object result = interceptor.invoke(invocationFor("defaultRetry")); assertEquals("ok", result); assertEquals(1, interceptor.retries()); assertEquals(2, interceptor.allInvocations()); } @Test void shouldExhaustAnnotatedMaxRetriesAndPropagate() { TestableInterceptor interceptor = interceptorWithConfig(true, 2, 0, 0, 0, 0); interceptor.enqueueOutcome( new ConfigurableStatusException(SESSION_BUSY), new ConfigurableStatusException(OVERLOADED), new ConfigurableStatusException(OVERLOADED)); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbCustomRetry"))); assertEquals(OVERLOADED, exception.statusCode()); assertEquals(2, interceptor.retries()); assertEquals(3, interceptor.allInvocations()); } @Test void shouldUseAnnotatedMaxRetriesWhenLowerThanConfig() { TestableInterceptor interceptor = interceptorWithConfig(true, 2, 0, 0, 0, 0); interceptor.enqueueOutcome( new ConfigurableStatusException(OVERLOADED), new ConfigurableStatusException(BAD_SESSION), new ConfigurableStatusException(OVERLOADED)); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbCustomRetry"))); assertEquals(OVERLOADED, exception.statusCode()); assertEquals(2, interceptor.retries()); assertEquals(3, interceptor.allInvocations()); } @Test void shouldUseAnnotatedMaxRetriesWhenHigherThanConfig() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 2, 0, 0, 0, 0); interceptor.enqueueOutcome( new ConfigurableStatusException(BAD_SESSION), new ConfigurableStatusException(SESSION_BUSY), new ConfigurableStatusException(ABORTED), new ConfigurableStatusException(OVERLOADED), "ok"); Object result = interceptor.invoke(invocationFor("ydbRequiredRetry")); assertEquals("ok", result); assertEquals(5, interceptor.allInvocations()); } @Test void shouldRetryDifferentStatusCodesAcrossRetries() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 2, 0, 0, 0, 0); interceptor.enqueueOutcome( new ConfigurableStatusException(ABORTED), new ConfigurableStatusException(BAD_SESSION), "ok"); Object result = interceptor.invoke(invocationFor("ydbRequiredRetry")); assertEquals("ok", result); assertEquals(3, interceptor.allInvocations()); } @Test void shouldNotRetryClientCancelledWhenNotIdempotent() { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(CLIENT_CANCELLED), "ok"); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbNonIdempotentRetry"))); assertEquals(CLIENT_CANCELLED, exception.statusCode()); assertEquals(1, interceptor.allInvocations()); } @Test void shouldNotRetryClientCancelledEvenWhenIdempotent() { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(CLIENT_CANCELLED), "ok"); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbIdempotentRetry"))); assertEquals(CLIENT_CANCELLED, exception.statusCode()); assertEquals(1, interceptor.allInvocations()); } @Test void shouldNotRetryClientInternalErrorEvenWhenIdempotent() { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(CLIENT_INTERNAL_ERROR), "ok"); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbIdempotentRetry"))); assertEquals(CLIENT_INTERNAL_ERROR, exception.statusCode()); assertEquals(1, interceptor.allInvocations()); } @Test void shouldUseInterfaceMethodYdbTransactionalOverrides() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 2, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(TRANSPORT_UNAVAILABLE), "ok"); Object result = interceptor.invoke(invocationFor( InterfaceAnnotatedService.class.getMethod("interfaceAnnotatedIdempotentRetry"), new InterfaceAnnotatedServiceImpl())); assertEquals("ok", result); assertEquals(2, interceptor.allInvocations()); } @Test void shouldNotRetryTransportUnavailableWhenNotIdempotent() { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(TRANSPORT_UNAVAILABLE), "ok"); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbNonIdempotentRetry"))); assertEquals(TRANSPORT_UNAVAILABLE, exception.statusCode()); assertEquals(1, interceptor.allInvocations()); } @Test void shouldRetryTransportUnavailableWhenIdempotent() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(TRANSPORT_UNAVAILABLE), "ok"); Object result = interceptor.invoke(invocationFor("ydbIdempotentRetry")); assertEquals("ok", result); assertEquals(2, interceptor.allInvocations()); } @Test void shouldRetryClientResourceExhaustedWhenNotIdempotent() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(CLIENT_RESOURCE_EXHAUSTED), "ok"); Object result = interceptor.invoke(invocationFor("ydbNonIdempotentRetry")); assertEquals("ok", result); assertEquals(2, interceptor.allInvocations()); } @Test void shouldRetryClientResourceExhaustedWhenIdempotent() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(CLIENT_RESOURCE_EXHAUSTED), "ok"); Object result = interceptor.invoke(invocationFor("ydbIdempotentRetry")); assertEquals("ok", result); assertEquals(2, interceptor.allInvocations()); } @Test void shouldNotRetryTimeoutWhenIdempotent() { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(TIMEOUT)); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbIdempotentRetry"))); assertEquals(TIMEOUT, exception.statusCode()); assertEquals(1, interceptor.allInvocations()); } @Test void shouldNotRetrySessionExpiredWhenNotIdempotent() { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(SESSION_EXPIRED)); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbNonIdempotentRetry"))); assertEquals(SESSION_EXPIRED, exception.statusCode()); assertEquals(1, interceptor.allInvocations()); } @Test void shouldRetryAlwaysRetryableCodesWhenIdempotent() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(ABORTED), "ok"); Object result = interceptor.invoke(invocationFor("ydbIdempotentRetry")); assertEquals("ok", result); assertEquals(2, interceptor.allInvocations()); } @Test void shouldRetryMixedStatusCodesWhenIdempotent() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome( new ConfigurableStatusException(ABORTED), new ConfigurableStatusException(UNDETERMINED), new ConfigurableStatusException(TRANSPORT_UNAVAILABLE), "ok"); Object result = interceptor.invoke(invocationFor("ydbIdempotentRetry")); assertEquals("ok", result); assertEquals(4, interceptor.allInvocations()); } @Test void shouldRetrySessionExpiredWithZeroDelayWhenIdempotent() throws Throwable { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(SESSION_EXPIRED), "ok"); Object result = interceptor.invoke(invocationFor("ydbIdempotentRetry")); assertEquals("ok", result); assertEquals(2, interceptor.allInvocations()); } @Test void shouldStopAtIdempotentOnlyCodeWhenNotIdempotent() { TestableInterceptor interceptor = interceptorWithConfig(true, 6, 0, 0, 0, 0); interceptor.enqueueOutcome( new ConfigurableStatusException(BAD_SESSION), new ConfigurableStatusException(TIMEOUT)); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbNonIdempotentRetry"))); assertEquals(TIMEOUT, exception.statusCode()); assertEquals(2, interceptor.allInvocations()); } @Test void shouldNotReachDelayCalculatorForTimeoutWhenIdempotent() { List<Long> delays = new ArrayList<>(); TestableInterceptor interceptor = interceptorWithSleeper(true, 6, 100, 50, 1000, 500, delays::add); interceptor.enqueueOutcome(new ConfigurableStatusException(TIMEOUT)); assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbIdempotentRetry"))); assertEquals(1, interceptor.allInvocations()); assertEquals(0, delays.size()); } @Test void shouldUseZeroDelayForSessionExpiredWhenIdempotent() throws Throwable { List<Long> delays = new ArrayList<>(); TestableInterceptor interceptor = interceptorWithSleeper(true, 6, 100, 50, 1000, 500, delays::add); interceptor.enqueueOutcome(new ConfigurableStatusException(SESSION_EXPIRED), "ok"); Object result = interceptor.invoke(invocationFor("ydbIdempotentRetry")); assertEquals("ok", result); assertEquals(2, interceptor.allInvocations()); assertEquals(List.of(0L), delays); } @Test void shouldUseFastBackoffForUndeterminedWhenIdempotent() throws Throwable { List<Long> delays = new ArrayList<>(); TestableInterceptor interceptor = interceptorWithSleeper(true, 6, 100, 50, 1000, 500, delays::add); interceptor.enqueueOutcome(new ConfigurableStatusException(UNDETERMINED), "ok"); interceptor.invoke(invocationFor("ydbIdempotentRetry")); assertEquals(1, delays.size()); assertTrue(delays.get(0) >= 0); } @Test void shouldDelayFirstOverloadedRetryUsingZeroBasedRetryIndex() throws Throwable { List<Long> delays = new ArrayList<>(); TestableInterceptor interceptor = interceptorWithSleeper(true, 6, 1, 1, 1, 1, delays::add); interceptor.enqueueOutcome(new ConfigurableStatusException(OVERLOADED), "ok"); Object result = interceptor.invoke(invocationFor("ydbCustomRetry")); assertEquals("ok", result); assertEquals(2, interceptor.allInvocations()); assertEquals(List.of(1L), delays); } @Test void shouldNotRetryWhenMethodDisablesRetry() { TestableInterceptor interceptor = interceptorWithConfig(true, 4, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(BAD_SESSION), "ok"); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbRetryDisabled"))); assertEquals(BAD_SESSION, exception.statusCode()); assertEquals(1, interceptor.allInvocations()); } @Test void shouldNotRetryWhenGlobalConfigDisablesRetryEvenIfMethodEnablesIt() { TestableInterceptor interceptor = interceptorWithConfig(false, 4, 0, 0, 0, 0); interceptor.enqueueOutcome(new ConfigurableStatusException(BAD_SESSION), "ok"); ConfigurableStatusException exception = assertThrows( ConfigurableStatusException.class, () -> interceptor.invoke(invocationFor("ydbRetryEnabled"))); assertEquals(BAD_SESSION, exception.statusCode()); assertEquals(1, interceptor.allInvocations()); } interface InterfaceAnnotatedService { @YdbTransactional(maxAttempts = 3, idempotent = true) String interfaceAnnotatedIdempotentRetry(); } static final class InterfaceAnnotatedServiceImpl implements InterfaceAnnotatedService { @Override public String interfaceAnnotatedIdempotentRetry() { return "ok"; } } }