diff --git a/core/src/main/java/io/grpc/internal/ClientCallImpl.java b/core/src/main/java/io/grpc/internal/ClientCallImpl.java index 07f2701d1c..b9e8dd79f0 100644 --- a/core/src/main/java/io/grpc/internal/ClientCallImpl.java +++ b/core/src/main/java/io/grpc/internal/ClientCallImpl.java @@ -561,7 +561,11 @@ final class ClientCallImpl extends ClientCall { } private void closeObserver(Listener observer, Status status, Metadata trailers) { - observer.onClose(status, trailers); + try { + observer.onClose(status, trailers); + } catch (RuntimeException ex) { + log.log(Level.WARNING, "Exception thrown by onClose() in ClientCall", ex); + } } @Override diff --git a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java index 66d626ec2b..03e613e13d 100644 --- a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java +++ b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java @@ -1105,6 +1105,32 @@ public class ClientCallImplTest { assertEquals(attrs, call.getAttributes()); } + @Test + public void onCloseExceptionCaughtAndLogged() { + DelayedExecutor executor = new DelayedExecutor(); + ClientCallImpl call = new ClientCallImpl<>( + method, + executor, + baseCallOptions, + clientStreamProvider, + deadlineCancellationExecutor, + channelCallTracer, configSelector); + + call.start(callListener, new Metadata()); + verify(stream).start(listenerArgumentCaptor.capture()); + final ClientStreamListener streamListener = listenerArgumentCaptor.getValue(); + streamListener.headersRead(new Metadata()); + + doThrow(new RuntimeException("Exception thrown by onClose() in ClientCall")).when(callListener) + .onClose(any(Status.class), any(Metadata.class)); + + Status status = Status.RESOURCE_EXHAUSTED.withDescription("simulated"); + streamListener.closed(status, PROCESSED, new Metadata()); + executor.release(); + + verify(callListener).onClose(same(status), any(Metadata.class)); + } + private static final class DelayedExecutor implements Executor { private final BlockingQueue commands = new LinkedBlockingQueue<>();