Skip to content

Commit 2addb19

Browse files
fix: retry aborted errors for writeAtLeastOnce (#2627)
* fix: retry aborted errors for writeAtLeastOnce The `writeAtLeastOnce` method could fail with an Aborted error if Cloud Spanner would abort the transaction during the single Commit RPC invocation that this method executes. This can for example happen if a schema change is executed while this method is being called. Fixes #2626 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 9964bd5 commit 2addb19

File tree

2 files changed

+41
-3
lines changed

2 files changed

+41
-3
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionImpl.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,11 @@ public CommitResponse writeAtLeastOnceWithOptions(
182182
}
183183
requestBuilder.setRequestOptions(requestOptionsBuilder.build());
184184
}
185+
CommitRequest request = requestBuilder.build();
185186
Span span = tracer.spanBuilder(SpannerImpl.COMMIT).startSpan();
186187
try (Scope s = tracer.withSpan(span)) {
187-
com.google.spanner.v1.CommitResponse response =
188-
spanner.getRpc().commit(requestBuilder.build(), this.options);
189-
return new CommitResponse(response);
188+
return SpannerRetryHelper.runTxWithRetriesOnAborted(
189+
() -> new CommitResponse(spanner.getRpc().commit(request, this.options)));
190190
} catch (RuntimeException e) {
191191
TraceUtil.setWithFailure(span, e);
192192
throw e;

google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java

+38
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,44 @@ public void testWrite() {
525525
assertEquals(Priority.PRIORITY_UNSPECIFIED, commit.getRequestOptions().getPriority());
526526
}
527527

528+
@Test
529+
public void testWriteAborted() {
530+
DatabaseClient client =
531+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
532+
// Force the Commit RPC to return Aborted the first time it is called. The exception is cleared
533+
// after the first call, so the retry should succeed.
534+
mockSpanner.setCommitExecutionTime(
535+
SimulatedExecutionTime.ofException(
536+
mockSpanner.createAbortedException(ByteString.copyFromUtf8("test"))));
537+
Timestamp timestamp =
538+
client.write(
539+
Collections.singletonList(
540+
Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build()));
541+
assertNotNull(timestamp);
542+
543+
List<CommitRequest> commitRequests = mockSpanner.getRequestsOfType(CommitRequest.class);
544+
assertEquals(2, commitRequests.size());
545+
}
546+
547+
@Test
548+
public void testWriteAtLeastOnceAborted() {
549+
DatabaseClient client =
550+
spanner.getDatabaseClient(DatabaseId.of(TEST_PROJECT, TEST_INSTANCE, TEST_DATABASE));
551+
// Force the Commit RPC to return Aborted the first time it is called. The exception is cleared
552+
// after the first call, so the retry should succeed.
553+
mockSpanner.setCommitExecutionTime(
554+
SimulatedExecutionTime.ofException(
555+
mockSpanner.createAbortedException(ByteString.copyFromUtf8("test"))));
556+
Timestamp timestamp =
557+
client.writeAtLeastOnce(
558+
Collections.singletonList(
559+
Mutation.newInsertBuilder("FOO").set("ID").to(1L).set("NAME").to("Bar").build()));
560+
assertNotNull(timestamp);
561+
562+
List<CommitRequest> commitRequests = mockSpanner.getRequestsOfType(CommitRequest.class);
563+
assertEquals(2, commitRequests.size());
564+
}
565+
528566
@Test
529567
public void testWriteWithOptions() {
530568
DatabaseClient client =

0 commit comments

Comments
 (0)