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

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
*/
package org.apache.dubbo.rpc.model;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
* A packable method is used to customize serialization for methods. It can provide a common wrapper
* for RESP / Protobuf.
Expand All @@ -38,6 +41,27 @@ default Object parseResponse(byte[] data, boolean isReturnTriException) throws E
return unPack.unpack(data);
}

/**
* Parse response from InputStream.
* Default implementation reads all bytes and delegates to byte[] version.
*
* @param inputStream the input stream containing the response data
* @param isReturnTriException whether the response is a Triple exception
* @return the parsed response object
* @throws Exception if parsing fails
*/
default Object parseResponse(InputStream inputStream, boolean isReturnTriException) throws Exception {
// Read all bytes from InputStream
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] tmp = new byte[4096];
int len;
while ((len = inputStream.read(tmp)) != -1) {
buffer.write(tmp, 0, len);
}
byte[] data = buffer.toByteArray();
return parseResponse(data, isReturnTriException);
}

default byte[] packRequest(Object request) throws Exception {
return getRequestPack().pack(request);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,15 @@ public void close() throws IOException {
}
}

private void releaseHeadStream() throws IOException {
InputStream removeStream = inputStreams.remove();
removeStream.close();
private void releaseHeadStream() {
InputStream removeStream = inputStreams.poll();
if (removeStream != null) {
try {
removeStream.close();
} catch (IOException ignore) {
// ignore
}
}
}

private void releaseIfNecessary(InputStream inputStream) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void close() {
return;
}
closed = true;
listener.onFragmentMessage(accumulate);
listener.onFragmentMessage(accumulate, accumulate.available());
accumulate.close();
listener.onClose();
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.model.ApplicationModel;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -119,7 +120,9 @@ private void doClose() {
} catch (IOException e) {
// ignore
}
listener.onClose();
if (listener != null) {
listener.onClose();
}
}

/**
Expand Down Expand Up @@ -233,27 +236,33 @@ private void processBody() throws IOException {
// Calculate total bytes read: header (offset + length field) + payload
int totalBytesRead = lengthFieldOffset + lengthFieldLength + requiredLength;

byte[] rawMessage;
MessageStream messageStream;
try {
rawMessage = readRawMessage(accumulate, requiredLength);
messageStream = readMessageStream(accumulate, requiredLength);
} finally {
// Notify listener about bytes read for flow control immediately after reading bytes
// This must be in finally block to ensure flow control works even if reading fails
// Following gRPC's pattern: bytesRead is called as soon as bytes are consumed from input
listener.bytesRead(totalBytesRead);
}

// Process the message after notifying about bytes read
InputStream inputStream = new ByteArrayInputStream(rawMessage);
invokeListener(inputStream);
invokeListener(messageStream.inputStream, messageStream.length);

// Done with this frame, begin processing the next header.
state = DecodeState.HEADER;
requiredLength = lengthFieldOffset + lengthFieldLength;
}

public void invokeListener(InputStream inputStream) {
this.listener.onFragmentMessage(inputStream);
public void invokeListener(InputStream inputStream, int messageLength) {
this.listener.onFragmentMessage(inputStream, messageLength);
}

/**
* Read message from the input stream and return it as a MessageStream.
*/
protected MessageStream readMessageStream(InputStream inputStream, int length) throws IOException {
InputStream boundedStream = new BoundedInputStream(inputStream, length);
return new MessageStream(boundedStream, length);
}

protected byte[] readRawMessage(InputStream inputStream, int length) throws IOException {
Expand All @@ -262,6 +271,82 @@ protected byte[] readRawMessage(InputStream inputStream, int length) throws IOEx
return data;
}

protected static class MessageStream {

public final InputStream inputStream;
public final int length;

public MessageStream(InputStream inputStream, int length) {
this.inputStream = inputStream;
this.length = length;
}
}

/**
* A bounded InputStream that reads at most 'limit' bytes from the source stream.
* Extends BufferedInputStream to support mark/reset, which is required by
* deserializers like Hessian2.
*/
private static class BoundedInputStream extends BufferedInputStream {

private final int limit;
private int remaining;
private int markedRemaining;

public BoundedInputStream(InputStream source, int limit) {
super(source, limit);
this.limit = limit;
this.remaining = limit;
this.markedRemaining = limit;
}

@Override
public synchronized int read() throws IOException {
if (remaining <= 0) {
return -1;
}
int result = super.read();
if (result != -1) {
remaining--;
}
return result;
}

@Override
public synchronized int read(byte[] b, int off, int len) throws IOException {
if (remaining <= 0) {
return -1;
}
int toRead = Math.min(len, remaining);
int result = super.read(b, off, toRead);
if (result > 0) {
remaining -= result;
}
return result;
}

@Override
public synchronized int available() throws IOException {
return Math.min(super.available(), remaining);
}

@Override
public synchronized void mark(int readlimit) {
// Force readlimit to be at least the remaining message length.
// This ensures mark is always valid within the bounded stream,
// regardless of what readlimit is passed by the deserializer (e.g., Hessian2).
super.mark(Math.max(readlimit, limit));
markedRemaining = remaining;
}

@Override
public synchronized void reset() throws IOException {
super.reset();
// Restore the remaining count to the value at mark time
remaining = markedRemaining;
}
}

private boolean hasEnoughBytes() {
return requiredLength - accumulate.available() <= 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ interface FragmentListener {
void bytesRead(int numBytes);

/**
* @param rawMessage raw message
* Called when a complete message fragment is received.
*
* @param rawMessage raw message as InputStream
* @param messageLength the length of the message payload in bytes
*/
void onFragmentMessage(InputStream rawMessage);
void onFragmentMessage(InputStream rawMessage, int messageLength);

default void onClose() {}
}
Expand All @@ -59,6 +62,6 @@ private NoopFragmentListener() {}
public void bytesRead(int numBytes) {}

@Override
public void onFragmentMessage(InputStream rawMessage) {}
public void onFragmentMessage(InputStream rawMessage, int messageLength) {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,13 @@ private void deliver() {

private void processBody() throws IOException {
byte[] rawMessage = readRawMessage(accumulate, accumulate.available());
int messageLength = rawMessage.length;
InputStream inputStream = new ByteArrayInputStream(rawMessage);
invokeListener(inputStream);
invokeListener(inputStream, messageLength);
}

protected void invokeListener(InputStream inputStream) {
this.listener.onFragmentMessage(inputStream);
protected void invokeListener(InputStream inputStream, int messageLength) {
this.listener.onFragmentMessage(inputStream, messageLength);
}

protected byte[] readRawMessage(InputStream inputStream, int length) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ 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 23, 2026

Choose a reason for hiding this comment

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

The call to rawMessage.mark(Integer.MAX_VALUE) assumes the InputStream supports mark/reset and has a sufficient buffer. While BoundedInputStream (used in the unified deframer) extends BufferedInputStream and supports this, consider adding a check or documentation to ensure all callers pass InputStreams that support mark/reset with adequate buffer size. If an InputStream that doesn't support mark/reset is passed, this will fail silently and the subsequent reset() on line 144 will throw an IOException.

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

Copilot uses AI. Check for mistakes.
rawMessage.mark(Integer.MAX_VALUE);
byte[] data = StreamUtils.readBytes(rawMessage);
List<MethodDescriptor> methodDescriptors = serviceDescriptor.getMethods(methodName);
TripleRequestWrapper request = TripleRequestWrapper.parseFrom(data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.dubbo.rpc.protocol.tri.stream.StreamUtils;
import org.apache.dubbo.rpc.protocol.tri.transport.TripleWriteQueue;

import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.Executor;

Expand Down Expand Up @@ -84,7 +85,7 @@ public boolean isReady() {

// stream listener start
@Override
public void onMessage(byte[] message, boolean isReturnTriException) {
public void onMessage(InputStream message, int messageLength, boolean isReturnTriException) {
if (done) {
LOGGER.warn(
PROTOCOL_STREAM_LISTENER,
Expand All @@ -95,8 +96,9 @@ public void onMessage(byte[] message, boolean isReturnTriException) {
return;
}
try {
// Use the new InputStream-based parseResponse method
Object unpacked = requestMetadata.packableMethod.parseResponse(message, isReturnTriException);
listener.onMessage(unpacked, message.length);
listener.onMessage(unpacked, messageLength);
} catch (Throwable t) {
TriRpcStatus status = TriRpcStatus.INTERNAL
.withDescription("Deserialize response failed")
Expand Down

This file was deleted.

Loading
Loading