Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@EarthChen
Copy link
Member

@EarthChen EarthChen commented Jan 23, 2026

What is the purpose of the change?

The current PR is based on #16041, and PR #16041 needs to be merged first.

Checklist

  • Make sure there is a GitHub_issue field for the change.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Write necessary unit-test to verify your logic correction. If the new feature or significant change is committed, please remember to add sample in dubbo samples project.
  • Make sure gitHub actions can pass. Why the workflow is failing and how to fix it?

This commit unifies the deframer implementation between client and server
sides for the Triple protocol, eliminating code duplication and improving
maintainability.

## Key Changes

### Deleted Files
- TriDecoder.java: Removed client-specific deframer implementation
- Deframer.java: Removed client deframer interface
- RecordListener.java: Removed test helper
- TriDecoderTest.java: Removed corresponding test

### Core Modifications

1. **LengthFieldStreamingDecoder**: Added BoundedInputStream to support:
   - Message boundary isolation (prevents reading beyond current message)
   - mark/reset support required by Hessian2 and other deserializers
   - Zero-copy optimization by reading directly from accumulate stream

2. **StreamingDecoder.FragmentListener**: Added messageLength parameter to
   onFragmentMessage() method for better flow control

3. **AbstractTripleClientStream**: Migrated from TriDecoder to GrpcStreamingDecoder,
   using ByteBufInputStream to adapt Netty's ByteBuf to InputStream

4. **Stream.Listener.onMessage**: Changed parameter from byte[] to InputStream
   for unified handling and memory optimization

5. **PackableMethod**: Added default parseResponse(InputStream) method for
   backward compatibility

6. **CompositeInputStream**: Improved stream release logic to prevent exceptions

7. **DescriptorUtils**: Added mark() call before reading stream to support reset

## Architecture Change

Before:
- Client: ByteBuf -> TriDecoder (Netty-specific) -> byte[] -> deserializer
- Server: InputStream -> LengthFieldStreamingDecoder -> byte[] -> deserializer

After:
- Client: ByteBuf -> ByteBufInputStream -> GrpcStreamingDecoder -> BoundedInputStream -> deserializer
- Server: InputStream -> GrpcStreamingDecoder -> BoundedInputStream -> deserializer

## Bug Fix

Fixed BoundedInputStream.reset() not restoring the 'remaining' counter,
which caused streams to return EOF after mark/reset. This was the root cause
of POJO method invocation failures with "Unexpected serialization type:null" error.
@EarthChen EarthChen changed the title Refactor packable stream Refactor tri pack & unpack use stream Jan 23, 2026
@codecov-commenter
Copy link

codecov-commenter commented Jan 23, 2026

Codecov Report

❌ Patch coverage is 64.07767% with 74 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.34%. Comparing base (be40746) to head (7a35d21).

Files with missing lines Patch % Lines
...pc/protocol/tri/TripleCustomerProtocolWrapper.java 54.02% 31 Missing and 9 partials ⚠️
...ng/http12/message/LengthFieldStreamingDecoder.java 75.67% 6 Missing and 3 partials ⚠️
...java/org/apache/dubbo/rpc/model/WrapperUnPack.java 16.66% 5 Missing ⚠️
...g/apache/dubbo/rpc/protocol/tri/PbArrayPacker.java 0.00% 4 Missing ⚠️
...bbo/rpc/protocol/tri/ReflectionPackableMethod.java 81.81% 4 Missing ⚠️
...ava/org/apache/dubbo/rpc/model/PackableMethod.java 70.00% 3 Missing ⚠️
...oting/websocket/FinalFragmentStreamingDecoder.java 0.00% 3 Missing ⚠️
...he/dubbo/remoting/http12/CompositeInputStream.java 60.00% 1 Missing and 1 partial ⚠️
...apache/dubbo/rpc/protocol/tri/DescriptorUtils.java 0.00% 2 Missing ⚠️
...ubbo/remoting/http12/message/StreamingDecoder.java 0.00% 1 Missing ⚠️
... and 1 more
Additional details and impacted files
@@             Coverage Diff              @@
##                3.3   #16045      +/-   ##
============================================
- Coverage     60.74%   60.34%   -0.40%     
+ Complexity    11754    10224    -1530     
============================================
  Files          1949     1950       +1     
  Lines         88898    89006     +108     
  Branches      13407    13429      +22     
============================================
- Hits          53998    53709     -289     
- Misses        29337    29693     +356     
- Partials       5563     5604      +41     
Flag Coverage Δ
integration-tests-java21 ?
integration-tests-java8 ?
samples-tests-java21 32.20% <43.20%> (+0.08%) ⬆️
samples-tests-java8 ?
unit-tests-java11 58.98% <60.67%> (-0.03%) ⬇️
unit-tests-java17 58.48% <60.67%> (-0.04%) ⬇️
unit-tests-java21 58.46% <60.67%> (-0.04%) ⬇️
unit-tests-java25 58.42% <60.67%> (-0.04%) ⬇️
unit-tests-java8 58.97% <60.67%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the Triple protocol's pack and unpack operations to use streams (InputStream/OutputStream) instead of byte arrays. This change improves memory efficiency by avoiding unnecessary buffering of large messages into memory, and enables more efficient streaming processing.

Changes:

  • Replaced byte array-based serialization/deserialization APIs with stream-based APIs in Pack/UnPack/PackableMethod interfaces
  • Refactored TriDecoder to use GrpcStreamingDecoder (unified with server-side implementation)
  • Updated all Pack/UnPack implementations to support the new stream-based APIs with backward compatibility
  • Introduced BoundedInputStream for safe stream reading with mark/reset support

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
Stream.java Updated Listener.onMessage to accept InputStream with messageLength parameter
AbstractTripleClientStream.java Replaced TriDecoder with GrpcStreamingDecoder and updated to use ByteBufInputStream
GrpcStreamingDecoder.java Implements stream-based message decompression
GrpcCompositeCodec.java Updated encode/decode to use streams instead of byte arrays
ReflectionPackableMethod.java Implemented stream-based pack/unpack methods with backward compatibility
TripleCustomerProtocolWrapper.java Added parseFrom(InputStream) and writeTo(OutputStream) methods with proper varint and field reading
PbUnpack.java & PbArrayPacker.java Implemented stream-based unpack/pack methods
LengthFieldStreamingDecoder.java Added BoundedInputStream for controlled stream reading with mark/reset support
Pack.java & UnPack.java Added new stream-based methods with default implementations and deprecated byte array methods
PackableMethod.java Added stream-based parseRequest/Response and packRequest/Response methods
MockClientStreamListener.java Updated test mock to handle InputStream parameters
TriDecoderTest.java & RecordListener.java Removed obsolete tests for deleted TriDecoder

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +208 to +224
int tag = b;
if ((b & 0x80) != 0) {
int shift = 7;
tag = b & 0x7F;
while (shift < 35) {
b = inputStream.read();
if (b == -1) {
throw new IOException("Unexpected end of stream while reading tag");
}
tag |= (b & 0x7F) << shift;
if ((b & 0x80) == 0) {
break;
}
shift += 7;
}
}

Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The varint parsing logic for tags (lines 208-223) is duplicated and slightly different from the readRawVarint32 method. Consider using readRawVarint32 for both tag and length parsing to reduce duplication and ensure consistency. The current implementation reads the first byte outside the loop and then continues if needed, which could be simplified.

Suggested change
int tag = b;
if ((b & 0x80) != 0) {
int shift = 7;
tag = b & 0x7F;
while (shift < 35) {
b = inputStream.read();
if (b == -1) {
throw new IOException("Unexpected end of stream while reading tag");
}
tag |= (b & 0x7F) << shift;
if ((b & 0x80) == 0) {
break;
}
shift += 7;
}
}
final int firstByte = b;
InputStream tagInputStream = new InputStream() {
private boolean first = true;
@Override
public int read() throws IOException {
if (first) {
first = false;
return firstByte;
}
return inputStream.read();
}
@Override
public int read(byte[] buffer, int off, int len) throws IOException {
if (buffer == null) {
throw new NullPointerException();
}
if (off < 0 || len < 0 || len > buffer.length - off) {
throw new IndexOutOfBoundsException();
}
if (len == 0) {
return 0;
}
if (first) {
first = false;
buffer[off] = (byte) firstByte;
return 1;
}
return inputStream.read(buffer, off, len);
}
};
int tag = readRawVarint32(tagInputStream);

Copilot uses AI. Check for mistakes.
Comment on lines +236 to +240
tripleResponseWrapper.serializeType = new String(fieldData);
} else if (fieldNum == 2) {
tripleResponseWrapper.data = fieldData;
} else if (fieldNum == 3) {
tripleResponseWrapper.type = new String(fieldData);
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String construction should specify charset explicitly. The current code uses the default platform charset (via new String(byte[])) which may differ across systems. Consider using new String(fieldData, StandardCharsets.UTF_8) to match the encoding used in toByteArray().

Suggested change
tripleResponseWrapper.serializeType = new String(fieldData);
} else if (fieldNum == 2) {
tripleResponseWrapper.data = fieldData;
} else if (fieldNum == 3) {
tripleResponseWrapper.type = new String(fieldData);
tripleResponseWrapper.serializeType = new String(fieldData, StandardCharsets.UTF_8);
} else if (fieldNum == 2) {
tripleResponseWrapper.data = fieldData;
} else if (fieldNum == 3) {
tripleResponseWrapper.type = new String(fieldData, StandardCharsets.UTF_8);

Copilot uses AI. Check for mistakes.
Comment on lines +435 to +439
tripleRequestWrapper.serializeType = new String(fieldData);
} else if (fieldNum == 2) {
tripleRequestWrapper.args.add(fieldData);
} else if (fieldNum == 3) {
tripleRequestWrapper.argTypes.add(new String(fieldData));
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String construction should specify charset explicitly. The current code uses the default platform charset (via new String(byte[])) which may differ across systems. Consider using new String(fieldData, StandardCharsets.UTF_8) to match the encoding used in toByteArray().

Suggested change
tripleRequestWrapper.serializeType = new String(fieldData);
} else if (fieldNum == 2) {
tripleRequestWrapper.args.add(fieldData);
} else if (fieldNum == 3) {
tripleRequestWrapper.argTypes.add(new String(fieldData));
tripleRequestWrapper.serializeType = new String(fieldData, StandardCharsets.UTF_8);
} else if (fieldNum == 2) {
tripleRequestWrapper.args.add(fieldData);
} else if (fieldNum == 3) {
tripleRequestWrapper.argTypes.add(new String(fieldData, StandardCharsets.UTF_8));

Copilot uses AI. Check for mistakes.
@@ -125,9 +124,9 @@ public static MethodDescriptor findTripleMethodDescriptor(
ServiceDescriptor serviceDescriptor, String methodName, InputStream rawMessage) throws IOException {
MethodDescriptor methodDescriptor = findReflectionMethodDescriptor(serviceDescriptor, methodName);
if (methodDescriptor == null) {
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using mark with Integer.MAX_VALUE as the readlimit parameter may not work with all InputStream implementations. The BoundedInputStream created in LengthFieldStreamingDecoder overrides mark to ensure sufficient buffer size, but this assumption may not hold for all input streams passed to this method. Consider checking if mark is supported using markSupported() before calling mark, or document that the input stream must support mark/reset.

Suggested change
if (methodDescriptor == null) {
if (methodDescriptor == null) {
if (!rawMessage.markSupported()) {
throw new IOException("InputStream must support mark/reset to resolve overloaded triple methods");
}

Copilot uses AI. Check for mistakes.
EarthChen and others added 12 commits January 24, 2026 20:41
…protocol/tri/stream/AbstractTripleClientStream.java

Co-authored-by: Copilot <[email protected]>
This commit unifies the deframer implementation between client and server
sides for the Triple protocol, eliminating code duplication and improving
maintainability.

## Key Changes

### Deleted Files
- TriDecoder.java: Removed client-specific deframer implementation
- Deframer.java: Removed client deframer interface
- RecordListener.java: Removed test helper
- TriDecoderTest.java: Removed corresponding test

### Core Modifications

1. **LengthFieldStreamingDecoder**: Added BoundedInputStream to support:
   - Message boundary isolation (prevents reading beyond current message)
   - mark/reset support required by Hessian2 and other deserializers
   - Zero-copy optimization by reading directly from accumulate stream

2. **StreamingDecoder.FragmentListener**: Added messageLength parameter to
   onFragmentMessage() method for better flow control

3. **AbstractTripleClientStream**: Migrated from TriDecoder to GrpcStreamingDecoder,
   using ByteBufInputStream to adapt Netty's ByteBuf to InputStream

4. **Stream.Listener.onMessage**: Changed parameter from byte[] to InputStream
   for unified handling and memory optimization

5. **PackableMethod**: Added default parseResponse(InputStream) method for
   backward compatibility

6. **CompositeInputStream**: Improved stream release logic to prevent exceptions

7. **DescriptorUtils**: Added mark() call before reading stream to support reset

## Architecture Change

Before:
- Client: ByteBuf -> TriDecoder (Netty-specific) -> byte[] -> deserializer
- Server: InputStream -> LengthFieldStreamingDecoder -> byte[] -> deserializer

After:
- Client: ByteBuf -> ByteBufInputStream -> GrpcStreamingDecoder -> BoundedInputStream -> deserializer
- Server: InputStream -> GrpcStreamingDecoder -> BoundedInputStream -> deserializer

## Bug Fix

Fixed BoundedInputStream.reset() not restoring the 'remaining' counter,
which caused streams to return EOF after mark/reset. This was the root cause
of POJO method invocation failures with "Unexpected serialization type:null" error.
# Conflicts:
#	dubbo-common/src/main/java/org/apache/dubbo/rpc/model/PackableMethod.java
#	dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DescriptorUtils.java
#	dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/AbstractTripleClientStream.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants