@@ -276,8 +276,12 @@ private <T> ListenableFuture<T> sendRequestWithOpenChannel(NettyResponseFuture<T
276276
277277 // channelInactive might be called between isChannelValid and writeRequest
278278 // so if we don't store the Future now, channelInactive won't perform
279- // handleUnexpectedClosedChannel
280- Channels .setAttribute (channel , future );
279+ // handleUnexpectedClosedChannel.
280+ // For HTTP/2 parent channels, skip this — each stream channel gets its own attribute
281+ // in openHttp2Stream(), and setting it on the parent would corrupt multiplexed state.
282+ if (!ChannelManager .isHttp2 (channel )) {
283+ Channels .setAttribute (channel , future );
284+ }
281285
282286 if (Channels .isChannelActive (channel )) {
283287 writeRequest (future , channel );
@@ -485,7 +489,6 @@ public <T> void writeRequest(NettyResponseFuture<T> future, Channel channel) {
485489 */
486490 private <T > void writeHttp2Request (NettyResponseFuture <T > future , Channel parentChannel ) {
487491 Http2ConnectionState state = parentChannel .attr (Http2ConnectionState .HTTP2_STATE_KEY ).get ();
488- Runnable openStream = () -> openHttp2Stream (future , parentChannel );
489492
490493 if (state != null && !state .tryAcquireStream ()) {
491494 if (state .isDraining ()) {
@@ -495,13 +498,13 @@ private <T> void writeHttp2Request(NettyResponseFuture<T> future, Channel parent
495498 return ;
496499 }
497500 // Queue for later when a stream slot opens up
498- state .addPendingOpener (openStream );
501+ state .addPendingOpener (() -> openHttp2Stream ( future , parentChannel , state ) );
499502 return ;
500503 }
501- openStream . run ( );
504+ openHttp2Stream ( future , parentChannel , state );
502505 }
503506
504- private <T > void openHttp2Stream (NettyResponseFuture <T > future , Channel parentChannel ) {
507+ private <T > void openHttp2Stream (NettyResponseFuture <T > future , Channel parentChannel , Http2ConnectionState state ) {
505508 new Http2StreamChannelBootstrap (parentChannel )
506509 .handler (new ChannelInitializer <Http2StreamChannel >() {
507510 @ Override
@@ -519,6 +522,7 @@ protected void initChannel(Http2StreamChannel streamCh) {
519522 Http2StreamChannel streamChannel = f .getNow ();
520523 channelManager .registerOpenChannel (streamChannel );
521524 Channels .setAttribute (streamChannel , future );
525+ Channels .setActiveToken (streamChannel );
522526 future .attachChannel (streamChannel , false );
523527 try {
524528 AsyncHandler <T > asyncHandler = future .getAsyncHandler ();
@@ -541,6 +545,10 @@ protected void initChannel(Http2StreamChannel streamCh) {
541545 abort (streamChannel , future , e );
542546 }
543547 } else {
548+ // Stream channel was never opened — release the acquired stream slot
549+ if (state != null ) {
550+ state .releaseStream ();
551+ }
544552 abort (parentChannel , future , f .cause ());
545553 }
546554 });
0 commit comments