use of com.google.cloud.spanner.AbortedException in project google-cloud-java by GoogleCloudPlatform.
the class ITTransactionTest method readAbort.
@Test
public void readAbort() throws InterruptedException {
final String key1 = uniqueKey();
final String key2 = uniqueKey();
client.write(Arrays.asList(Mutation.newInsertBuilder("T").set("K").to(key1).set("V").to(0).build(), Mutation.newInsertBuilder("T").set("K").to(key2).set("V").to(1).build()));
final CountDownLatch t1Started = new CountDownLatch(1);
final CountDownLatch t1Done = new CountDownLatch(1);
final CountDownLatch t2Running = new CountDownLatch(1);
final CountDownLatch t2Done = new CountDownLatch(1);
// Thread 1 performs a read before notifying that it has started and allowing
// thread 2 to start. This ensures that it establishes a senior lock priority relative to
// thread 2. It then waits for thread 2 to read, so that both threads have shared locks on
// key1, before continuing to commit; the act of committing means that thread 1's lock is
// upgraded and thread 2's transaction is aborted. When thread 1 is done, thread 2 tries a
// second read, which will abort. Both threads will mask SpannerExceptions to ensure that
// the implementation does not require TransactionCallable to propagate them.
Thread t1 = new Thread() {
@Override
public void run() {
client.readWriteTransaction().run(new TransactionCallable<Void>() {
@Override
public Void run(TransactionContext transaction) throws SpannerException {
try {
Struct row = transaction.readRow("T", Key.of(key1), Arrays.asList("V"));
t1Started.countDown();
Uninterruptibles.awaitUninterruptibly(t2Running);
transaction.buffer(Mutation.newUpdateBuilder("T").set("K").to(key1).set("V").to(row.getLong(0) + 1).build());
return null;
} catch (SpannerException e) {
if (e.getErrorCode() == ErrorCode.ABORTED) {
assertThat(e).isInstanceOf(AbortedException.class);
assertThat(((AbortedException) e).getRetryDelayInMillis()).isNotEqualTo(-1L);
}
throw new RuntimeException("Swallowed exception: " + e.getMessage());
}
}
});
t1Done.countDown();
}
};
Thread t2 = new Thread() {
@Override
public void run() {
client.readWriteTransaction().run(new TransactionCallable<Void>() {
@Override
public Void run(TransactionContext transaction) throws SpannerException {
try {
Struct r1 = transaction.readRow("T", Key.of(key1), Arrays.asList("V"));
t2Running.countDown();
Uninterruptibles.awaitUninterruptibly(t1Done);
Struct r2 = transaction.readRow("T", Key.of(key2), Arrays.asList("V"));
transaction.buffer(Mutation.newUpdateBuilder("T").set("K").to(key2).set("V").to(r1.getLong(0) + r2.getLong(0)).build());
return null;
} catch (SpannerException e) {
if (e.getErrorCode() == ErrorCode.ABORTED) {
assertThat(e).isInstanceOf(AbortedException.class);
assertThat(((AbortedException) e).getRetryDelayInMillis()).isNotEqualTo(-1L);
}
throw new RuntimeException("Swallowed exception: " + e.getMessage());
}
}
});
t2Done.countDown();
}
};
t1.start();
Uninterruptibles.awaitUninterruptibly(t1Started);
// Thread 2 will abort on the first attempt and should retry; wait for completion to confirm.
t2.start();
assertThat(t2Done.await(1, TimeUnit.MINUTES)).isTrue();
// Check that both transactions effects are visible.
assertThat(client.singleUse(TimestampBound.strong()).readRow("T", Key.of(key1), Arrays.asList("V")).getLong(0)).isEqualTo(1);
assertThat(client.singleUse(TimestampBound.strong()).readRow("T", Key.of(key2), Arrays.asList("V")).getLong(0)).isEqualTo(2);
}
Aggregations