diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/http2/HttpOrHttp2ChannelPool.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/http2/HttpOrHttp2ChannelPool.java index 5815cab683c6..c84df0c8b2da 100644 --- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/http2/HttpOrHttp2ChannelPool.java +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/http2/HttpOrHttp2ChannelPool.java @@ -51,7 +51,7 @@ public class HttpOrHttp2ChannelPool implements SdkChannelPool { private boolean protocolImplPromiseInitializationStarted = false; private Promise protocolImplPromise; - private BetterFixedChannelPool protocolImpl; + private volatile BetterFixedChannelPool protocolImpl; private boolean closed; public HttpOrHttp2ChannelPool(ChannelPool delegatePool, @@ -194,9 +194,12 @@ public Future release(Channel channel) { @Override public Future release(Channel channel, Promise promise) { - doInEventLoop(eventLoop, - () -> release0(channel, promise), - promise); + // If protocolImpl != null, it’s already visible, so we call protocolImpl.release + // directly in the channel event loop. Otherwise — whether unassigned or not yet + // visible — we fall back to the safe path. Since protocolImpl is assigned only + // once, there's no risk of calling it on an incorrect instance. + if (protocolImpl != null) protocolImpl.release(channel, promise); + else doInEventLoop(eventLoop, () -> release0(channel, promise), promise); return promise; }