use of com.google.cloud.spanner.AbortedDueToConcurrentModificationException in project java-spanner by googleapis.
the class ConnectionAsyncApiAbortedTest method testMultipleBlindUpdatesAborted_WithConcurrentModification.
@Test
public void testMultipleBlindUpdatesAborted_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 the transaction on the next statement. The retry should now fail because of the
// changed result of the first update.
mockSpanner.abortNextStatement();
// Continue to (try to) execute blind updates. This should not cause any exceptions, although
// all of the returned futures will fail.
List<ApiFuture<Long>> futures = new ArrayList<>();
for (int i = 0; i < 3; i++) {
futures.add(connection.executeUpdateAsync(INSERT_STATEMENT));
}
for (ApiFuture<Long> fut : futures) {
try {
get(fut);
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 ITAsyncTransactionRetryTest method testAbortWithConcurrentUpdate.
@Test
public void testAbortWithConcurrentUpdate() {
assumeFalse("concurrent transactions are not supported on the emulator", isUsingEmulator());
AbortInterceptor interceptor = new AbortInterceptor(0);
// first insert two test records
try (ITConnection connection = createConnection()) {
connection.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test 1')"));
connection.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (2, 'test 2')"));
get(connection.commitAsync());
}
// open a new connection and select the two test records
try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) {
// select the test records and consume the entire result set
try (AsyncResultSet rs = connection.executeQueryAsync(Statement.of("SELECT * FROM TEST ORDER BY ID"))) {
get(rs.setCallback(executor, resultSet -> {
while (true) {
switch(resultSet.tryNext()) {
case DONE:
return CallbackResponse.DONE;
case NOT_READY:
return CallbackResponse.CONTINUE;
case OK:
break;
}
}
}));
}
// open a new connection and transaction and update one of the test records
try (ITConnection connection2 = createConnection()) {
connection2.executeUpdateAsync(Statement.of("UPDATE TEST SET NAME='test updated' WHERE ID=2"));
get(connection2.commitAsync());
}
// now try to do an insert that will abort. The retry should now fail as there has been a
// concurrent modification
interceptor.setProbability(1.0);
interceptor.setOnlyInjectOnce(true);
try {
get(connection.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (3, 'test 3')")));
fail("Missing expected exception");
} catch (AbortedDueToConcurrentModificationException e) {
assertRetryStatistics(1, 1, 0);
}
}
}
use of com.google.cloud.spanner.AbortedDueToConcurrentModificationException in project java-spanner by googleapis.
the class ITAsyncTransactionRetryTest method testAbortWithConcurrentDelete.
@Test
public void testAbortWithConcurrentDelete() {
assumeFalse("concurrent transactions are not supported on the emulator", isUsingEmulator());
AbortInterceptor interceptor = new AbortInterceptor(0);
// first insert two test records
try (ITConnection connection = createConnection()) {
connection.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test 1')"));
connection.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (2, 'test 2')"));
get(connection.commitAsync());
}
// open a new connection and select the two test records
try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) {
// select the test records and consume the entire result set
try (AsyncResultSet rs = connection.executeQueryAsync(Statement.of("SELECT * FROM TEST ORDER BY ID"))) {
get(rs.setCallback(executor, resultSet -> {
while (true) {
switch(resultSet.tryNext()) {
case DONE:
return CallbackResponse.DONE;
case NOT_READY:
return CallbackResponse.CONTINUE;
case OK:
break;
}
}
}));
}
// open a new connection and transaction and remove one of the test records
try (ITConnection connection2 = createConnection()) {
connection2.executeUpdateAsync(Statement.of("DELETE FROM TEST WHERE ID=1"));
get(connection2.commitAsync());
}
// now try to do an insert that will abort. The retry should now fail as there has been a
// concurrent modification
interceptor.setProbability(1.0);
interceptor.setOnlyInjectOnce(true);
try {
get(connection.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (3, 'test 3')")));
fail("Missing expected exception");
} catch (AbortedDueToConcurrentModificationException e) {
assertRetryStatistics(1, 1, 0);
}
}
}
use of com.google.cloud.spanner.AbortedDueToConcurrentModificationException in project java-spanner by googleapis.
the class ITAsyncTransactionRetryTest method testAbortWithConcurrentInsert.
@Test
public void testAbortWithConcurrentInsert() {
assumeFalse("concurrent transactions are not supported on the emulator", isUsingEmulator());
AbortInterceptor interceptor = new AbortInterceptor(0);
try (ITConnection connection = createConnection(interceptor, new CountTransactionRetryListener())) {
// insert two test records
connection.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test 1')"));
connection.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (2, 'test 2')"));
// select the test records and consume the entire result set
try (AsyncResultSet rs = connection.executeQueryAsync(Statement.of("SELECT * FROM TEST ORDER BY ID"))) {
get(rs.setCallback(executor, resultSet -> {
while (true) {
switch(resultSet.tryNext()) {
case DONE:
return CallbackResponse.DONE;
case NOT_READY:
return CallbackResponse.CONTINUE;
case OK:
break;
}
}
}));
}
// open a new connection and transaction and do an additional insert
try (ITConnection connection2 = createConnection()) {
connection2.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (3, 'test 3')"));
get(connection2.commitAsync());
}
// now try to do an insert that will abort. The retry should now fail as there has been a
// concurrent modification
interceptor.setProbability(1.0);
interceptor.setOnlyInjectOnce(true);
ApiFuture<Long> updateCount = connection.executeUpdateAsync(Statement.of("INSERT INTO TEST (ID, NAME) VALUES (4, 'test 4')"));
try {
get(updateCount);
fail("Missing expected exception");
} catch (AbortedDueToConcurrentModificationException e) {
assertRetryStatistics(1, 1, 0);
}
}
}
use of com.google.cloud.spanner.AbortedDueToConcurrentModificationException in project java-spanner by googleapis.
the class ITSqlMusicScriptTest method test02_RunAbortedTest.
@Test
public void test02_RunAbortedTest() {
assumeFalse("concurrent transactions are not supported on the emulator", isUsingEmulator());
final long SINGER_ID = 2L;
final long VENUE_ID = 68L;
final long NUMBER_OF_SINGERS = 30L;
final long NUMBER_OF_ALBUMS = 60L;
final long NUMBER_OF_SONGS = 149L;
final long NUMBER_OF_CONCERTS = 100L;
long numberOfSongs = 0L;
AbortInterceptor interceptor = new AbortInterceptor(0.0D);
try (ITConnection connection = createConnection(interceptor)) {
connection.setAutocommit(false);
connection.setRetryAbortsInternally(true);
// Read all data from the different music tables in the transaction
// The previous test deleted the first two Singers records.
long expectedId = 3L;
try (ResultSet rs = connection.executeQuery(Statement.of("SELECT * FROM Singers ORDER BY SingerId"))) {
while (rs.next()) {
assertThat(rs.getLong("SingerId"), is(equalTo(expectedId)));
expectedId++;
}
}
assertThat(expectedId, is(equalTo(NUMBER_OF_SINGERS + 1L)));
expectedId = 3L;
try (ResultSet rs = connection.executeQuery(Statement.of("SELECT * FROM Albums ORDER BY AlbumId"))) {
while (rs.next()) {
assertThat(rs.getLong("AlbumId"), is(equalTo(expectedId)));
expectedId++;
// 31 and 32 were deleted by the first test script.
if (expectedId == 31L || expectedId == 32L) {
expectedId = 33L;
}
}
}
assertThat(expectedId, is(equalTo(NUMBER_OF_ALBUMS + 1L)));
expectedId = 1L;
try (ResultSet rs = connection.executeQuery(Statement.of("SELECT * FROM Songs ORDER BY TrackId"))) {
while (rs.next()) {
assertThat(rs.getLong("TrackId"), is(equalTo(expectedId)));
expectedId++;
numberOfSongs++;
// 40, 64, 76, 86 and 96 were deleted by the first test script.
if (expectedId == 40L || expectedId == 64L || expectedId == 76L || expectedId == 86L || expectedId == 96L) {
expectedId++;
}
}
}
assertThat(expectedId, is(equalTo(NUMBER_OF_SONGS + 1L)));
// Concerts are not in the table hierarchy, so no records have been deleted.
expectedId = 1L;
try (ResultSet rs = connection.executeQuery(Statement.of("SELECT * FROM Concerts ORDER BY VenueId"))) {
while (rs.next()) {
assertThat(rs.getLong("VenueId"), is(equalTo(expectedId)));
expectedId++;
}
}
assertThat(expectedId, is(equalTo(NUMBER_OF_CONCERTS + 1L)));
// make one small concurrent change in a different transaction
List<Long> originalPrices;
List<Long> newPrices;
try (ITConnection connection2 = createConnection()) {
assertThat(connection2.isAutocommit(), is(true));
try (ResultSet rs = connection2.executeQuery(Statement.newBuilder("SELECT TicketPrices FROM Concerts WHERE SingerId=@singer AND VenueId=@venue").bind("singer").to(SINGER_ID).bind("venue").to(VENUE_ID).build())) {
assertThat(rs.next(), is(true));
originalPrices = rs.getLongList(0);
// increase one of the prices by 1
newPrices = new ArrayList<>(originalPrices);
newPrices.set(1, originalPrices.get(1) + 1);
connection2.executeUpdate(Statement.newBuilder("UPDATE Concerts SET TicketPrices=@prices WHERE SingerId=@singer AND VenueId=@venue").bind("prices").toInt64Array(newPrices).bind("singer").to(SINGER_ID).bind("venue").to(VENUE_ID).build());
}
}
// try to add a new song and then try to commit, but trigger an abort on commit
connection.bufferedWrite(Mutation.newInsertBuilder("Songs").set("SingerId").to(3L).set("AlbumId").to(3L).set("TrackId").to(1L).set("SongName").to("Aborted").set("Duration").to(1L).set("SongGenre").to("Unknown").build());
interceptor.setProbability(1.0);
interceptor.setOnlyInjectOnce(true);
// the transaction retry should fail because of the concurrent modification
boolean expectedException = false;
try {
connection.commit();
} catch (AbortedDueToConcurrentModificationException e) {
expectedException = true;
}
// verify that the commit aborted, an internal retry was started and then aborted because of
// the concurrent modification
assertThat(expectedException, is(true));
// Rollback the transaction to start a new one.
connection.rollback();
// verify that the prices were changed
try (ResultSet rs = connection.executeQuery(Statement.newBuilder("SELECT TicketPrices FROM Concerts WHERE SingerId=@singer AND VenueId=@venue").bind("singer").to(SINGER_ID).bind("venue").to(VENUE_ID).build())) {
assertThat(rs.next(), is(true));
assertThat(rs.getLongList(0), is(equalTo(newPrices)));
}
// verify that the new song was not written to the database
try (ResultSet rs = connection.executeQuery(Statement.of("SELECT COUNT(*) FROM Songs"))) {
assertThat(rs.next(), is(true));
assertThat(rs.getLong(0), is(equalTo(numberOfSongs)));
}
}
}
Aggregations