Skip to content

Commit 525f916

Browse files
igorbernstein2pongad
authored andcommitted
BIgtable: bug fix - Fix Reframer error handling (googleapis#2920)
1 parent 18bdfb6 commit 525f916

3 files changed

Lines changed: 102 additions & 2 deletions

File tree

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/reframing/ReframingResponseObserver.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,23 @@ private void onCancel() {
193193
*/
194194
@Override
195195
protected void onResponseImpl(InnerT response) {
196+
boolean shoudCancelStream = false;
197+
196198
synchronized (lock) {
197199
Preconditions.checkState(awaitingInner, "Received unsolicited response from upstream");
198200
awaitingInner = false;
199-
reframer.push(response);
201+
202+
try {
203+
reframer.push(response);
204+
} catch (Throwable t) {
205+
if (error == null) {
206+
shoudCancelStream = true;
207+
error = t;
208+
}
209+
}
210+
}
211+
if (shoudCancelStream) {
212+
innerController.cancel();
200213
}
201214
deliver();
202215
}
@@ -262,6 +275,7 @@ private void deliver() {
262275

263276
if (forceClose) {
264277
outerResponseObserver.onError(t);
278+
innerController.cancel();
265279
}
266280
}
267281
}

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/reframing/ReframingResponseObserverTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.google.cloud.bigtable.gaxx.reframing;
1717

1818
import com.google.cloud.bigtable.gaxx.testing.FakeStreamingApi.ServerStreamingStashCallable;
19+
import com.google.cloud.bigtable.gaxx.testing.FakeStreamingApi.ServerStreamingStashCallable.StreamControllerStash;
1920
import com.google.cloud.bigtable.gaxx.testing.MockStreamingApi.MockResponseObserver;
2021
import com.google.cloud.bigtable.gaxx.testing.MockStreamingApi.MockServerStreamingCall;
2122
import com.google.cloud.bigtable.gaxx.testing.MockStreamingApi.MockServerStreamingCallable;
@@ -34,6 +35,7 @@
3435
import java.util.concurrent.Executors;
3536
import java.util.concurrent.Future;
3637
import java.util.concurrent.TimeUnit;
38+
import java.util.concurrent.atomic.AtomicInteger;
3739
import org.junit.After;
3840
import org.junit.Before;
3941
import org.junit.Test;
@@ -311,6 +313,67 @@ public void run() {
311313
Truth.assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
312314
}
313315

316+
@Test
317+
public void testReframerPushError() throws Exception {
318+
MockResponseObserver<String> outerObserver = new MockResponseObserver<>(true);
319+
Reframer<String, String> reframer =
320+
new DasherizingReframer(1) {
321+
@Override
322+
public void push(String response) {
323+
if (response.equals("boom")) {
324+
throw new IllegalStateException("fake error");
325+
}
326+
super.push(response);
327+
}
328+
};
329+
330+
ReframingResponseObserver<String, String> middleware =
331+
new ReframingResponseObserver<>(outerObserver, reframer);
332+
ServerStreamingStashCallable<String, String> innerCallable =
333+
new ServerStreamingStashCallable<>(ImmutableList.of("a", "boom", "c"));
334+
335+
innerCallable.call("request", middleware);
336+
337+
Truth.assertThat(outerObserver.getFinalError()).isInstanceOf(IllegalStateException.class);
338+
Truth.assertThat(outerObserver.getFinalError()).hasMessage("fake error");
339+
Truth.assertThat(outerObserver.popNextResponse()).isEqualTo("a");
340+
Truth.assertThat(outerObserver.popNextResponse()).isNull();
341+
}
342+
343+
@Test
344+
public void testReframerPopError() {
345+
final AtomicInteger popCount = new AtomicInteger();
346+
347+
MockResponseObserver<String> outerObserver = new MockResponseObserver<>(true);
348+
Reframer<String, String> reframer =
349+
new DasherizingReframer(1) {
350+
@Override
351+
public String pop() {
352+
if (popCount.incrementAndGet() == 2) {
353+
throw new IllegalStateException("fake error");
354+
}
355+
return super.pop();
356+
}
357+
};
358+
359+
ReframingResponseObserver<String, String> middleware =
360+
new ReframingResponseObserver<>(outerObserver, reframer);
361+
ServerStreamingStashCallable<String, String> innerCallable =
362+
new ServerStreamingStashCallable<>(ImmutableList.of("a", "boom", "c"));
363+
364+
innerCallable.call("request", middleware);
365+
StreamControllerStash<String> lastCall = innerCallable.popLastCall();
366+
367+
Truth.assertThat(outerObserver.getFinalError()).isInstanceOf(IllegalStateException.class);
368+
Truth.assertThat(outerObserver.getFinalError()).hasMessage("fake error");
369+
Truth.assertThat(outerObserver.popNextResponse()).isEqualTo("a");
370+
Truth.assertThat(outerObserver.popNextResponse()).isNull();
371+
Truth.assertThat(popCount.get()).isEqualTo(2);
372+
373+
Truth.assertThat(lastCall.getError()).isInstanceOf(CancellationException.class);
374+
Truth.assertThat(lastCall.getNumDelivered()).isEqualTo(2);
375+
}
376+
314377
/**
315378
* A simple implementation of a {@link Reframer}. The input string is split by dash, and the
316379
* output is concatenated by dashes. The test can verify M:N behavior by adjusting the

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/testing/FakeStreamingApi.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
import java.util.ArrayList;
3131
import java.util.List;
3232
import java.util.Queue;
33+
import java.util.concurrent.BlockingQueue;
3334
import java.util.concurrent.CancellationException;
35+
import java.util.concurrent.TimeUnit;
3436

3537
@InternalApi("for testing")
3638
public class FakeStreamingApi {
@@ -118,6 +120,7 @@ public static class ServerStreamingStashCallable<RequestT, ResponseT>
118120
private ResponseObserver<ResponseT> actualObserver;
119121
private RequestT actualRequest;
120122
private List<ResponseT> responseList;
123+
private BlockingQueue<StreamControllerStash<ResponseT>> calls = Queues.newLinkedBlockingQueue();
121124

122125
public ServerStreamingStashCallable() {
123126
responseList = new ArrayList<>();
@@ -138,9 +141,19 @@ public void call(
138141

139142
StreamControllerStash<ResponseT> controller =
140143
new StreamControllerStash<>(responseList, responseObserver);
144+
calls.add(controller);
141145
controller.start();
142146
}
143147

148+
public StreamControllerStash<ResponseT> popLastCall() {
149+
try {
150+
return calls.poll(1, TimeUnit.SECONDS);
151+
} catch (InterruptedException e) {
152+
Thread.currentThread().interrupt();
153+
throw new RuntimeException(e);
154+
}
155+
}
156+
144157
public ApiCallContext getContext() {
145158
return context;
146159
}
@@ -154,11 +167,12 @@ public RequestT getActualRequest() {
154167
}
155168

156169
// Minimal implementation of back pressure aware stream controller. Not threadsafe
157-
private static class StreamControllerStash<ResponseT> implements StreamController {
170+
public static class StreamControllerStash<ResponseT> implements StreamController {
158171
final ResponseObserver<ResponseT> observer;
159172
final Queue<ResponseT> queue;
160173
boolean autoFlowControl = true;
161174
long numPending;
175+
long numDelivered;
162176
Throwable error;
163177
boolean delivering, closed;
164178

@@ -168,6 +182,14 @@ public StreamControllerStash(
168182
this.queue = Queues.newArrayDeque(responseList);
169183
}
170184

185+
public Throwable getError() {
186+
return error;
187+
}
188+
189+
public long getNumDelivered() {
190+
return numDelivered;
191+
}
192+
171193
public void start() {
172194
observer.onStart(this);
173195
if (autoFlowControl) {
@@ -200,6 +222,7 @@ private void deliver() {
200222
try {
201223
while (error == null && numPending > 0 && !queue.isEmpty()) {
202224
numPending--;
225+
numDelivered++;
203226
observer.onResponse(queue.poll());
204227
}
205228

0 commit comments

Comments
 (0)