use of com.google.cloud.spanner.TransactionContext in project google-cloud-java by GoogleCloudPlatform.
the class ITTransactionTest method doBasicsTest.
private void doBasicsTest(final ReadStrategy strategy) throws InterruptedException {
final String key = uniqueKey();
// Initial value.
client.write(Arrays.asList(Mutation.newInsertBuilder("T").set("K").to(key).set("V").to(0).build()));
final int numThreads = 3;
final CountDownLatch commitBarrier = new CountDownLatch(numThreads);
final CountDownLatch complete = new CountDownLatch(numThreads);
final TransactionCallable<Long> callable = new TransactionCallable<Long>() {
@Override
public Long run(TransactionContext transaction) throws SpannerException {
Struct row = strategy.read(transaction, key);
long newValue = row.getLong(0) + 1;
transaction.buffer(Mutation.newUpdateBuilder("T").set("K").to(key).set("V").to(newValue).build());
commitBarrier.countDown();
// Synchronize so that all threads attempt to commit at the same time.
Uninterruptibles.awaitUninterruptibly(commitBarrier);
return newValue;
}
};
// We start multiple threads all attempting to update the same value concurrently. We expect
// to see at least some of the corresponding transactions abort.
final Vector<Long> results = new Vector<>();
final Vector<Timestamp> commitTimestamps = new Vector<>();
class TxnThread extends Thread {
@Override
public void run() {
TransactionRunner runner = client.readWriteTransaction();
Long result = runner.run(callable);
results.add(result);
commitTimestamps.add(runner.getCommitTimestamp());
complete.countDown();
}
}
for (int i = 0; i < numThreads; ++i) {
new TxnThread().start();
}
complete.await();
assertThat(results).hasSize(numThreads);
List<Long> expectedResults = new ArrayList<>();
for (int i = 0; i < numThreads; ++i) {
expectedResults.add(i + 1L);
}
assertThat(results).containsAllIn(expectedResults);
assertThat(Sets.newHashSet(commitTimestamps)).hasSize(numThreads);
assertThat(client.singleUse(TimestampBound.strong()).readRow("T", Key.of(key), Arrays.asList("V")).getLong(0)).isEqualTo(Long.valueOf(numThreads));
}
use of com.google.cloud.spanner.TransactionContext in project google-cloud-java by GoogleCloudPlatform.
the class ITTransactionTest method userExceptionPreventsCommit.
@Test
public void userExceptionPreventsCommit() {
class UserException extends Exception {
UserException(String message) {
super(message);
}
}
final String key = uniqueKey();
TransactionCallable<Void> callable = new TransactionCallable<Void>() {
@Override
public Void run(TransactionContext transaction) throws UserException {
transaction.buffer(Mutation.newInsertOrUpdateBuilder("T").set("K").to(key).build());
throw new UserException("User failure");
}
};
try {
client.readWriteTransaction().run(callable);
fail("Expected user exception");
} catch (SpannerException e) {
assertThat(e.getErrorCode()).isEqualTo(ErrorCode.UNKNOWN);
assertThat(e.getMessage()).contains("User failure");
assertThat(e.getCause()).isInstanceOf(UserException.class);
}
Struct row = client.singleUse(TimestampBound.strong()).readRow("T", Key.of(key), Arrays.asList("K"));
assertThat(row).isNull();
}
use of com.google.cloud.spanner.TransactionContext 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);
}
use of com.google.cloud.spanner.TransactionContext in project google-cloud-java by GoogleCloudPlatform.
the class ITTransactionTest method userExceptionIsSpannerException.
@Test
public void userExceptionIsSpannerException() {
final String key = uniqueKey();
TransactionCallable<Void> callable = new TransactionCallable<Void>() {
@Override
public Void run(TransactionContext transaction) {
transaction.buffer(Mutation.newInsertOrUpdateBuilder("T").set("K").to(key).build());
throw newSpannerException(ErrorCode.OUT_OF_RANGE, "User failure");
}
};
try {
client.readWriteTransaction().run(callable);
fail("Expected user exception");
} catch (SpannerException e) {
assertThat(e.getErrorCode()).isEqualTo(ErrorCode.OUT_OF_RANGE);
assertThat(e.getMessage()).contains("User failure");
}
Struct row = client.singleUse(TimestampBound.strong()).readRow("T", Key.of(key), Arrays.asList("K"));
assertThat(row).isNull();
}
Aggregations