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]

Reply via email to