diff --git a/.github/readme/synth.metadata/synth.metadata b/.github/readme/synth.metadata/synth.metadata index dfef9855f54..def4dadcd00 100644 --- a/.github/readme/synth.metadata/synth.metadata +++ b/.github/readme/synth.metadata/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/java-spanner.git", - "sha": "3f5351df6d13bd3bc3639e8a75e24ae924a4cfec" + "sha": "7d4f70a613b9a7668e371ee3aa174fdf897de141" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "b32c87d2c087fb2a841acc623ab540105de821af" + "sha": "4dca4132c6d63788c6675e1b1e11e7b9225f8694" } } ] diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 4572da72d57..17f6568f292 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -60,6 +60,7 @@ javadoc) ;; integration) mvn -B ${INTEGRATION_TEST_ARGS} \ + -ntp \ -Penable-integration-tests \ -DtrimStackTrace=false \ -Dclirr.skip=true \ @@ -81,6 +82,7 @@ samples) pushd ${SAMPLES_DIR} mvn -B \ -Penable-samples \ + -ntp \ -DtrimStackTrace=false \ -Dclirr.skip=true \ -Denforcer.skip=true \ diff --git a/.kokoro/release/publish_javadoc.cfg b/.kokoro/release/publish_javadoc.cfg index cbde2338f3d..1dbad91bb4c 100644 --- a/.kokoro/release/publish_javadoc.cfg +++ b/.kokoro/release/publish_javadoc.cfg @@ -7,10 +7,10 @@ env_vars: { value: "docs-staging" } +# cloud-rad staging env_vars: { key: "STAGING_BUCKET_V2" - value: "docs-staging-v2" - # Production will be at: docs-staging-v2 + value: "docs-staging-v2-staging" } env_vars: { diff --git a/.kokoro/release/publish_javadoc.sh b/.kokoro/release/publish_javadoc.sh index eded3cc375c..aa1e26e4e19 100755 --- a/.kokoro/release/publish_javadoc.sh +++ b/.kokoro/release/publish_javadoc.sh @@ -71,7 +71,7 @@ python3 -m docuploader create-metadata \ --version ${VERSION} \ --language java -# upload docs +# upload docs to staging bucket python3 -m docuploader upload . \ --credentials ${CREDENTIALS} \ --staging-bucket ${STAGING_BUCKET_V2} diff --git a/.kokoro/release/publish_javadoc11.cfg b/.kokoro/release/publish_javadoc11.cfg index 02cd9b1ccff..a432da71657 100644 --- a/.kokoro/release/publish_javadoc11.cfg +++ b/.kokoro/release/publish_javadoc11.cfg @@ -1,9 +1,9 @@ # Format: //devtools/kokoro/config/proto/build.proto +# cloud-rad production env_vars: { key: "STAGING_BUCKET_V2" value: "docs-staging-v2" - # Production will be at: docs-staging-v2 } # Configure the docker image for kokoro-trampoline diff --git a/.kokoro/release/publish_javadoc11.sh b/.kokoro/release/publish_javadoc11.sh index 63af49cf0da..8ab16b7ecaf 100755 --- a/.kokoro/release/publish_javadoc11.sh +++ b/.kokoro/release/publish_javadoc11.sh @@ -48,7 +48,7 @@ python3 -m docuploader create-metadata \ --version ${VERSION} \ --language java -# upload yml +# upload yml to production bucket python3 -m docuploader upload . \ --credentials ${CREDENTIALS} \ --staging-bucket ${STAGING_BUCKET_V2} \ diff --git a/CHANGELOG.md b/CHANGELOG.md index aaa57c3d4ed..f3dad539aa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +### [4.0.1](https://www.github.com/googleapis/java-spanner/compare/v4.0.0...v4.0.1) (2021-02-22) + + +### Bug Fixes + +* wrong use of getRetryDelayInMillis() / 1000 in documentation and retry loops ([#885](https://www.github.com/googleapis/java-spanner/issues/885)) ([a55d7ce](https://www.github.com/googleapis/java-spanner/commit/a55d7ce64fff434151c1c3af0796d290e9db7470)), closes [#874](https://www.github.com/googleapis/java-spanner/issues/874) + + +### Documentation + +* Add OpenCensus to OpenTelemetry shim to README ([#879](https://www.github.com/googleapis/java-spanner/issues/879)) ([b58d73d](https://www.github.com/googleapis/java-spanner/commit/b58d73ddb768c0d33d149ed8bc84f5af618514e1)) + + +### Dependencies + +* update dependency com.google.cloud:google-cloud-shared-dependencies to v0.19.0 ([#895](https://www.github.com/googleapis/java-spanner/issues/895)) ([e3e2c95](https://www.github.com/googleapis/java-spanner/commit/e3e2c95936f40a7954639a95c84cc9495e318e55)) + ## [4.0.0](https://www.github.com/googleapis/java-spanner/compare/v3.3.2...v4.0.0) (2021-02-17) diff --git a/README.md b/README.md index f35503e755a..e19e2fe2add 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ If you are using Maven without BOM, add this to your dependencies: com.google.cloud google-cloud-spanner - 3.3.2 + 4.0.0 ``` @@ -51,12 +51,12 @@ compile 'com.google.cloud:google-cloud-spanner' ``` If you are using Gradle without BOM, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-spanner:3.3.2' +compile 'com.google.cloud:google-cloud-spanner:4.0.0' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "3.3.2" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "4.0.0" ``` ## Authentication diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml index 14a3963a578..5f9869bbca4 100644 --- a/google-cloud-spanner-bom/pom.xml +++ b/google-cloud-spanner-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-spanner-bom - 4.0.0 + 4.0.1 pom com.google.cloud @@ -64,43 +64,43 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 4.0.0 + 4.0.1 com.google.api.grpc grpc-google-cloud-spanner-v1 - 4.0.0 + 4.0.1 com.google.api.grpc proto-google-cloud-spanner-v1 - 4.0.0 + 4.0.1 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 4.0.0 + 4.0.1 com.google.cloud google-cloud-spanner - 4.0.0 + 4.0.1 com.google.cloud google-cloud-spanner test-jar - 4.0.0 + 4.0.1 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 4.0.0 + 4.0.1 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 4.0.0 + 4.0.1 diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 323dc2a364a..59c6c3c6dac 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-spanner - 4.0.0 + 4.0.1 jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 4.0.0 + 4.0.1 google-cloud-spanner diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClient.java index 8d0e0ea0e3f..bd42aa307ff 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseClient.java @@ -321,19 +321,19 @@ CommitResponse writeAtLeastOnceWithOptions( *
{@code
    * long singerId = my_singer_id;
    * try (TransactionManager manager = dbClient.transactionManager()) {
-   *   TransactionContext txn = manager.begin();
+   *   TransactionContext transaction = manager.begin();
    *   while (true) {
    *     String column = "FirstName";
-   *     Struct row = txn.readRow("Singers", Key.of(singerId), Collections.singleton(column));
+   *     Struct row = transaction.readRow("Singers", Key.of(singerId), Collections.singleton(column));
    *     String name = row.getString(column);
-   *     txn.buffer(
+   *     transaction.buffer(
    *         Mutation.newUpdateBuilder("Singers").set(column).to(name.toUpperCase()).build());
    *     try {
    *       manager.commit();
    *       break;
    *     } catch (AbortedException e) {
-   *       Thread.sleep(e.getRetryDelayInMillis() / 1000);
-   *       txn = manager.resetForRetry();
+   *       Thread.sleep(e.getRetryDelayInMillis());
+   *       transaction = manager.resetForRetry();
    *     }
    *   }
    * }
@@ -385,19 +385,19 @@ CommitResponse writeAtLeastOnceWithOptions(
    * 
{@code
    * long singerId = 1L;
    * try (AsyncTransactionManager manager = client.transactionManagerAsync()) {
-   *   TransactionContextFuture txnFut = manager.beginAsync();
+   *   TransactionContextFuture transactionFuture = manager.beginAsync();
    *   while (true) {
    *     String column = "FirstName";
    *     CommitTimestampFuture commitTimestamp =
-   *         txnFut
+   *         transactionFuture
    *             .then(
-   *                 (txn, __) ->
-   *                     txn.readRowAsync(
+   *                 (transaction, __) ->
+   *                     transaction.readRowAsync(
    *                         "Singers", Key.of(singerId), Collections.singleton(column)))
    *             .then(
-   *                 (txn, row) -> {
+   *                 (transaction, row) -> {
    *                   String name = row.getString(column);
-   *                   txn.buffer(
+   *                   transaction.buffer(
    *                       Mutation.newUpdateBuilder("Singers")
    *                           .set(column)
    *                           .to(name.toUpperCase())
@@ -409,8 +409,8 @@ CommitResponse writeAtLeastOnceWithOptions(
    *       commitTimestamp.get();
    *       break;
    *     } catch (AbortedException e) {
-   *       Thread.sleep(e.getRetryDelayInMillis() / 1000);
-   *       txnFut = manager.resetForRetryAsync();
+   *       Thread.sleep(e.getRetryDelayInMillis());
+   *       transactionFuture = manager.resetForRetryAsync();
    *     }
    *   }
    * }
@@ -421,26 +421,26 @@ CommitResponse writeAtLeastOnceWithOptions(
    * 
{@code
    * final long singerId = 1L;
    * try (AsyncTransactionManager manager = client().transactionManagerAsync()) {
-   *   TransactionContextFuture txn = manager.beginAsync();
+   *   TransactionContextFuture transactionFuture = manager.beginAsync();
    *   while (true) {
    *     final String column = "FirstName";
    *     CommitTimestampFuture commitTimestamp =
-   *         txn.then(
+   *         transactionFuture.then(
    *                 new AsyncTransactionFunction() {
    *                   @Override
-   *                   public ApiFuture apply(TransactionContext txn, Void input)
+   *                   public ApiFuture apply(TransactionContext transaction, Void input)
    *                       throws Exception {
-   *                     return txn.readRowAsync(
+   *                     return transaction.readRowAsync(
    *                         "Singers", Key.of(singerId), Collections.singleton(column));
    *                   }
    *                 })
    *             .then(
    *                 new AsyncTransactionFunction() {
    *                   @Override
-   *                   public ApiFuture apply(TransactionContext txn, Struct input)
+   *                   public ApiFuture apply(TransactionContext transaction, Struct input)
    *                       throws Exception {
    *                     String name = input.getString(column);
-   *                     txn.buffer(
+   *                     transaction.buffer(
    *                         Mutation.newUpdateBuilder("Singers")
    *                             .set(column)
    *                             .to(name.toUpperCase())
@@ -453,8 +453,8 @@ CommitResponse writeAtLeastOnceWithOptions(
    *       commitTimestamp.get();
    *       break;
    *     } catch (AbortedException e) {
-   *       Thread.sleep(e.getRetryDelayInMillis() / 1000);
-   *       txn = manager.resetForRetryAsync();
+   *       Thread.sleep(e.getRetryDelayInMillis());
+   *       transactionFuture = manager.resetForRetryAsync();
    *     }
    *   }
    * }
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
index 51a42525bfc..0d5f36b5c1c 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionPool.java
@@ -816,13 +816,21 @@ private TransactionContext internalBegin() {
     }
 
     @Override
-    public SpannerException handleSessionNotFound(SessionNotFoundException notFound) {
-      session = sessionPool.replaceSession(notFound, session);
+    public SpannerException handleSessionNotFound(SessionNotFoundException notFoundException) {
+      session = sessionPool.replaceSession(notFoundException, session);
       PooledSession pooledSession = session.get();
       delegate = pooledSession.delegate.transactionManager(options);
       restartedAfterSessionNotFound = true;
+      return createAbortedExceptionWithMinimalRetryDelay(notFoundException);
+    }
+
+    private static SpannerException createAbortedExceptionWithMinimalRetryDelay(
+        SessionNotFoundException notFoundException) {
       return SpannerExceptionFactory.newSpannerException(
-          ErrorCode.ABORTED, notFound.getMessage(), notFound);
+          ErrorCode.ABORTED,
+          notFoundException.getMessage(),
+          SpannerExceptionFactory.createAbortedExceptionWithRetryDelay(
+              notFoundException.getMessage(), notFoundException, 0, 1));
     }
 
     @Override
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java
index 706ee87fd77..696ffc9a216 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java
@@ -24,9 +24,11 @@
 import com.google.common.base.Predicate;
 import com.google.rpc.ErrorInfo;
 import com.google.rpc.ResourceInfo;
+import com.google.rpc.RetryInfo;
 import io.grpc.Context;
 import io.grpc.Metadata;
 import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
 import io.grpc.protobuf.ProtoUtils;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.TimeoutException;
@@ -226,6 +228,28 @@ private static ErrorInfo extractErrorInfo(Throwable cause) {
     return null;
   }
 
+  /**
+   * Creates a {@link StatusRuntimeException} that contains a {@link RetryInfo} with the specified
+   * retry delay.
+   */
+  static StatusRuntimeException createAbortedExceptionWithRetryDelay(
+      String message, Throwable cause, long retryDelaySeconds, int retryDelayNanos) {
+    Metadata.Key key = ProtoUtils.keyForProto(RetryInfo.getDefaultInstance());
+    Metadata trailers = new Metadata();
+    RetryInfo retryInfo =
+        RetryInfo.newBuilder()
+            .setRetryDelay(
+                com.google.protobuf.Duration.newBuilder()
+                    .setNanos(retryDelayNanos)
+                    .setSeconds(retryDelaySeconds))
+            .build();
+    trailers.put(key, retryInfo);
+    return io.grpc.Status.ABORTED
+        .withDescription(message)
+        .withCause(cause)
+        .asRuntimeException(trailers);
+  }
+
   static SpannerException newSpannerExceptionPreformatted(
       ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
     // This is the one place in the codebase that is allowed to call constructors directly.
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java
index dbbbca068a0..fef873112d3 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java
@@ -531,7 +531,10 @@ public void onError(SpannerException e, boolean withBeginTransaction) {
         // Simulate an aborted transaction to force a retry with a new transaction.
         this.transactionIdFuture.setException(
             SpannerExceptionFactory.newSpannerException(
-                ErrorCode.ABORTED, "Aborted due to failed initial statement", e));
+                ErrorCode.ABORTED,
+                "Aborted due to failed initial statement",
+                SpannerExceptionFactory.createAbortedExceptionWithRetryDelay(
+                    "Aborted due to failed initial statement", e, 0, 1)));
       }
 
       if (e.getErrorCode() == ErrorCode.ABORTED) {
@@ -684,6 +687,19 @@ public void run() {
       return updateCount;
     }
 
+    private SpannerException createAbortedExceptionForBatchDml(ExecuteBatchDmlResponse response) {
+      // Manually construct an AbortedException with a 10ms retry delay for BatchDML responses that
+      // return an Aborted status (and not an AbortedException).
+      return newSpannerException(
+          ErrorCode.fromRpcStatus(response.getStatus()),
+          response.getStatus().getMessage(),
+          SpannerExceptionFactory.createAbortedExceptionWithRetryDelay(
+              response.getStatus().getMessage(),
+              /* cause = */ null,
+              /* retryDelaySeconds = */ 0,
+              /* retryDelayNanos = */ (int) TimeUnit.MILLISECONDS.toNanos(10L)));
+    }
+
     @Override
     public long[] batchUpdate(Iterable statements, UpdateOption... options) {
       beforeReadOrQuery();
@@ -705,8 +721,7 @@ public long[] batchUpdate(Iterable statements, UpdateOption... option
         // If one of the DML statements was aborted, we should throw an aborted exception.
         // In all other cases, we should throw a BatchUpdateException.
         if (response.getStatus().getCode() == Code.ABORTED_VALUE) {
-          throw newSpannerException(
-              ErrorCode.fromRpcStatus(response.getStatus()), response.getStatus().getMessage());
+          throw createAbortedExceptionForBatchDml(response);
         } else if (response.getStatus().getCode() != 0) {
           throw newSpannerBatchUpdateException(
               ErrorCode.fromRpcStatus(response.getStatus()),
@@ -741,25 +756,24 @@ public ApiFuture batchUpdateAsync(
               response,
               new ApiFunction() {
                 @Override
-                public long[] apply(ExecuteBatchDmlResponse input) {
-                  long[] results = new long[input.getResultSetsCount()];
-                  for (int i = 0; i < input.getResultSetsCount(); ++i) {
-                    results[i] = input.getResultSets(i).getStats().getRowCountExact();
-                    if (input.getResultSets(i).getMetadata().hasTransaction()) {
+                public long[] apply(ExecuteBatchDmlResponse batchDmlResponse) {
+                  long[] results = new long[batchDmlResponse.getResultSetsCount()];
+                  for (int i = 0; i < batchDmlResponse.getResultSetsCount(); ++i) {
+                    results[i] = batchDmlResponse.getResultSets(i).getStats().getRowCountExact();
+                    if (batchDmlResponse.getResultSets(i).getMetadata().hasTransaction()) {
                       onTransactionMetadata(
-                          input.getResultSets(i).getMetadata().getTransaction(),
+                          batchDmlResponse.getResultSets(i).getMetadata().getTransaction(),
                           builder.getTransaction().hasBegin());
                     }
                   }
                   // If one of the DML statements was aborted, we should throw an aborted exception.
                   // In all other cases, we should throw a BatchUpdateException.
-                  if (input.getStatus().getCode() == Code.ABORTED_VALUE) {
-                    throw newSpannerException(
-                        ErrorCode.fromRpcStatus(input.getStatus()), input.getStatus().getMessage());
-                  } else if (input.getStatus().getCode() != 0) {
+                  if (batchDmlResponse.getStatus().getCode() == Code.ABORTED_VALUE) {
+                    throw createAbortedExceptionForBatchDml(batchDmlResponse);
+                  } else if (batchDmlResponse.getStatus().getCode() != 0) {
                     throw newSpannerBatchUpdateException(
-                        ErrorCode.fromRpcStatus(input.getStatus()),
-                        input.getStatus().getMessage(),
+                        ErrorCode.fromRpcStatus(batchDmlResponse.getStatus()),
+                        batchDmlResponse.getStatus().getMessage(),
                         results);
                   }
                   return results;
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java
index 0a8e322e796..aa3f8e1ef2e 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ReadWriteTransaction.java
@@ -725,8 +725,11 @@ private void handleAborted(AbortedException aborted) {
       logger.fine(toString() + ": Starting internal transaction retry");
       while (true) {
         // First back off and then restart the transaction.
+        long delay = aborted.getRetryDelayInMillis();
         try {
-          Thread.sleep(aborted.getRetryDelayInMillis() / 1000);
+          if (delay > 0L) {
+            Thread.sleep(delay);
+          }
         } catch (InterruptedException ie) {
           Thread.currentThread().interrupt();
           throw SpannerExceptionFactory.newSpannerException(
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 748d6f76405..b5fa3ee5813 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
@@ -1137,7 +1137,7 @@ public ApiFuture apply(TransactionContext txn, Struct input)
           commitTimestamp.get();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetryAsync();
         }
       }
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
index 42a158d755b..5cfdb5ea624 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java
@@ -662,7 +662,7 @@ public void transactionManagerIsNonBlocking() throws Exception {
           txManager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           tx = txManager.resetForRetry();
         }
       }
@@ -705,7 +705,7 @@ public CallbackResponse cursorReady(AsyncResultSet resultSet) {
           txManager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           tx = txManager.resetForRetry();
         }
       }
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java
index 5aa2948b518..69204cb9582 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InlineBeginTransactionTest.java
@@ -36,7 +36,6 @@
 import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
 import com.google.cloud.spanner.TransactionRunner.TransactionCallable;
 import com.google.cloud.spanner.TransactionRunnerImpl.TransactionContextImpl;
-import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.protobuf.AbstractMessage;
@@ -61,7 +60,6 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
@@ -69,7 +67,6 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.junit.After;
@@ -1501,76 +1498,6 @@ public Void run(TransactionContext transaction) throws Exception {
       assertThat(countRequests(CommitRequest.class)).isEqualTo(1);
     }
 
-    @Test
-    public void testCloseResultSetWhileRequestInFlight() throws Exception {
-      DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d"));
-      final ExecutorService service = Executors.newSingleThreadExecutor();
-      try {
-        client
-            .readWriteTransaction()
-            .run(
-                new TransactionCallable() {
-                  @Override
-                  public Void run(TransactionContext transaction) throws Exception {
-                    final ResultSet rs = transaction.executeQuery(SELECT1);
-                    // Prevent the server from executing the query.
-                    final CountDownLatch latch = new CountDownLatch(1);
-                    mockSpanner.freeze();
-                    service.submit(
-                        new Runnable() {
-                          @Override
-                          public void run() {
-                            try {
-                              // This call will be stuck on the server until the mock server is
-                              // unfrozen.
-                              rs.next();
-                            } finally {
-                              latch.countDown();
-                            }
-                          }
-                        });
-
-                    // First wait for the request to be on the server and then close the result set
-                    // while the request is in flight.
-                    mockSpanner.waitForRequestsToContain(
-                        new Predicate() {
-                          @Override
-                          public boolean apply(AbstractMessage input) {
-                            return input instanceof ExecuteSqlRequest
-                                && ((ExecuteSqlRequest) input).getTransaction().hasBegin();
-                          }
-                        },
-                        1000L);
-                    rs.close();
-                    // The next statement should now fail before it is sent to the server because
-                    // the first statement failed to return a transaction while the result set was
-                    // still open.
-                    mockSpanner.unfreeze();
-                    latch.await(1L, TimeUnit.SECONDS);
-                    try {
-                      transaction.executeUpdate(UPDATE_STATEMENT);
-                      fail("missing expected exception");
-                    } catch (SpannerException e) {
-                      assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION);
-                      assertThat(e.getMessage())
-                          .contains("ResultSet was closed before a transaction id was returned");
-                    }
-                    return null;
-                  }
-                });
-        fail("missing expected exception");
-      } catch (SpannerException e) {
-        // The commit request will also fail, which means that the entire transaction will fail.
-        assertThat(e.getErrorCode()).isEqualTo(ErrorCode.FAILED_PRECONDITION);
-        assertThat(e.getMessage())
-            .contains("ResultSet was closed before a transaction id was returned");
-      }
-      service.shutdown();
-      assertThat(countRequests(BeginTransactionRequest.class)).isEqualTo(0);
-      assertThat(countRequests(ExecuteSqlRequest.class)).isEqualTo(1);
-      assertThat(countRequests(CommitRequest.class)).isEqualTo(0);
-    }
-
     @Test
     public void testQueryWithInlineBeginDidNotReturnTransaction() {
       DatabaseClient client = spanner.getDatabaseClient(DatabaseId.of("p", "i", "d"));
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java
index 424e9796923..0b688296047 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockSpannerServiceImpl.java
@@ -1732,7 +1732,7 @@ private void simulateAbort(Session session, ByteString transactionId) {
 
   public StatusRuntimeException createAbortedException(ByteString transactionId) {
     RetryInfo retryInfo =
-        RetryInfo.newBuilder().setRetryDelay(Duration.newBuilder().setNanos(100).build()).build();
+        RetryInfo.newBuilder().setRetryDelay(Duration.newBuilder().setNanos(1).build()).build();
     Metadata.Key key =
         Metadata.Key.of(
             retryInfo.getDescriptorForType().getFullName() + Metadata.BINARY_HEADER_SUFFIX,
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java
index 157d71bf367..9ea77366b16 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java
@@ -1007,7 +1007,7 @@ public void transactionManagerReadOnlySessionInPool() throws InterruptedExceptio
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1035,7 +1035,7 @@ public void transactionManagerSelect() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1063,7 +1063,7 @@ public void transactionManagerRead() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1092,7 +1092,7 @@ public void transactionManagerReadUsingIndex() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1116,7 +1116,7 @@ public void transactionManagerReadRow() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1140,7 +1140,7 @@ public void transactionManagerReadRowUsingIndex() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1164,7 +1164,7 @@ public void transactionManagerUpdate() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1197,7 +1197,7 @@ public void transactionManagerAborted_thenSessionNotFoundOnBeginTransaction()
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1224,7 +1224,7 @@ public void transactionManagerBatchUpdate() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1248,7 +1248,7 @@ public void transactionManagerBuffer() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1286,7 +1286,7 @@ public void transactionManagerSelectInvalidatedDuringTransaction() throws Interr
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1324,7 +1324,7 @@ public void transactionManagerReadInvalidatedDuringTransaction() throws Interrup
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1365,7 +1365,7 @@ public void transactionManagerReadUsingIndexInvalidatedDuringTransaction()
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1394,7 +1394,7 @@ public void transactionManagerReadRowInvalidatedDuringTransaction() throws Inter
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
@@ -1424,7 +1424,7 @@ public void transactionManagerReadRowUsingIndexInvalidatedDuringTransaction()
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           transaction = manager.resetForRetry();
         }
       }
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerAbortedTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerAbortedTest.java
index 0291e678687..8917d2d2e54 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerAbortedTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/TransactionManagerAbortedTest.java
@@ -27,13 +27,13 @@
 import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
 import com.google.cloud.spanner.v1.SpannerClient;
 import com.google.cloud.spanner.v1.SpannerSettings;
+import com.google.protobuf.ByteString;
 import com.google.protobuf.ListValue;
 import com.google.spanner.v1.ResultSetMetadata;
 import com.google.spanner.v1.StructType;
 import com.google.spanner.v1.StructType.Field;
 import com.google.spanner.v1.TypeCode;
 import io.grpc.Server;
-import io.grpc.Status;
 import io.grpc.inprocess.InProcessServerBuilder;
 import java.io.IOException;
 import java.util.Arrays;
@@ -139,7 +139,7 @@ public static void startStaticServer() throws IOException {
     mockSpanner.putStatementResult(
         StatementResult.exception(
             UPDATE_ABORTED_STATEMENT,
-            Status.ABORTED.withDescription("Transaction was aborted").asRuntimeException()));
+            mockSpanner.createAbortedException(ByteString.copyFromUtf8("test"))));
 
     String uniqueName = InProcessServerBuilder.generateName();
     server =
@@ -199,7 +199,7 @@ public void testTransactionManagerAbortOnCommit() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           manager.resetForRetry();
         }
       }
@@ -226,7 +226,7 @@ public void testTransactionManagerAbortOnUpdate() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
@@ -253,7 +253,7 @@ public void testTransactionManagerAbortOnBatchUpdate() throws InterruptedExcepti
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
@@ -281,7 +281,7 @@ public void testTransactionManagerAbortOnBatchUpdateHalfway() throws Interrupted
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
@@ -313,7 +313,7 @@ public void testTransactionManagerAbortOnSelect() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
@@ -345,7 +345,7 @@ public void testTransactionManagerAbortOnRead() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
@@ -378,7 +378,7 @@ public void testTransactionManagerAbortOnReadUsingIndex() throws InterruptedExce
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
@@ -405,7 +405,7 @@ public void testTransactionManagerAbortOnReadRow() throws InterruptedException {
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
@@ -432,7 +432,7 @@ public void testTransactionManagerAbortOnReadRowUsingIndex() throws InterruptedE
           manager.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionAsyncApiAbortedTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionAsyncApiAbortedTest.java
index a209bfa3122..dcf5ac2f35c 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionAsyncApiAbortedTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionAsyncApiAbortedTest.java
@@ -39,8 +39,8 @@
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.protobuf.AbstractMessage;
+import com.google.protobuf.ByteString;
 import com.google.spanner.v1.ExecuteSqlRequest;
-import io.grpc.Status;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -127,7 +127,8 @@ public void testSingleQueryAborted() {
     try (Connection connection = createConnection(counter)) {
       assertThat(counter.retryCount).isEqualTo(0);
       mockSpanner.setExecuteStreamingSqlExecutionTime(
-          SimulatedExecutionTime.ofException(Status.ABORTED.asRuntimeException()));
+          SimulatedExecutionTime.ofException(
+              mockSpanner.createAbortedException(ByteString.copyFromUtf8("test"))));
       QueryResult res = executeQueryAsync(connection, SELECT_RANDOM_STATEMENT);
 
       assertThat(get(res.finished)).isNull();
@@ -143,7 +144,8 @@ public void testTwoQueriesSecondAborted() {
       assertThat(counter.retryCount).isEqualTo(0);
       QueryResult res1 = executeQueryAsync(connection, SELECT_RANDOM_STATEMENT);
       mockSpanner.setExecuteStreamingSqlExecutionTime(
-          SimulatedExecutionTime.ofException(Status.ABORTED.asRuntimeException()));
+          SimulatedExecutionTime.ofException(
+              mockSpanner.createAbortedException(ByteString.copyFromUtf8("test"))));
       QueryResult res2 = executeQueryAsync(connection, SELECT_RANDOM_STATEMENT_2);
 
       assertThat(get(res1.finished)).isNull();
@@ -160,12 +162,14 @@ public void testTwoQueriesBothAborted() throws InterruptedException {
     try (Connection connection = createConnection(counter)) {
       assertThat(counter.retryCount).isEqualTo(0);
       mockSpanner.setExecuteStreamingSqlExecutionTime(
-          SimulatedExecutionTime.ofException(Status.ABORTED.asRuntimeException()));
+          SimulatedExecutionTime.ofException(
+              mockSpanner.createAbortedException(ByteString.copyFromUtf8("test"))));
       QueryResult res1 = executeQueryAsync(connection, SELECT_RANDOM_STATEMENT);
       // Wait until the first query aborted.
       assertThat(counter.latch.await(10L, TimeUnit.SECONDS)).isTrue();
       mockSpanner.setExecuteStreamingSqlExecutionTime(
-          SimulatedExecutionTime.ofException(Status.ABORTED.asRuntimeException()));
+          SimulatedExecutionTime.ofException(
+              mockSpanner.createAbortedException(ByteString.copyFromUtf8("test"))));
       QueryResult res2 = executeQueryAsync(connection, SELECT_RANDOM_STATEMENT_2);
 
       assertThat(get(res1.finished)).isNull();
@@ -180,7 +184,8 @@ public void testTwoQueriesBothAborted() throws InterruptedException {
   public void testSingleQueryAbortedMidway() {
     mockSpanner.setExecuteStreamingSqlExecutionTime(
         SimulatedExecutionTime.ofStreamException(
-            Status.ABORTED.asRuntimeException(), RANDOM_RESULT_SET_ROW_COUNT / 2));
+            mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")),
+            RANDOM_RESULT_SET_ROW_COUNT / 2));
     RetryCounter counter = new RetryCounter();
     try (Connection connection = createConnection(counter)) {
       assertThat(counter.retryCount).isEqualTo(0);
@@ -200,7 +205,8 @@ public void testTwoQueriesSecondAbortedMidway() {
       QueryResult res1 = executeQueryAsync(connection, SELECT_RANDOM_STATEMENT);
       mockSpanner.setExecuteStreamingSqlExecutionTime(
           SimulatedExecutionTime.ofStreamException(
-              Status.ABORTED.asRuntimeException(), RANDOM_RESULT_SET_ROW_COUNT_2 / 2));
+              mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")),
+              RANDOM_RESULT_SET_ROW_COUNT_2 / 2));
       QueryResult res2 = executeQueryAsync(connection, SELECT_RANDOM_STATEMENT_2);
 
       assertThat(get(res1.finished)).isNull();
@@ -215,7 +221,7 @@ public void testTwoQueriesSecondAbortedMidway() {
   public void testTwoQueriesOneAbortedMidway() {
     mockSpanner.setExecuteStreamingSqlExecutionTime(
         SimulatedExecutionTime.ofStreamException(
-            Status.ABORTED.asRuntimeException(),
+            mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")),
             Math.min(RANDOM_RESULT_SET_ROW_COUNT / 2, RANDOM_RESULT_SET_ROW_COUNT_2 / 2)));
     RetryCounter counter = new RetryCounter();
     try (Connection connection = createConnection(counter)) {
@@ -239,7 +245,8 @@ public void testTwoQueriesOneAbortedMidway() {
   public void testUpdateAndQueryAbortedMidway() throws InterruptedException {
     mockSpanner.setExecuteStreamingSqlExecutionTime(
         SimulatedExecutionTime.ofStreamException(
-            Status.ABORTED.asRuntimeException(), RANDOM_RESULT_SET_ROW_COUNT / 2));
+            mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")),
+            RANDOM_RESULT_SET_ROW_COUNT / 2));
     final RetryCounter counter = new RetryCounter();
     try (Connection connection = createConnection(counter)) {
       assertThat(counter.retryCount).isEqualTo(0);
@@ -334,7 +341,8 @@ public boolean apply(AbstractMessage input) {
   public void testUpdateAndQueryAbortedMidway_UpdateCountChanged() throws InterruptedException {
     mockSpanner.setExecuteStreamingSqlExecutionTime(
         SimulatedExecutionTime.ofStreamException(
-            Status.ABORTED.asRuntimeException(), RANDOM_RESULT_SET_ROW_COUNT / 2));
+            mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")),
+            RANDOM_RESULT_SET_ROW_COUNT / 2));
     final RetryCounter counter = new RetryCounter();
     try (Connection connection = createConnection(counter)) {
       assertThat(counter.retryCount).isEqualTo(0);
@@ -423,7 +431,8 @@ public boolean apply(AbstractMessage input) {
   public void testQueriesAbortedMidway_ResultsChanged() throws InterruptedException {
     mockSpanner.setExecuteStreamingSqlExecutionTime(
         SimulatedExecutionTime.ofStreamException(
-            Status.ABORTED.asRuntimeException(), RANDOM_RESULT_SET_ROW_COUNT - 1));
+            mockSpanner.createAbortedException(ByteString.copyFromUtf8("test")),
+            RANDOM_RESULT_SET_ROW_COUNT - 1));
     final Statement statement = Statement.of("SELECT * FROM TEST_TABLE");
     final RandomResultSetGenerator generator =
         new RandomResultSetGenerator(RANDOM_RESULT_SET_ROW_COUNT - 10);
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java
index 88cbcc108b9..aa633552a87 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITAbstractSpannerTest.java
@@ -34,6 +34,10 @@
 import com.google.common.base.Preconditions;
 import com.google.common.base.Stopwatch;
 import com.google.common.base.Strings;
+import com.google.rpc.RetryInfo;
+import io.grpc.Metadata;
+import io.grpc.StatusRuntimeException;
+import io.grpc.protobuf.ProtoUtils;
 import java.lang.reflect.Field;
 import java.nio.file.Files;
 import java.nio.file.Paths;
@@ -158,10 +162,23 @@ public void intercept(
             probability = 0;
           }
           throw SpannerExceptionFactory.newSpannerException(
-              ErrorCode.ABORTED, "Transaction was aborted by interceptor");
+              ErrorCode.ABORTED,
+              "Transaction was aborted by interceptor",
+              createAbortedExceptionWithMinimalRetry());
         }
       }
     }
+
+    private static StatusRuntimeException createAbortedExceptionWithMinimalRetry() {
+      Metadata.Key key = ProtoUtils.keyForProto(RetryInfo.getDefaultInstance());
+      Metadata trailers = new Metadata();
+      RetryInfo retryInfo =
+          RetryInfo.newBuilder()
+              .setRetryDelay(com.google.protobuf.Duration.newBuilder().setNanos(1).setSeconds(0L))
+              .build();
+      trailers.put(key, retryInfo);
+      return io.grpc.Status.ABORTED.asRuntimeException(trailers);
+    }
   }
 
   @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv();
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java
index de5c9fbdeba..ff44aa65382 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ReadWriteTransactionTest.java
@@ -49,7 +49,11 @@
 import com.google.cloud.spanner.Type.StructField;
 import com.google.cloud.spanner.connection.StatementParser.ParsedStatement;
 import com.google.cloud.spanner.connection.StatementParser.StatementType;
+import com.google.rpc.RetryInfo;
 import com.google.spanner.v1.ResultSetStats;
+import io.grpc.Metadata;
+import io.grpc.StatusRuntimeException;
+import io.grpc.protobuf.ProtoUtils;
 import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Collections;
@@ -98,7 +102,8 @@ public void commit() {
         case ABORT:
           state = TransactionState.COMMIT_FAILED;
           commitBehavior = CommitBehavior.SUCCEED;
-          throw SpannerExceptionFactory.newSpannerException(ErrorCode.ABORTED, "commit aborted");
+          throw SpannerExceptionFactory.newSpannerException(
+              ErrorCode.ABORTED, "commit aborted", createAbortedExceptionWithMinimalRetry());
         default:
           throw new IllegalStateException();
       }
@@ -448,7 +453,9 @@ public void testRetry() {
       }
 
       // first abort, then do nothing
-      doThrow(SpannerExceptionFactory.newSpannerException(ErrorCode.ABORTED, "commit aborted"))
+      doThrow(
+              SpannerExceptionFactory.newSpannerException(
+                  ErrorCode.ABORTED, "commit aborted", createAbortedExceptionWithMinimalRetry()))
           .doNothing()
           .when(txManager)
           .commit();
@@ -677,4 +684,15 @@ public void testChecksumResultSetWithArray() {
     rs2.next();
     assertThat(rs1.getChecksum(), is(not(equalTo(rs2.getChecksum()))));
   }
+
+  private static StatusRuntimeException createAbortedExceptionWithMinimalRetry() {
+    Metadata.Key key = ProtoUtils.keyForProto(RetryInfo.getDefaultInstance());
+    Metadata trailers = new Metadata();
+    RetryInfo retryInfo =
+        RetryInfo.newBuilder()
+            .setRetryDelay(com.google.protobuf.Duration.newBuilder().setNanos(1).setSeconds(0L))
+            .build();
+    trailers.put(key, retryInfo);
+    return io.grpc.Status.ABORTED.asRuntimeException(trailers);
+  }
 }
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITClosedSessionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITClosedSessionTest.java
index 22dc4c5c459..f846dd3deb1 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITClosedSessionTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITClosedSessionTest.java
@@ -258,7 +258,7 @@ public void testTransactionManager() throws InterruptedException {
             break;
           }
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerAsyncTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerAsyncTest.java
index f487fe4b0b9..2ce0a5bc3fb 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerAsyncTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerAsyncTest.java
@@ -133,7 +133,7 @@ public ApiFuture apply(TransactionContext txn, Void input)
           assertThat(row.getBoolean(1)).isTrue();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetryAsync();
         }
       }
@@ -166,7 +166,7 @@ public ApiFuture apply(TransactionContext txn, Void input)
               .get();
           fail("Expected exception");
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetryAsync();
         } catch (ExecutionException e) {
           assertThat(e.getCause()).isInstanceOf(SpannerException.class);
@@ -211,7 +211,7 @@ public ApiFuture apply(TransactionContext txn, Void input) throws Exceptio
           manager.rollbackAsync();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetryAsync();
         }
       }
@@ -286,7 +286,7 @@ public ApiFuture apply(TransactionContext txn, Struct input)
           txn1Step2.commitAsync().get();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           // It is possible that it was txn2 that aborted.
           // In that case we should just retry without resetting anything.
           if (manager1.getState() == TransactionState.ABORTED) {
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerTest.java
index 3ea4a067717..870acda32dc 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITTransactionManagerTest.java
@@ -91,7 +91,7 @@ public void simpleInsert() throws InterruptedException {
           assertThat(row.getBoolean(1)).isTrue();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
@@ -115,7 +115,7 @@ public void invalidInsert() throws InterruptedException {
           manager.commit();
           fail("Expected exception");
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         } catch (SpannerException e) {
           // expected
@@ -145,7 +145,7 @@ public void rollback() throws InterruptedException {
           manager.rollback();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           txn = manager.resetForRetry();
         }
       }
@@ -188,7 +188,7 @@ public void abortAndRetry() throws InterruptedException {
           manager1.commit();
           break;
         } catch (AbortedException e) {
-          Thread.sleep(e.getRetryDelayInMillis() / 1000);
+          Thread.sleep(e.getRetryDelayInMillis());
           // It is possible that it was txn2 that aborted.
           // In that case we should just retry without resetting anything.
           if (manager1.getState() == TransactionState.ABORTED) {
diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml
index 5ee7143baf5..49b808c4e69 100644
--- a/grpc-google-cloud-spanner-admin-database-v1/pom.xml
+++ b/grpc-google-cloud-spanner-admin-database-v1/pom.xml
@@ -4,13 +4,13 @@
   4.0.0
   com.google.api.grpc
   grpc-google-cloud-spanner-admin-database-v1
-  4.0.0
+  4.0.1
   grpc-google-cloud-spanner-admin-database-v1
   GRPC library for grpc-google-cloud-spanner-admin-database-v1
   
     com.google.cloud
     google-cloud-spanner-parent
-    4.0.0
+    4.0.1
   
   
     
diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml
index 1ba5438a566..f858f6820b5 100644
--- a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml
+++ b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml
@@ -4,13 +4,13 @@
   4.0.0
   com.google.api.grpc
   grpc-google-cloud-spanner-admin-instance-v1
-  4.0.0
+  4.0.1
   grpc-google-cloud-spanner-admin-instance-v1
   GRPC library for grpc-google-cloud-spanner-admin-instance-v1
   
     com.google.cloud
     google-cloud-spanner-parent
-    4.0.0
+    4.0.1
   
   
     
diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml
index 15ec20312d7..fc3ccbc1c60 100644
--- a/grpc-google-cloud-spanner-v1/pom.xml
+++ b/grpc-google-cloud-spanner-v1/pom.xml
@@ -4,13 +4,13 @@
   4.0.0
   com.google.api.grpc
   grpc-google-cloud-spanner-v1
-  4.0.0
+  4.0.1
   grpc-google-cloud-spanner-v1
   GRPC library for grpc-google-cloud-spanner-v1
   
     com.google.cloud
     google-cloud-spanner-parent
-    4.0.0
+    4.0.1
   
   
     
diff --git a/pom.xml b/pom.xml
index 55f71d6c3cf..7ad17728e2c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
   com.google.cloud
   google-cloud-spanner-parent
   pom
-  4.0.0
+  4.0.1
   Google Cloud Spanner Parent
   https://github.com/googleapis/java-spanner
   
@@ -63,7 +63,7 @@
     UTF-8
     github
     google-cloud-spanner-parent
-    0.18.0
+    0.19.0
   
 
   
@@ -71,37 +71,37 @@
       
         com.google.api.grpc
         proto-google-cloud-spanner-admin-instance-v1
-        4.0.0
+        4.0.1
       
       
         com.google.api.grpc
         proto-google-cloud-spanner-v1
-        4.0.0
+        4.0.1
       
       
         com.google.api.grpc
         proto-google-cloud-spanner-admin-database-v1
-        4.0.0
+        4.0.1
       
       
         com.google.api.grpc
         grpc-google-cloud-spanner-v1
-        4.0.0
+        4.0.1
       
       
         com.google.api.grpc
         grpc-google-cloud-spanner-admin-instance-v1
-        4.0.0
+        4.0.1
       
       
         com.google.api.grpc
         grpc-google-cloud-spanner-admin-database-v1
-        4.0.0
+        4.0.1
       
       
         com.google.cloud
         google-cloud-spanner
-        4.0.0
+        4.0.1
       
 
       
diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml
index a67c464ecd0..b81c7d1b287 100644
--- a/proto-google-cloud-spanner-admin-database-v1/pom.xml
+++ b/proto-google-cloud-spanner-admin-database-v1/pom.xml
@@ -4,13 +4,13 @@
   4.0.0
   com.google.api.grpc
   proto-google-cloud-spanner-admin-database-v1
-  4.0.0
+  4.0.1
   proto-google-cloud-spanner-admin-database-v1
   PROTO library for proto-google-cloud-spanner-admin-database-v1
   
     com.google.cloud
     google-cloud-spanner-parent
-    4.0.0
+    4.0.1
   
   
     
diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml
index 5de65e2e2a6..8b8cf153f45 100644
--- a/proto-google-cloud-spanner-admin-instance-v1/pom.xml
+++ b/proto-google-cloud-spanner-admin-instance-v1/pom.xml
@@ -4,13 +4,13 @@
   4.0.0
   com.google.api.grpc
   proto-google-cloud-spanner-admin-instance-v1
-  4.0.0
+  4.0.1
   proto-google-cloud-spanner-admin-instance-v1
   PROTO library for proto-google-cloud-spanner-admin-instance-v1
   
     com.google.cloud
     google-cloud-spanner-parent
-    4.0.0
+    4.0.1
   
   
     
diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml
index d1251b3ff1b..bb6b2cf4aa3 100644
--- a/proto-google-cloud-spanner-v1/pom.xml
+++ b/proto-google-cloud-spanner-v1/pom.xml
@@ -4,13 +4,13 @@
   4.0.0
   com.google.api.grpc
   proto-google-cloud-spanner-v1
-  4.0.0
+  4.0.1
   proto-google-cloud-spanner-v1
   PROTO library for proto-google-cloud-spanner-v1
   
     com.google.cloud
     google-cloud-spanner-parent
-    4.0.0
+    4.0.1
   
   
     
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index b9c60d3097b..9b7f7029a9b 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -32,7 +32,7 @@
     
       com.google.cloud
       google-cloud-spanner
-      3.3.2
+      4.0.0
     
     
     
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index b3006bd7483..17f49744d1d 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -31,7 +31,7 @@
     
       com.google.cloud
       google-cloud-spanner
-      4.0.0
+      4.0.1
     
     
     
diff --git a/synth.metadata b/synth.metadata
index 016c9d8aef1..da820a34902 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -4,7 +4,7 @@
       "git": {
         "name": ".",
         "remote": "https://github.com/googleapis/java-spanner.git",
-        "sha": "326b8331d952005cf48eaa9efb054fab5918f7e9"
+        "sha": "864f2a27a102de20ac57e3382a6068d4cd844e0b"
       }
     },
     {
@@ -19,7 +19,7 @@
       "git": {
         "name": "synthtool",
         "remote": "https://github.com/googleapis/synthtool.git",
-        "sha": "f327d3b657a63ae4a8efd7f011a15eacae36b59c"
+        "sha": "1aeca92e4a38f47134cb955f52ea76f84f09ff88"
       }
     }
   ],
diff --git a/versions.txt b/versions.txt
index d07c027940c..7bc2981e725 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,10 +1,10 @@
 # Format:
 # module:released-version:current-version
 
-proto-google-cloud-spanner-admin-instance-v1:4.0.0:4.0.0
-proto-google-cloud-spanner-v1:4.0.0:4.0.0
-proto-google-cloud-spanner-admin-database-v1:4.0.0:4.0.0
-grpc-google-cloud-spanner-v1:4.0.0:4.0.0
-grpc-google-cloud-spanner-admin-instance-v1:4.0.0:4.0.0
-grpc-google-cloud-spanner-admin-database-v1:4.0.0:4.0.0
-google-cloud-spanner:4.0.0:4.0.0
\ No newline at end of file
+proto-google-cloud-spanner-admin-instance-v1:4.0.1:4.0.1
+proto-google-cloud-spanner-v1:4.0.1:4.0.1
+proto-google-cloud-spanner-admin-database-v1:4.0.1:4.0.1
+grpc-google-cloud-spanner-v1:4.0.1:4.0.1
+grpc-google-cloud-spanner-admin-instance-v1:4.0.1:4.0.1
+grpc-google-cloud-spanner-admin-database-v1:4.0.1:4.0.1
+google-cloud-spanner:4.0.1:4.0.1
\ No newline at end of file