Search in sources :

Example 1 with TransactionContext

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));
}
Also used : TransactionCallable(com.google.cloud.spanner.TransactionRunner.TransactionCallable) ArrayList(java.util.ArrayList) CountDownLatch(java.util.concurrent.CountDownLatch) Timestamp(com.google.cloud.Timestamp) Struct(com.google.cloud.spanner.Struct) TransactionContext(com.google.cloud.spanner.TransactionContext) TransactionRunner(com.google.cloud.spanner.TransactionRunner) Vector(java.util.Vector)

Example 2 with TransactionContext

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();
}
Also used : TransactionCallable(com.google.cloud.spanner.TransactionRunner.TransactionCallable) TransactionContext(com.google.cloud.spanner.TransactionContext) SpannerExceptionFactory.newSpannerException(com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException) SpannerException(com.google.cloud.spanner.SpannerException) SpannerExceptionFactory.newSpannerException(com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException) AbortedException(com.google.cloud.spanner.AbortedException) SpannerException(com.google.cloud.spanner.SpannerException) Struct(com.google.cloud.spanner.Struct) IntegrationTest(com.google.cloud.spanner.IntegrationTest) Test(org.junit.Test)

Example 3 with TransactionContext

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);
}
Also used : TransactionContext(com.google.cloud.spanner.TransactionContext) AbortedException(com.google.cloud.spanner.AbortedException) SpannerExceptionFactory.newSpannerException(com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException) SpannerException(com.google.cloud.spanner.SpannerException) CountDownLatch(java.util.concurrent.CountDownLatch) Struct(com.google.cloud.spanner.Struct) IntegrationTest(com.google.cloud.spanner.IntegrationTest) Test(org.junit.Test)

Example 4 with TransactionContext

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();
}
Also used : TransactionCallable(com.google.cloud.spanner.TransactionRunner.TransactionCallable) TransactionContext(com.google.cloud.spanner.TransactionContext) SpannerExceptionFactory.newSpannerException(com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException) SpannerException(com.google.cloud.spanner.SpannerException) Struct(com.google.cloud.spanner.Struct) IntegrationTest(com.google.cloud.spanner.IntegrationTest) Test(org.junit.Test)

Aggregations

Struct (com.google.cloud.spanner.Struct)4 TransactionContext (com.google.cloud.spanner.TransactionContext)4 IntegrationTest (com.google.cloud.spanner.IntegrationTest)3 SpannerException (com.google.cloud.spanner.SpannerException)3 SpannerExceptionFactory.newSpannerException (com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException)3 TransactionCallable (com.google.cloud.spanner.TransactionRunner.TransactionCallable)3 Test (org.junit.Test)3 AbortedException (com.google.cloud.spanner.AbortedException)2 CountDownLatch (java.util.concurrent.CountDownLatch)2 Timestamp (com.google.cloud.Timestamp)1 TransactionRunner (com.google.cloud.spanner.TransactionRunner)1 ArrayList (java.util.ArrayList)1 Vector (java.util.Vector)1