Skip to content

Commit c735d42

Browse files
authored
fix: retry as PDML dit not retry Resource limit exceeded (googleapis#4258)
Most transactions that exceed the mutation limit for an atomic transaction will fail with the error "The transaction contains too many mutations.". However, it is also possible that the transaction fails with the more generic error message "Transaction resource limits exceeded". This error did not trigger a retry of the statement using a PDML transaction. Fixes googleapis#4253
1 parent 7b49412 commit c735d42

File tree

2 files changed

+51
-8
lines changed

2 files changed

+51
-8
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionMutationLimitExceededException.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public class TransactionMutationLimitExceededException extends SpannerException
2828

2929
private static final String ERROR_MESSAGE = "The transaction contains too many mutations.";
3030

31+
private static final String TRANSACTION_RESOURCE_LIMIT_EXCEEDED_MESSAGE =
32+
"Transaction resource limits exceeded";
33+
3134
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
3235
TransactionMutationLimitExceededException(
3336
DoNotConstructDirectly token,
@@ -40,13 +43,17 @@ public class TransactionMutationLimitExceededException extends SpannerException
4043
}
4144

4245
static boolean isTransactionMutationLimitException(ErrorCode code, String message) {
43-
return code == ErrorCode.INVALID_ARGUMENT && message != null && message.contains(ERROR_MESSAGE);
46+
return code == ErrorCode.INVALID_ARGUMENT
47+
&& message != null
48+
&& (message.contains(ERROR_MESSAGE)
49+
|| message.contains(TRANSACTION_RESOURCE_LIMIT_EXCEEDED_MESSAGE));
4450
}
4551

4652
static boolean isTransactionMutationLimitException(Throwable cause, ApiException apiException) {
4753
if (cause == null
4854
|| cause.getMessage() == null
49-
|| !cause.getMessage().contains(ERROR_MESSAGE)) {
55+
|| !(cause.getMessage().contains(ERROR_MESSAGE)
56+
|| cause.getMessage().contains(TRANSACTION_RESOURCE_LIMIT_EXCEEDED_MESSAGE))) {
5057
return false;
5158
}
5259
// Spanner includes a hint that points to the Spanner limits documentation page when the error
@@ -66,6 +73,9 @@ static boolean isTransactionMutationLimitException(Throwable cause, ApiException
6673
.getLinks(0)
6774
.getUrl()
6875
.equals("https://cloud.google.com/spanner/docs/limits");
76+
} else if (cause.getMessage().contains(TRANSACTION_RESOURCE_LIMIT_EXCEEDED_MESSAGE)) {
77+
// This more generic error does not contain any additional details.
78+
return true;
6979
}
7080
return false;
7181
}

google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RetryDmlAsPartitionedDmlMockServerTest.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,37 @@
4343
import io.grpc.StatusRuntimeException;
4444
import org.junit.Test;
4545
import org.junit.runner.RunWith;
46-
import org.junit.runners.JUnit4;
46+
import org.junit.runners.Parameterized;
47+
import org.junit.runners.Parameterized.Parameter;
48+
import org.junit.runners.Parameterized.Parameters;
4749

48-
@RunWith(JUnit4.class)
50+
@RunWith(Parameterized.class)
4951
public class RetryDmlAsPartitionedDmlMockServerTest extends AbstractMockServerTest {
52+
private enum ExceptionType {
53+
MutationLimitExceeded {
54+
@Override
55+
StatusRuntimeException createException() {
56+
return createTransactionMutationLimitExceededException();
57+
}
58+
},
59+
ResourceLimitExceeded {
60+
@Override
61+
StatusRuntimeException createException() {
62+
return createTransactionResourceLimitExceededException();
63+
}
64+
};
65+
66+
abstract StatusRuntimeException createException();
67+
}
68+
69+
@Parameters(name = "exception = {0}")
70+
public static Object[] data() {
71+
return ExceptionType.values();
72+
}
73+
74+
@SuppressWarnings("ClassEscapesDefinedScope")
75+
@Parameter
76+
public ExceptionType exceptionType;
5077

5178
static StatusRuntimeException createTransactionMutationLimitExceededException() {
5279
Metadata.Key<byte[]> key =
@@ -70,10 +97,16 @@ static StatusRuntimeException createTransactionMutationLimitExceededException()
7097
.asRuntimeException(trailers);
7198
}
7299

100+
static StatusRuntimeException createTransactionResourceLimitExceededException() {
101+
return Status.INVALID_ARGUMENT
102+
.withDescription("Transaction resource limits exceeded")
103+
.asRuntimeException();
104+
}
105+
73106
@Test
74107
public void testTransactionMutationLimitExceeded_isNotRetriedByDefault() {
75108
mockSpanner.setExecuteSqlExecutionTime(
76-
SimulatedExecutionTime.ofException(createTransactionMutationLimitExceededException()));
109+
SimulatedExecutionTime.ofException(exceptionType.createException()));
77110

78111
try (Connection connection = createConnection()) {
79112
connection.setAutocommit(true);
@@ -95,7 +128,7 @@ public void testTransactionMutationLimitExceeded_isNotRetriedByDefault() {
95128
public void testTransactionMutationLimitExceeded_canBeRetriedAsPDML() {
96129
Statement statement = Statement.of("update test set value=1 where true");
97130
mockSpanner.setExecuteSqlExecutionTime(
98-
SimulatedExecutionTime.ofException(createTransactionMutationLimitExceededException()));
131+
SimulatedExecutionTime.ofException(exceptionType.createException()));
99132
mockSpanner.putStatementResult(
100133
MockSpannerServiceImpl.StatementResult.update(statement, 100000L));
101134

@@ -134,7 +167,7 @@ public void testTransactionMutationLimitExceeded_retryAsPDMLFails() {
134167
Statement statement = Statement.of("insert into test (id, value) select -id, value from test");
135168
// The transactional update statement uses ExecuteSql(..).
136169
mockSpanner.setExecuteSqlExecutionTime(
137-
SimulatedExecutionTime.ofException(createTransactionMutationLimitExceededException()));
170+
SimulatedExecutionTime.ofException(exceptionType.createException()));
138171
mockSpanner.putStatementResult(
139172
MockSpannerServiceImpl.StatementResult.exception(
140173
statement,
@@ -230,7 +263,7 @@ public void testTransactionMutationLimitExceeded_isWrappedAsCauseOfBatchUpdateEx
230263
Statement statement = Statement.of(sql);
231264
mockSpanner.putStatementResult(
232265
MockSpannerServiceImpl.StatementResult.exception(
233-
statement, createTransactionMutationLimitExceededException()));
266+
statement, exceptionType.createException()));
234267

235268
try (Connection connection = createConnection()) {
236269
connection.setAutocommit(true);

0 commit comments

Comments
 (0)