use of com.google.cloud.spanner.AbortedDueToConcurrentModificationException in project java-spanner by googleapis.
the class ITTransactionRetryTest method testAbortWithExceptionOnSelectAndConcurrentModification.
/**
* Test that shows the following:
*
* <ol>
* <li>Insert two records into table TEST and commit.
* <li>Try to query the non-existing table FOO. This will lead to an exception.
* <li>Query all the records from the TEST table and consume the result set.
* <li>Open another connection and create the table FOO.
* <li>Insert another record into TEST that aborts.
* <li>The transaction is internally retried. The retry fails as the SELECT statement on FOO
* will now succeed.
* </ol>
*/
@Test
public void testAbortWithExceptionOnSelectAndConcurrentModification() {
assumeFalse("concurrent transactions are not supported on the emulator", isUsingEmulator());
boolean abortedDueToConcurrentModification = false;
AbortInterceptor interceptor = new AbortInterceptor(0);
// first insert two test records
try (ITConnection connection = createConnection()) {
connection.executeUpdate(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test 1')"));
connection.executeUpdate(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (2, 'test 2')"));
connection.commit();
}
try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) {
// do a select that will fail
boolean expectedException = false;
try (ResultSet rs = connection.executeQuery(Statement.of("SELECT * FROM FOO"))) {
while (rs.next()) {
// do nothing
}
} catch (SpannerException e) {
// expected
expectedException = true;
}
assertThat(expectedException, is(true));
// do a select that will succeed
try (ResultSet rs = connection.executeQuery(Statement.of("SELECT * FROM TEST"))) {
while (rs.next()) {
// do nothing
}
}
// CREATE FOO
try (ITConnection connection2 = createConnection()) {
connection2.setAutocommit(true);
connection2.execute(Statement.of("CREATE TABLE FOO (ID INT64, NAME STRING(100)) PRIMARY KEY (ID)"));
}
// Now try to do an insert that will abort. The subsequent retry will fail as the SELECT *
// FROM FOO now returns a result.
interceptor.setProbability(1.0);
interceptor.setOnlyInjectOnce(true);
try {
connection.executeUpdate(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (3, 'test 3')"));
} catch (AbortedDueToConcurrentModificationException e) {
abortedDueToConcurrentModification = true;
}
}
// DROP FOO regardless of the result to avoid any interference with other test cases
try (ITConnection connection2 = createConnection()) {
connection2.setAutocommit(true);
connection2.execute(Statement.of("DROP TABLE FOO"));
}
assertThat(abortedDueToConcurrentModification, is(true));
assertRetryStatistics(1, 1, 0);
}
use of com.google.cloud.spanner.AbortedDueToConcurrentModificationException in project java-spanner by googleapis.
the class ITTransactionRetryTest method testAbortWithExceptionOnInsertAndConcurrentModification.
/**
* Test that shows the following:
*
* <ol>
* <li>Insert two records into table TEST and commit.
* <li>Try to insert a record in the non-existing table FOO. This will lead to an exception.
* <li>Query all the records from the TEST table and consume the result set.
* <li>Open another connection and create the table FOO.
* <li>Insert another record into TEST that aborts.
* <li>The transaction is internally retried. The retry fails as the insert statement on FOO
* will now succeed.
* </ol>
*/
@Test
public void testAbortWithExceptionOnInsertAndConcurrentModification() {
assumeFalse("concurrent transactions are not supported on the emulator", isUsingEmulator());
boolean abortedDueToConcurrentModification = false;
AbortInterceptor interceptor = new AbortInterceptor(0);
// first insert two test records
try (ITConnection connection = createConnection()) {
connection.executeUpdate(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test 1')"));
connection.executeUpdate(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (2, 'test 2')"));
connection.commit();
}
try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) {
// do an insert that will fail
boolean expectedException = false;
try {
connection.executeUpdate(Statement.of("INSERT INTO FOO (ID, NAME) VALUES (1, 'test 1')"));
} catch (SpannerException e) {
// expected
expectedException = true;
}
assertThat(expectedException, is(true));
// do a select that will succeed
try (ResultSet rs = connection.executeQuery(Statement.of("SELECT * FROM TEST"))) {
while (rs.next()) {
// do nothing
}
}
// CREATE FOO
try (ITConnection connection2 = createConnection()) {
connection2.setAutocommit(true);
connection2.execute(Statement.of("CREATE TABLE FOO (ID INT64, NAME STRING(100)) PRIMARY KEY (ID)"));
}
// Now try to do an insert that will abort. The subsequent retry will fail as the INSERT INTO
// FOO now succeeds.
interceptor.setProbability(1.0);
interceptor.setOnlyInjectOnce(true);
try {
connection.executeUpdate(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (3, 'test 3')"));
} catch (AbortedDueToConcurrentModificationException e) {
abortedDueToConcurrentModification = true;
}
}
// DROP FOO regardless of the result to avoid any interference with other test cases
try (ITConnection connection2 = createConnection()) {
connection2.setAutocommit(true);
connection2.execute(Statement.of("DROP TABLE FOO"));
}
assertThat(abortedDueToConcurrentModification, is(true));
assertRetryStatistics(1, 1, 0);
}
use of com.google.cloud.spanner.AbortedDueToConcurrentModificationException in project java-spanner by googleapis.
the class ConnectionAsyncApiAbortedTest method testUpdateAndQueryAbortedMidway_UpdateCountChanged.
@Test
public void testUpdateAndQueryAbortedMidway_UpdateCountChanged() throws InterruptedException {
mockSpanner.setExecuteStreamingSqlExecutionTime(SimulatedExecutionTime.ofStreamException(mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")), RANDOM_RESULT_SET_ROW_COUNT / 2));
final RetryCounter counter = new RetryCounter();
try (Connection connection = createConnection(counter)) {
assertThat(counter.retryCount).isEqualTo(0);
final CountDownLatch updateLatch = new CountDownLatch(1);
final CountDownLatch queryLatch = new CountDownLatch(1);
ApiFuture<Void> finished;
try (AsyncResultSet rs = connection.executeQueryAsync(SELECT_RANDOM_STATEMENT, Options.bufferRows(RANDOM_RESULT_SET_ROW_COUNT / 2 - 1))) {
finished = rs.setCallback(singleThreadedExecutor, resultSet -> {
// Indicate that the query has been executed.
queryLatch.countDown();
try {
// Wait until the update is on its way.
updateLatch.await(10L, TimeUnit.SECONDS);
while (true) {
switch(resultSet.tryNext()) {
case OK:
break;
case DONE:
return CallbackResponse.DONE;
case NOT_READY:
return CallbackResponse.CONTINUE;
}
}
} catch (InterruptedException e) {
throw SpannerExceptionFactory.propagateInterrupt(e);
}
});
}
// Wait until the query has actually executed.
queryLatch.await(10L, TimeUnit.SECONDS);
// Execute an update statement and wait until it has finished before allowing the
// AsyncResultSet to continue processing. Also change the result of the update statement after
// it has finished. The AsyncResultSet will see an aborted transaction halfway, and then
// during the retry, it will get a different result for this update statement. That will cause
// the retry to be aborted.
get(connection.executeUpdateAsync(INSERT_STATEMENT));
try {
mockSpanner.putStatementResult(StatementResult.update(INSERT_STATEMENT, UPDATE_COUNT + 1));
updateLatch.countDown();
get(finished);
fail("Missing expected exception");
} catch (AbortedDueToConcurrentModificationException e) {
assertThat(counter.retryCount).isEqualTo(1);
} finally {
mockSpanner.putStatementResult(StatementResult.update(INSERT_STATEMENT, UPDATE_COUNT));
}
// Verify the order of the statements on the server.
List<? extends AbstractMessage> requests = Lists.newArrayList(Collections2.filter(mockSpanner.getRequests(), input -> input instanceof ExecuteSqlRequest));
// The entire transaction should be retried, but will not succeed as the result of the update
// statement was different during the retry.
assertThat(requests).hasSize(4);
assertThat(((ExecuteSqlRequest) requests.get(0)).getSeqno()).isEqualTo(1L);
assertThat(((ExecuteSqlRequest) requests.get(0)).getSql()).isEqualTo(SELECT_RANDOM_STATEMENT.getSql());
assertThat(((ExecuteSqlRequest) requests.get(1)).getSeqno()).isEqualTo(2L);
assertThat(((ExecuteSqlRequest) requests.get(1)).getSql()).isEqualTo(INSERT_STATEMENT.getSql());
assertThat(((ExecuteSqlRequest) requests.get(2)).getSeqno()).isEqualTo(1L);
assertThat(((ExecuteSqlRequest) requests.get(2)).getSql()).isEqualTo(SELECT_RANDOM_STATEMENT.getSql());
assertThat(((ExecuteSqlRequest) requests.get(3)).getSeqno()).isEqualTo(2L);
assertThat(((ExecuteSqlRequest) requests.get(3)).getSql()).isEqualTo(INSERT_STATEMENT.getSql());
}
}
use of com.google.cloud.spanner.AbortedDueToConcurrentModificationException in project java-spanner by googleapis.
the class ConnectionAsyncApiAbortedTest method testBlindUpdateAborted_SelectResults.
@Test
public void testBlindUpdateAborted_SelectResults() {
final Statement update1 = Statement.of("UPDATE FOO SET BAR=1 WHERE BAZ=100");
mockSpanner.putStatementResult(StatementResult.update(update1, 100));
RetryCounter counter = new RetryCounter();
try (Connection connection = createConnection(counter)) {
// Execute an update statement and then change the result for the next time it is executed.
connection.executeUpdate(update1);
// Abort on the next statement. The retry should now fail because of the changed result of the
// first update.
mockSpanner.abortNextStatement();
mockSpanner.putStatementResult(StatementResult.update(update1, 200));
connection.executeUpdateAsync(INSERT_STATEMENT);
ApiFuture<Void> commit = connection.commitAsync();
try (AsyncResultSet rs = connection.executeQueryAsync(SELECT_RANDOM_STATEMENT)) {
while (rs.next()) {
}
}
get(connection.commitAsync());
try {
get(commit);
fail("Missing expected exception");
} catch (AbortedDueToConcurrentModificationException e) {
assertThat(counter.retryCount).isEqualTo(1);
}
}
}
use of com.google.cloud.spanner.AbortedDueToConcurrentModificationException in project java-spanner by googleapis.
the class ConnectionAsyncApiAbortedTest method testBlindUpdateAborted_ThenAsyncQuery_WithConcurrentModification.
@Test
public void testBlindUpdateAborted_ThenAsyncQuery_WithConcurrentModification() {
Statement update1 = Statement.of("UPDATE FOO SET BAR=1 WHERE BAZ=100");
mockSpanner.putStatementResult(StatementResult.update(update1, 100));
RetryCounter counter = new RetryCounter();
try (Connection connection = createConnection(counter)) {
// Execute an update statement and then change the result for the next time it is executed.
get(connection.executeUpdateAsync(update1));
mockSpanner.putStatementResult(StatementResult.update(update1, 200));
// Abort on the next statement. The retry should now fail because of the changed result of the
// first update.
mockSpanner.abortNextStatement();
connection.executeUpdateAsync(INSERT_STATEMENT);
// AbortedDueToConcurrentModificationException.
try (AsyncResultSet rs = connection.executeQueryAsync(SELECT_RANDOM_STATEMENT)) {
ApiFuture<Void> fut = rs.setCallback(singleThreadedExecutor, resultSet -> {
// The following line should throw AbortedDueToConcurrentModificationException.
resultSet.tryNext();
return CallbackResponse.DONE;
});
try {
assertThat(get(fut)).isNull();
fail("Missing expected exception");
} catch (AbortedDueToConcurrentModificationException e) {
assertThat(counter.retryCount).isEqualTo(1);
}
}
// Ensure that a rollback and then a new statement does succeed.
connection.rollbackAsync();
try (AsyncResultSet rs = connection.executeQueryAsync(SELECT_RANDOM_STATEMENT)) {
ApiFuture<Void> fut = rs.setCallback(singleThreadedExecutor, resultSet -> {
resultSet.tryNext();
return CallbackResponse.DONE;
});
assertThat(get(fut)).isNull();
}
get(connection.commitAsync());
}
}
Aggregations