diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManager.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManager.java index bb5140c475..1372177258 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManager.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManager.java @@ -203,7 +203,12 @@ interface AsyncTransactionFunction { /** Returns the state of the transaction. */ TransactionState getState(); - /** Returns the {@link CommitResponse} of this transaction. */ + /** + * Returns an {@link ApiFuture} with the eventual {@link CommitResponse} of this transaction. This + * method can be called before the transaction has been committed. The {@link ApiFuture} will be + * done when the transaction is committed or rolled back. If the commit fails or the transaction + * is rolled back, the result of this {@link ApiFuture} will be an exception. + */ ApiFuture getCommitResponse(); /** diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java index ebba9fa9e1..c6d9acaf8a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java @@ -191,6 +191,11 @@ public ApiFuture rollbackAsync() { Preconditions.checkState( txnState == TransactionState.STARTED, "rollback can only be called if the transaction is in progress"); + if (!commitResponse.isDone()) { + commitResponse.setException( + SpannerExceptionFactory.newSpannerException( + ErrorCode.FAILED_PRECONDITION, "The transaction has been rolled back")); + } try { return ApiFutures.transformAsync( txn.rollbackAsync(), diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java index 68cf63d872..6ea6f0f23f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AsyncTransactionManagerTest.java @@ -25,6 +25,7 @@ import static com.google.cloud.spanner.SpannerApiFutures.get; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -200,6 +201,39 @@ public void asyncTransactionManager_shouldRollbackOnCloseAsync() throws Exceptio 0L); } + @Test + public void testAsyncTransactionManager_commitResponseReturnsErrorAfterRollback() + throws Exception { + try (AsyncTransactionManager manager = client().transactionManagerAsync()) { + TransactionContextFuture transactionContextFuture = manager.beginAsync(); + ApiFuture commitResponse = manager.getCommitResponse(); + while (true) { + try { + AsyncTransactionStep next = + transactionContextFuture.then( + (transactionContext, ignored) -> + transactionContext.bufferAsync( + Collections.singleton(Mutation.delete("FOO", Key.of("foo")))), + executor); + assertFalse(commitResponse.isDone()); + manager.rollbackAsync().get(); + assertTrue(commitResponse.isDone()); + ExecutionException executionException = + assertThrows(ExecutionException.class, commitResponse::get); + assertEquals(SpannerException.class, executionException.getCause().getClass()); + SpannerException spannerException = (SpannerException) executionException.getCause(); + assertEquals(ErrorCode.FAILED_PRECONDITION, spannerException.getErrorCode()); + assertEquals( + "FAILED_PRECONDITION: The transaction has been rolled back", + spannerException.getMessage()); + break; + } catch (AbortedException e) { + transactionContextFuture = manager.resetForRetryAsync(); + } + } + } + } + @Test public void testAsyncTransactionManager_returnsCommitStats() throws Exception { try (AsyncTransactionManager manager =