Skip to content

Commit 45b8b0e

Browse files
committed
netty: Abrupt GOAWAY should not cause INTERNAL status
The stream creation was failing because the stream id was disallowed: Caused by: io.grpc.StatusRuntimeException: INTERNAL: http2 exception at io.grpc.Status.asRuntimeException(Status.java:533) at io.grpc.stub.ClientCalls$BlockingResponseStream.hasNext(ClientCalls.java:629) ... 16 more Caused by: io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception$StreamException: Cannot create stream 222691 greater than Last-Stream-ID 222689 from GOAWAY. The problem was introduced in 9ead606. Fixes #7357
1 parent f5c7f4e commit 45b8b0e

File tree

2 files changed

+34
-0
lines changed

2 files changed

+34
-0
lines changed

netty/src/main/java/io/grpc/netty/NettyClientHandler.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ protected void handleNotInUse() {
129129
private Http2Ping ping;
130130
private Attributes attributes;
131131
private InternalChannelz.Security securityInfo;
132+
private Status abruptGoAwayStatus;
132133

133134
static NettyClientHandler newHandler(
134135
ClientTransportLifecycleManager lifecycleManager,
@@ -556,6 +557,21 @@ private void createStream(CreateStreamCommand command, ChannelPromise promise)
556557
}
557558
return;
558559
}
560+
if (connection().goAwayReceived()
561+
&& streamId > connection().local().lastStreamKnownByPeer()) {
562+
// This should only be reachable during onGoAwayReceived, as otherwise
563+
// getShutdownThrowable() != null
564+
command.stream().setNonExistent();
565+
Status s = abruptGoAwayStatus;
566+
if (s == null) {
567+
// Should be impossible, but handle psuedo-gracefully
568+
s = Status.INTERNAL.withDescription(
569+
"Failed due to abrupt GOAWAY, but can't find GOAWAY details");
570+
}
571+
command.stream().transportReportStatus(s, RpcProgress.REFUSED, true, new Metadata());
572+
promise.setFailure(s.asRuntimeException());
573+
return;
574+
}
559575

560576
NettyClientStream.TransportState stream = command.stream();
561577
Http2Headers headers = command.headers();
@@ -772,6 +788,7 @@ public boolean visit(Http2Stream stream) throws Http2Exception {
772788
*/
773789
private void goingAway(Status status) {
774790
lifecycleManager.notifyGracefulShutdown(status);
791+
abruptGoAwayStatus = status;
775792
// Try to allocate as many in-flight streams as possible, to reduce race window of
776793
// https://github.com/grpc/grpc-java/issues/2562 . To be of any help, the server has to
777794
// gracefully shut down the connection with two GOAWAYs. gRPC servers generally send a PING

netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,23 @@ public void receivedGoAwayShouldNotAffectRacingQueuedStreamId() throws Exception
375375
assertTrue(future.isDone());
376376
}
377377

378+
@Test
379+
public void receivedAbruptGoAwayShouldFailRacingQueuedStreamid() throws Exception {
380+
// This command has not actually been executed yet
381+
ChannelFuture future = writeQueue().enqueue(
382+
newCreateStreamCommand(grpcHeaders, streamTransportState), true);
383+
// Read a GOAWAY that indicates our stream can't be sent
384+
channelRead(goAwayFrame(0, 8 /* Cancel */, Unpooled.copiedBuffer("this is a test", UTF_8)));
385+
386+
ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
387+
verify(streamListener).closed(captor.capture(), same(REFUSED),
388+
ArgumentMatchers.<Metadata>notNull());
389+
assertEquals(Status.CANCELLED.getCode(), captor.getValue().getCode());
390+
assertEquals("HTTP/2 error code: CANCEL\nReceived Goaway\nthis is a test",
391+
captor.getValue().getDescription());
392+
assertTrue(future.isDone());
393+
}
394+
378395
@Test
379396
public void receivedResetWithRefuseCode() throws Exception {
380397
ChannelFuture future = enqueue(newCreateStreamCommand(grpcHeaders, streamTransportState));

0 commit comments

Comments
 (0)