| commit a37d3eb349e048b953633027ed011cda8b68c603 | 
 | Author: George Gensure <werkt0@gmail.com> | 
 | Date:   Thu Jul 10 09:49:54 2025 -0400 | 
 |  | 
 |     Guarantee missing stream promise delivery | 
 |      | 
 |     In observed cases, whether RST_STREAM or another failure from netty or | 
 |     the server, listeners can fail to be notified when a connection yields a | 
 |     null stream for the selected streamId. This causes hangs in clients, | 
 |     despite deadlines, with no obvious resolution. | 
 |      | 
 |     Tests which relied upon this promise succeeding must now change. | 
 |  | 
 | diff --git a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java | 
 | index a5fa0f800..276fa623c 100644 | 
 | --- a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java | 
 | +++ b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java | 
 | @@ -738,14 +738,19 @@ class NettyClientHandler extends AbstractNettyHandler { | 
 |   | 
 |                  // 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) { | 
 | diff --git a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java | 
 | index f8fbeea9b..dd4fcb4ea 100644 | 
 | --- a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java | 
 | +++ b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java | 
 | @@ -268,7 +268,7 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase<NettyClientHand | 
 |      // Cancel the stream. | 
 |      cancelStream(Status.CANCELLED); | 
 |   | 
 | -    assertTrue(createFuture.isSuccess()); | 
 | +    assertFalse(createFuture.isSuccess()); | 
 |      verify(streamListener).closed(eq(Status.CANCELLED), same(PROCESSED), any(Metadata.class)); | 
 |    } | 
 |   | 
 | @@ -311,7 +311,7 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase<NettyClientHand | 
 |      ChannelFuture cancelFuture = cancelStream(Status.CANCELLED); | 
 |      assertTrue(cancelFuture.isSuccess()); | 
 |      assertTrue(createFuture.isDone()); | 
 | -    assertTrue(createFuture.isSuccess()); | 
 | +    assertFalse(createFuture.isSuccess()); | 
 |    } | 
 |   | 
 |    /** |