Skip to content

Guarantee missing stream promise delivery (1.74.x backport) #12232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions netty/src/main/java/io/grpc/netty/NettyClientHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -738,14 +738,19 @@ public void operationComplete(ChannelFuture future) throws Exception {

// Attach the client stream to the HTTP/2 stream object as user data.
stream.setHttp2Stream(http2Stream);
promise.setSuccess();
} else {
// Otherwise, the stream has been cancelled and Netty is sending a
// RST_STREAM frame which causes it to purge pending writes from the
// flow-controller and delete the http2Stream. The stream listener has already
// been notified of cancellation so there is nothing to do.
//
// This process has been observed to fail in some circumstances, leaving listeners
// unanswered. Ensure that some exception has been delivered consistent with the
// implied RST_STREAM result above.
Status status = Status.INTERNAL.withDescription("unknown stream for connection");
promise.setFailure(status.asRuntimeException());
}
// Otherwise, the stream has been cancelled and Netty is sending a
// RST_STREAM frame which causes it to purge pending writes from the
// flow-controller and delete the http2Stream. The stream listener has already
// been notified of cancellation so there is nothing to do.

// Just forward on the success status to the original promise.
promise.setSuccess();
} else {
Throwable cause = future.cause();
if (cause instanceof StreamBufferingEncoder.Http2GoAwayException) {
Expand Down
4 changes: 2 additions & 2 deletions netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ public void cancelBufferedStreamShouldChangeClientStreamStatus() throws Exceptio
// Cancel the stream.
cancelStream(Status.CANCELLED);

assertTrue(createFuture.isSuccess());
assertFalse(createFuture.isSuccess());
verify(streamListener).closed(eq(Status.CANCELLED), same(PROCESSED), any(Metadata.class));
}

Expand Down Expand Up @@ -311,7 +311,7 @@ public void cancelWhileBufferedShouldSucceed() throws Exception {
ChannelFuture cancelFuture = cancelStream(Status.CANCELLED);
assertTrue(cancelFuture.isSuccess());
assertTrue(createFuture.isDone());
assertTrue(createFuture.isSuccess());
assertFalse(createFuture.isSuccess());
}

/**
Expand Down