chaoyoung opened a new issue, #16389: URL: https://github.com/apache/dubbo/issues/16389
### Pre-check - [x] I am sure that all the content I provide is in English. ### Search before asking - [x] I had searched in the [issues](https://github.com/apache/dubbo/issues?q=is%3Aissue) and found no similar issues. ### Apache Dubbo Component Java SDK (apache/dubbo) ### Dubbo Version - Apache Dubbo Java: `3.3.6` - Netty: `4.2.15.Final` - JDK: OpenJDK 25 - Operating system: Linux container The same result is reproducible with Netty `4.2.9.Final`. ### Steps to reproduce this issue `NettyHttp2FrameCodec` wraps inbound HTTP/2 DATA content in a `ByteBufInputStream` with `releaseOnClose=true`. The following program shows that input arriving after the Dubbo streaming decoder is closed is ignored without being closed: ```java import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.PooledByteBufAllocator; import org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcStreamingDecoder; import java.io.InputStream; public final class ClosedDecoderInputOwnershipReproducer { public static void main(String[] args) { GrpcStreamingDecoder decoder = new GrpcStreamingDecoder(); decoder.onStreamClosed(); ByteBuf buffer = PooledByteBufAllocator.DEFAULT.directBuffer(1); buffer.writeByte(0); InputStream lateInput = new ByteBufInputStream(buffer, true); try { decoder.decode(lateInput); System.out.println("refCnt after ignored input = " + buffer.refCnt()); } finally { // Test cleanup only. Dubbo did not release this buffer. if (buffer.refCnt() > 0) { buffer.release(buffer.refCnt()); } } } } ``` Actual output: ```text refCnt after ignored input = 1 ``` The `finally` block only prevents the reproducer itself from leaking direct memory. It is not part of the behavior under test. The corresponding server-side sequence is: 1. A Triple HTTP/2 stream is initialized. 2. Its streaming decoder reaches the closing or closed state. 3. An in-flight or late DATA frame reaches the stream channel. 4. `NettyHttp2FrameCodec` wraps the content in `ByteBufInputStream(content, true)`. 5. `LengthFieldStreamingDecoder.decode()` returns because `closing || closed` is true. 6. The input is not closed, and the DATA `ByteBuf` remains at `refCnt == 1`. ### What you expected to happen Dubbo should release every inbound HTTP/2 DATA buffer exactly once, including DATA received after the streaming decoder has entered a closing or closed state. Expected output: ```text refCnt after ignored input = 0 ``` Actual behavior leaves the reference count at `1`. Netty leak detection later reports: ```text LEAK: ByteBuf.release() was not called before it's garbage-collected. ``` ### Anything else This occurs at low frequency in a Triple server. A representative leak trace is: ```text LEAK: ByteBuf.release() was not called before it's garbage-collected. Hint: 'NettyHttp2FrameCodec#0' will handle the message from this point. io.netty.handler.codec.http2.DefaultHttp2DataFrame.touch(DefaultHttp2DataFrame.java:174) ... org.apache.dubbo.rpc.protocol.tri.transport.TripleServerConnectionHandler.channelRead( TripleServerConnectionHandler.java:72) ... io.netty.handler.codec.http2.Http2FrameCodec$FrameListener.onDataRead(Http2FrameCodec.java:661) ``` The ownership gap in Dubbo `3.3.6` is: 1. [`NettyHttp2FrameCodec.onHttp2DataFrame()`](https://github.com/apache/dubbo/blob/dubbo-3.3.6/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h2/NettyHttp2FrameCodec.java#L159-L162) transfers DATA content into `new ByteBufInputStream(dataFrame.content(), true)`. 2. [`LengthFieldStreamingDecoder.decode()`](https://github.com/apache/dubbo/blob/dubbo-3.3.6/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/LengthFieldStreamingDecoder.java#L62-L69) returns on `closing || closed` without closing or retaining the input. 3. [`GenericHttp2ServerTransportListener.onDataFinally()`](https://github.com/apache/dubbo/blob/dubbo-3.3.6/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java#L167-L169) is empty, so there is no fallback `message.close()` for input rejected by the decoder. Normal input is added to `CompositeInputStream` and closed after consumption or decoder shutdown. Input rejected before `accumulate.addInputStream(inputStream)` loses its owner. The same deterministic probe was run 10,000 times with each Netty version: | Netty version | Iterations | Retained buffers | | --- | ---: | ---: | | `4.2.15.Final` | 10,000 | 10,000 | | `4.2.9.Final` | 10,000 | 10,000 | `Http2FrameCodec.onDataRead` and `DefaultHttp2DataFrame` have identical source content in those two Netty releases, so this does not appear to be a Netty regression between 4.2.9 and 4.2.15. Related historical issues [#12339](https://github.com/apache/dubbo/issues/12339) / [#12805](https://github.com/apache/dubbo/pull/12805) and [#14070](https://github.com/apache/dubbo/issues/14070) / [#14071](https://github.com/apache/dubbo/pull/14071) concern older Triple implementations and do not cover this closed-decoder input path. A possible fix is to close input rejected because the decoder is closing or closed while preserving `CompositeInputStream` ownership for accepted input. ### Do you have a (mini) reproduction demo? - [x] Yes, I have a minimal reproduction demo to help resolve this issue more effectively! ### Are you willing to submit a pull request to fix on your own? - [ ] Yes I am willing to submit a pull request on my own! ### Code of Conduct - [x] I agree to follow this project's [Code of Conduct](https://www.apache.org/foundation/policies/conduct) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
