Skip to content

Commit b5afda5

Browse files
committed
WIP WIP WIP Handle indeterminate YDB request state
1 parent 3a21fb8 commit b5afda5

33 files changed

+750
-256
lines changed

aspect/src/main/java/tech/ydb/yoj/aspect/tx/YojTransactionAspect.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.aspectj.lang.annotation.Aspect;
77
import tech.ydb.yoj.repository.db.Tx;
88
import tech.ydb.yoj.repository.db.TxManager;
9+
import tech.ydb.yoj.repository.db.exception.ConditionallyRetryableException;
910
import tech.ydb.yoj.repository.db.exception.RetryableException;
1011

1112
/**
@@ -68,7 +69,7 @@ private Object doInTransaction(ProceedingJoinPoint pjp, YojTransactional transac
6869

6970
return localTx.tx(() -> safeCall(pjp));
7071
}
71-
} catch (CallRetryableException | CallException e) {
72+
} catch (CallRetryableException | CallConditionallyRetryableException | CallException e) {
7273
throw e.getCause();
7374
}
7475
}
@@ -88,17 +89,28 @@ Object safeCall(ProceedingJoinPoint pjp) {
8889
return pjp.proceed();
8990
} catch (RetryableException e) {
9091
throw new CallRetryableException(e);
92+
} catch (ConditionallyRetryableException e) {
93+
throw new CallConditionallyRetryableException(e);
9194
} catch (Throwable e) {
9295
throw new CallException(e);
9396
}
9497
}
9598

9699
/**
97-
* It's a hint for tx manager to retry was requested
100+
* It's a hint for tx manager that an unconditional retry was requested
98101
*/
99102
static class CallRetryableException extends RetryableException {
100103
CallRetryableException(RetryableException e) {
101-
super(e.getMessage(), e.getCause());
104+
super(e.getMessage(), e.getRetryPolicy(), e.getCause());
105+
}
106+
}
107+
108+
/**
109+
* It's a hint for tx manager that a conditional retry was requested
110+
*/
111+
static class CallConditionallyRetryableException extends ConditionallyRetryableException {
112+
CallConditionallyRetryableException(ConditionallyRetryableException e) {
113+
super(e.getMessage(), e.getRetryPolicy(), e.getCause());
102114
}
103115
}
104116

repository-inmemory/src/main/java/tech/ydb/yoj/repository/test/inmemory/InMemoryRepositoryTransaction.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package tech.ydb.yoj.repository.test.inmemory;
22

3+
import com.google.common.base.Preconditions;
34
import com.google.common.base.Stopwatch;
45
import com.google.common.collect.Iterables;
56
import lombok.Getter;
@@ -23,7 +24,10 @@
2324
import java.util.function.Supplier;
2425

2526
public class InMemoryRepositoryTransaction implements BaseDb, RepositoryTransaction {
26-
private final static AtomicLong txIdGenerator = new AtomicLong();
27+
private static final String CLOSE_ACTION_COMMIT = "commit()";
28+
private static final String CLOSE_ACTION_ROLLBACK = "rollback()";
29+
30+
private static final AtomicLong txIdGenerator = new AtomicLong();
2731

2832
private final long txId = txIdGenerator.incrementAndGet();
2933
private final Stopwatch txStopwatch = Stopwatch.createStarted();
@@ -81,7 +85,12 @@ public void commit() {
8185
if (isBadSession) {
8286
throw new IllegalStateException("Transaction was invalidated. Commit isn't possible");
8387
}
84-
endTransaction("commit()", this::commitImpl);
88+
endTransaction(CLOSE_ACTION_COMMIT, this::commitImpl);
89+
}
90+
91+
@Override
92+
public boolean wasCommitAttempted() {
93+
return CLOSE_ACTION_COMMIT.equals(closeAction);
8594
}
8695

8796
private void commitImpl() {
@@ -101,14 +110,15 @@ private void commitImpl() {
101110

102111
@Override
103112
public void rollback() {
104-
endTransaction("rollback()", this::rollbackImpl);
113+
endTransaction(CLOSE_ACTION_ROLLBACK, this::rollbackImpl);
105114
}
106115

107116
private void rollbackImpl() {
108117
storage.rollback(txId);
109118
}
110119

111120
private void endTransaction(String action, Runnable runnable) {
121+
ensureTransactionActive();
112122
try {
113123
if (isFinalActionNeeded(action)) {
114124
logTransaction(action, runnable);
@@ -134,6 +144,7 @@ private boolean isFinalActionNeeded(String action) {
134144
final <T extends Entity<T>> void doInWriteTransaction(
135145
String log, TableDescriptor<T> tableDescriptor, Consumer<WriteTxDataShard<T>> consumer
136146
) {
147+
ensureTransactionActive();
137148
if (options.isScan()) {
138149
throw new IllegalTransactionScanException("Mutable operations");
139150
}
@@ -158,6 +169,7 @@ final <T extends Entity<T>> void doInWriteTransaction(
158169
final <T extends Entity<T>, R> R doInTransaction(
159170
String action, TableDescriptor<T> tableDescriptor, Function<ReadOnlyTxDataShard<T>, R> func
160171
) {
172+
ensureTransactionActive();
161173
return logTransaction(action, () -> {
162174
InMemoryTxLockWatcher findWatcher = hasWrites ? watcher : InMemoryTxLockWatcher.NO_LOCKS;
163175
ReadOnlyTxDataShard<T> shard = storage.getReadOnlyTxDataShard(
@@ -180,10 +192,6 @@ private void logTransaction(String action, Runnable runnable) {
180192
}
181193

182194
private <R> R logTransaction(String action, Supplier<R> supplier) {
183-
if (closeAction != null) {
184-
throw new IllegalStateException("Transaction already closed by " + closeAction);
185-
}
186-
187195
Stopwatch sw = Stopwatch.createStarted();
188196
try {
189197
R result = supplier.get();
@@ -195,6 +203,10 @@ private <R> R logTransaction(String action, Supplier<R> supplier) {
195203
}
196204
}
197205

206+
private void ensureTransactionActive() {
207+
Preconditions.checkState(closeAction == null, "Transaction already closed by %s", closeAction);
208+
}
209+
198210
private String printResult(Object result) {
199211
if (result instanceof Iterable<?>) {
200212
long size = Iterables.size((Iterable<?>) result);
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package tech.ydb.yoj.repository.ydb.exception;
22

3-
import tech.ydb.yoj.repository.db.exception.RetryableException;
3+
import tech.ydb.yoj.util.retry.RetryPolicy;
44

55
/**
66
* Tried to use a no longer active or valid YDB session, e.g. on a node that is now down.
77
*/
8-
public class BadSessionException extends RetryableException {
9-
public BadSessionException(String message) {
10-
super(message);
8+
public class BadSessionException extends YdbUnconditionallyRetryableException {
9+
public BadSessionException(Enum<?> statusCode, Object request, Object response) {
10+
super("Bad session", statusCode, request, response, RetryPolicy.retryImmediately());
1111
}
1212
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package tech.ydb.yoj.repository.ydb.exception;
22

33
public final class UnexpectedException extends YdbRepositoryException {
4+
public UnexpectedException(Enum<?> statusCode, Object request, Object response) {
5+
super("Unknown YDB status: " + statusCode, statusCode, request, response);
6+
}
7+
48
public UnexpectedException(String message) {
5-
super(message);
9+
this(message, null);
610
}
711

812
public UnexpectedException(String message, Throwable cause) {
9-
super(message, cause);
13+
super(message, null, null, null, cause);
1014
}
1115
}

repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbClientInternalException.java

Lines changed: 0 additions & 23 deletions
This file was deleted.

repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbComponentUnavailableException.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
package tech.ydb.yoj.repository.ydb.exception;
22

33
import tech.ydb.yoj.repository.db.exception.RepositoryException;
4-
import tech.ydb.yoj.repository.db.exception.RetryableException;
54
import tech.ydb.yoj.repository.db.exception.UnavailableException;
6-
import tech.ydb.yoj.util.lang.Strings;
75
import tech.ydb.yoj.util.retry.RetryPolicy;
86

97
/**
108
* One or more YDB components are not available, but the YDB API was still able to respond.
119
*/
12-
public final class YdbComponentUnavailableException extends RetryableException {
10+
public final class YdbComponentUnavailableException extends YdbUnconditionallyRetryableException {
1311
private static final RetryPolicy UNAVAILABLE_RETRY_POLICY = RetryPolicy.fixed(100L, 0.2);
1412

15-
public YdbComponentUnavailableException(Object request, Object response) {
16-
super(Strings.join("\n", request, response), UNAVAILABLE_RETRY_POLICY);
17-
}
18-
19-
public YdbComponentUnavailableException(String message, Throwable t) {
20-
super(message, UNAVAILABLE_RETRY_POLICY, t);
13+
public YdbComponentUnavailableException(Enum<?> statusCode, Object request, Object response) {
14+
super("Some database components are not available, but we still got a reply from the DB",
15+
statusCode, request, response, UNAVAILABLE_RETRY_POLICY);
2116
}
2217

2318
@Override
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package tech.ydb.yoj.repository.ydb.exception;
2+
3+
import tech.ydb.yoj.ExperimentalApi;
4+
import tech.ydb.yoj.repository.db.exception.ConditionallyRetryableException;
5+
import tech.ydb.yoj.util.lang.Strings;
6+
import tech.ydb.yoj.util.retry.RetryPolicy;
7+
8+
import javax.annotation.Nullable;
9+
10+
/**
11+
* A <em>conditionally-retryable</em> exception from the YDB database, the YDB Java SDK, or the GRPC client used
12+
* by the YDB Java SDK.
13+
*
14+
* @see ConditionallyRetryableException Conditionally-retryable exceptions
15+
*/
16+
@ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/issues/165")
17+
public class YdbConditionallyRetryableException extends ConditionallyRetryableException {
18+
private static final RetryPolicy UNDETERMINED_BACKOFF = RetryPolicy.expBackoff(5L, 500L, 0.1, 2.0);
19+
20+
private final Enum<?> statusCode;
21+
22+
public YdbConditionallyRetryableException(Enum<?> statusCode, Object request, Object response) {
23+
this("Indeterminate request state: it's not known if the request succeeded or failed",
24+
statusCode, request, response, UNDETERMINED_BACKOFF);
25+
}
26+
27+
public YdbConditionallyRetryableException(String message, Enum<?> statusCode, Object request, Object response, RetryPolicy retryPolicy) {
28+
super(Strings.join("\n", Strings.join(": ", message, statusCode), request, response), retryPolicy);
29+
this.statusCode = statusCode;
30+
}
31+
32+
@Nullable
33+
public Enum<?> getStatusCode() {
34+
return statusCode;
35+
}
36+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package tech.ydb.yoj.repository.ydb.exception;
2+
3+
/**
4+
* Represents a non-retryable YDB error.
5+
*/
6+
public final class YdbInternalException extends YdbRepositoryException {
7+
public YdbInternalException(Enum<?> statusCode, Object request, Object response) {
8+
super("Bad YDB response status", statusCode, request, response);
9+
}
10+
}

repository-ydb-common/src/main/java/tech/ydb/yoj/repository/ydb/exception/YdbOverloadedException.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
package tech.ydb.yoj.repository.ydb.exception;
22

33
import tech.ydb.yoj.repository.db.exception.RepositoryException;
4-
import tech.ydb.yoj.repository.db.exception.RetryableException;
54
import tech.ydb.yoj.repository.db.exception.UnavailableException;
6-
import tech.ydb.yoj.util.lang.Strings;
75
import tech.ydb.yoj.util.retry.RetryPolicy;
86

97
/**
108
* YDB node is overloaded, but the YDB API was still able to respond.
119
*/
12-
public final class YdbOverloadedException extends RetryableException {
10+
public final class YdbOverloadedException extends YdbUnconditionallyRetryableException {
1311
private static final RetryPolicy OVERLOADED_BACKOFF = RetryPolicy.expBackoff(100L, 1_000L, 0.1, 2.0);
1412

15-
public YdbOverloadedException(Object request, Object response) {
16-
super(Strings.join("\n", request, response), OVERLOADED_BACKOFF);
13+
public YdbOverloadedException(Enum<?> statusCode, Object request, Object response) {
14+
super("Database overloaded", statusCode, request, response, OVERLOADED_BACKOFF);
1715
}
1816

1917
@Override
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package tech.ydb.yoj.repository.ydb.exception;
2+
3+
/**
4+
* Query precondition failed.
5+
*/
6+
public final class YdbPreconditionFailedException extends YdbRepositoryException {
7+
public YdbPreconditionFailedException(Object request, Object response) {
8+
super("Query precondition failed", request, response);
9+
}
10+
}

0 commit comments

Comments
 (0)