Skip to content

Commit d5372e6

Browse files
rahul2393olavloitegcf-owl-bot[bot]
authored
fix: retry on RST_STREAM internal error (#2111)
* fix: retry on RST_STREAM internal error * Update google-cloud-spanner/src/test/java/com/google/cloud/spanner/IsRetryableInternalErrorTest.java Co-authored-by: Knut Olav Løite <[email protected]> * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Knut Olav Løite <[email protected]> Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent a40bda9 commit d5372e6

File tree

4 files changed

+57
-1
lines changed

4 files changed

+57
-1
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ If you are using Maven without BOM, add this to your dependencies:
4949
If you are using Gradle 5.x or later, add this to your dependencies:
5050

5151
```Groovy
52-
implementation platform('com.google.cloud:libraries-bom:26.2.0')
52+
implementation platform('com.google.cloud:libraries-bom:26.3.0')
5353
5454
implementation 'com.google.cloud:google-cloud-spanner'
5555
```

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

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public class IsRetryableInternalError implements Predicate<Throwable> {
2929
private static final String EOS_ERROR_MESSAGE =
3030
"Received unexpected EOS on DATA frame from server";
3131

32+
private static final String RST_STREAM_ERROR_MESSAGE = "stream terminated by RST_STREAM";
33+
3234
@Override
3335
public boolean apply(Throwable cause) {
3436
if (isInternalError(cause)) {
@@ -38,6 +40,8 @@ public boolean apply(Throwable cause) {
3840
return true;
3941
} else if (cause.getMessage().contains(EOS_ERROR_MESSAGE)) {
4042
return true;
43+
} else if (cause.getMessage().contains(RST_STREAM_ERROR_MESSAGE)) {
44+
return true;
4145
}
4246
}
4347
return false;

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

+13
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.cloud.spanner;
1818

1919
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.assertTrue;
2021

2122
import com.google.api.gax.grpc.GrpcStatusCode;
2223
import com.google.api.gax.rpc.InternalException;
@@ -114,6 +115,18 @@ public void genericInternalStatusRuntimeExceptionIsRetryable() {
114115
assertThat(predicate.apply(e)).isFalse();
115116
}
116117

118+
@Test
119+
public void rstStreamInternalExceptionIsRetryable() {
120+
final InternalException e =
121+
new InternalException(
122+
"INTERNAL: stream terminated by RST_STREAM.",
123+
null,
124+
GrpcStatusCode.of(Code.INTERNAL),
125+
false);
126+
127+
assertTrue(predicate.apply(e));
128+
}
129+
117130
@Test
118131
public void genericInternalExceptionIsNotRetryable() {
119132
final InternalException e =

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

+39
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,45 @@ public void testExecuteStreamingPartitionedUpdateUnexpectedEOS() {
344344
Mockito.eq(executeRequestWithResumeToken), anyMap(), any(Duration.class));
345345
}
346346

347+
@Test
348+
public void testExecuteStreamingPartitionedUpdateRSTstream() {
349+
ResultSetStats stats = ResultSetStats.newBuilder().setRowCountLowerBound(1000L).build();
350+
PartialResultSet p1 = PartialResultSet.newBuilder().setResumeToken(resumeToken).build();
351+
PartialResultSet p2 = PartialResultSet.newBuilder().setStats(stats).build();
352+
ServerStream<PartialResultSet> stream1 = mock(ServerStream.class);
353+
Iterator<PartialResultSet> iterator = mock(Iterator.class);
354+
when(iterator.hasNext()).thenReturn(true, true, false);
355+
when(iterator.next())
356+
.thenReturn(p1)
357+
.thenThrow(
358+
new InternalException(
359+
"INTERNAL: stream terminated by RST_STREAM.",
360+
null,
361+
GrpcStatusCode.of(Code.INTERNAL),
362+
true));
363+
when(stream1.iterator()).thenReturn(iterator);
364+
ServerStream<PartialResultSet> stream2 = mock(ServerStream.class);
365+
when(stream2.iterator()).thenReturn(ImmutableList.of(p1, p2).iterator());
366+
when(rpc.executeStreamingPartitionedDml(
367+
Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class)))
368+
.thenReturn(stream1);
369+
when(rpc.executeStreamingPartitionedDml(
370+
Mockito.eq(executeRequestWithResumeToken), anyMap(), any(Duration.class)))
371+
.thenReturn(stream2);
372+
373+
PartitionedDmlTransaction tx = new PartitionedDmlTransaction(session, rpc, ticker);
374+
long count = tx.executeStreamingPartitionedUpdate(Statement.of(sql), Duration.ofMinutes(10));
375+
376+
assertThat(count).isEqualTo(1000L);
377+
verify(rpc).beginTransaction(any(BeginTransactionRequest.class), anyMap());
378+
verify(rpc)
379+
.executeStreamingPartitionedDml(
380+
Mockito.eq(executeRequestWithoutResumeToken), anyMap(), any(Duration.class));
381+
verify(rpc)
382+
.executeStreamingPartitionedDml(
383+
Mockito.eq(executeRequestWithResumeToken), anyMap(), any(Duration.class));
384+
}
385+
347386
@Test
348387
public void testExecuteStreamingPartitionedUpdateGenericInternalException() {
349388
PartialResultSet p1 = PartialResultSet.newBuilder().setResumeToken(resumeToken).build();

0 commit comments

Comments
 (0)