Skip to content

Commit 15fe657

Browse files
committed
1. Remove relying on UnsafeByteOperations.unsafeWrap(byteBuffer) in data plane onMessage handling for sending to ext_proc (inboundStreamToByteString) since the Netty byte buffers could be deallocated once onMessage returns and the ext_proc call continuing async will read deallocated memory. Resorting to making a separate copy of message for sending to ext_proc.
2. Removed usage of ZeroCopyInputStream that provided HasByteBuffer, Detachable, KnownLength abstraction over byteString because GCS is not using ext_proc and any such optimization can be done later when needed. 3. In outboundStreamToByteString for the non-Drainable fallback path, replaced allocating byte array and then doing ByteString.copyFrom on it with a single call to ByteString.readFrom (which also does the same thing) but retained the Drainable optimization because this avoids the extra temporary byte[] allocation.
1 parent a6d56f9 commit 15fe657

2 files changed

Lines changed: 10 additions & 127 deletions

File tree

xds/src/main/java/io/grpc/xds/ExternalProcessorClientInterceptor.java

Lines changed: 10 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@
2323
import com.google.common.collect.ImmutableList;
2424
import com.google.common.collect.ImmutableMap;
2525
import com.google.common.io.BaseEncoding;
26-
import com.google.common.io.ByteStreams;
2726
import com.google.protobuf.ByteString;
2827
import com.google.protobuf.Struct;
29-
import com.google.protobuf.UnsafeByteOperations;
3028
import com.google.protobuf.Value;
3129
import io.envoyproxy.envoy.config.core.v3.HeaderMap;
3230
import io.envoyproxy.envoy.extensions.filters.http.ext_proc.v3.ProcessingMode;
@@ -49,12 +47,10 @@
4947
import io.grpc.ClientInterceptor;
5048
import io.grpc.Context;
5149
import io.grpc.Deadline;
52-
import io.grpc.Detachable;
5350
import io.grpc.DoubleHistogramMetricInstrument;
5451
import io.grpc.Drainable;
5552
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
5653
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
57-
import io.grpc.HasByteBuffer;
5854
import io.grpc.KnownLength;
5955
import io.grpc.Metadata;
6056
import io.grpc.MethodDescriptor;
@@ -81,7 +77,6 @@
8177
import java.io.IOException;
8278
import java.io.InputStream;
8379
import java.io.OutputStream;
84-
import java.nio.ByteBuffer;
8580
import java.util.ArrayList;
8681
import java.util.List;
8782
import java.util.Locale;
@@ -1325,13 +1320,16 @@ public void onMessage(InputStream message) {
13251320
}
13261321

13271322
try {
1328-
ByteString bodyByteString = inboundStreamToByteString(message);
1323+
ByteString bodyByteString = ByteString.readFrom(message);
13291324
sendResponseBodyToExtProc(bodyByteString, false);
13301325
dataPlaneClientCall.bodyMessageSentToExtProc.set(true);
13311326

13321327
if (dataPlaneClientCall.getConfig().getObservabilityMode()) {
1328+
// If needed, downstream reading can be made more optimal by creating a wrapped
1329+
// Inputstream wraps the underlying bytestring and that implements HasByteBuffer,
1330+
// Detachable, KnownLength
13331331
dataPlaneClientCall.getCallContext().run(
1334-
() -> delegate().onMessage(new InboundZeroCopyInputStream(bodyByteString)));
1332+
() -> delegate().onMessage(bodyByteString.newInput()));
13351333
}
13361334
} catch (IOException e) {
13371335
rawCall.cancel("Failed to read server response", e);
@@ -1443,8 +1441,11 @@ private void proceedWithClose(Status status, Metadata trailers) {
14431441
}
14441442

14451443
void onExternalBody(ByteString body) {
1444+
// If needed, downstream reading can be made more optimal by creating a wrapped
1445+
// Inputstream wraps the underlying bytestring and that implements HasByteBuffer,
1446+
// Detachable, KnownLength
14461447
dataPlaneClientCall.getCallContext().run(
1447-
() -> delegate().onMessage(new InboundZeroCopyInputStream(body)));
1448+
() -> delegate().onMessage(body.newInput()));
14481449
}
14491450

14501451
void unblockAfterStreamComplete() {
@@ -1537,39 +1538,6 @@ private void sendResponseBodyToExtProc(
15371538
}
15381539
}
15391540

1540-
@com.google.common.annotations.VisibleForTesting
1541-
static ByteString inboundStreamToByteString(InputStream message) throws IOException {
1542-
if (message == null) {
1543-
return ByteString.EMPTY;
1544-
}
1545-
if (message instanceof HasByteBuffer && ((HasByteBuffer) message).byteBufferSupported()) {
1546-
HasByteBuffer hasByteBuffer = (HasByteBuffer) message;
1547-
List<ByteString> chunks = new ArrayList<>();
1548-
while (message.available() > 0) {
1549-
ByteBuffer byteBuffer = hasByteBuffer.getByteBuffer();
1550-
if (byteBuffer != null && byteBuffer.hasRemaining()) {
1551-
int remaining = byteBuffer.remaining();
1552-
chunks.add(UnsafeByteOperations.unsafeWrap(byteBuffer));
1553-
long skipped = message.skip(remaining);
1554-
if (skipped < remaining) {
1555-
throw new IOException("Failed to skip all bytes in HasByteBuffer stream");
1556-
}
1557-
} else {
1558-
// Force advance past any exhausted component buffers at the head of the composite queue.
1559-
message.skip(0);
1560-
ByteBuffer nextBuffer = hasByteBuffer.getByteBuffer();
1561-
if (nextBuffer == null || !nextBuffer.hasRemaining()) {
1562-
break;
1563-
}
1564-
}
1565-
}
1566-
if (!chunks.isEmpty() && message.available() == 0) {
1567-
return ByteString.copyFrom(chunks);
1568-
}
1569-
}
1570-
byte[] bodyBytes = ByteStreams.toByteArray(message);
1571-
return ByteString.copyFrom(bodyBytes);
1572-
}
15731541

15741542

15751543
private static ByteString outboundStreamToByteString(InputStream message) throws IOException {
@@ -1583,77 +1551,7 @@ private static ByteString outboundStreamToByteString(InputStream message) throws
15831551
((Drainable) message).drainTo(output);
15841552
return output.toByteString();
15851553
}
1586-
byte[] bodyBytes = ByteStreams.toByteArray(message);
1587-
return ByteString.copyFrom(bodyBytes);
1588-
}
1589-
1590-
private static final class InboundZeroCopyInputStream extends InputStream
1591-
implements HasByteBuffer, Detachable, KnownLength {
1592-
private final ByteString byteString;
1593-
private final InputStream delegate;
1594-
private boolean detached = false;
1595-
1596-
InboundZeroCopyInputStream(ByteString byteString) {
1597-
this.byteString = byteString;
1598-
this.delegate = byteString.newInput();
1599-
}
1600-
1601-
@Override
1602-
public int read() throws IOException {
1603-
return delegate.read();
1604-
}
1605-
1606-
@Override
1607-
public int read(byte[] b, int off, int len) throws IOException {
1608-
return delegate.read(b, off, len);
1609-
}
1610-
1611-
@Override
1612-
public int available() throws IOException {
1613-
return delegate.available();
1614-
}
1615-
1616-
@Override
1617-
public void close() throws IOException {
1618-
delegate.close();
1619-
}
1620-
1621-
@Override
1622-
public boolean markSupported() {
1623-
return delegate.markSupported();
1624-
}
1625-
1626-
@Override
1627-
public void mark(int readlimit) {
1628-
delegate.mark(readlimit);
1629-
}
1630-
1631-
@Override
1632-
public void reset() throws IOException {
1633-
delegate.reset();
1634-
}
1635-
1636-
@Override
1637-
public boolean byteBufferSupported() {
1638-
return !detached;
1639-
}
1640-
1641-
@Override
1642-
public ByteBuffer getByteBuffer() {
1643-
if (detached) {
1644-
throw new IllegalStateException("Stream has been detached");
1645-
}
1646-
return byteString.asReadOnlyByteBuffer();
1647-
}
1648-
1649-
@Override
1650-
public InputStream detach() {
1651-
if (detached) {
1652-
throw new IllegalStateException("Stream already detached");
1653-
}
1654-
detached = true;
1655-
return delegate;
1656-
}
1554+
return ByteString.readFrom(message);
16571555
}
16581556

16591557
private static final class OutboundZeroCopyInputStream extends InputStream

xds/src/test/java/io/grpc/xds/ExternalProcessorClientInterceptorTest.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -274,21 +274,6 @@ private ExternalProcessor.Builder createBaseProto(String targetName) {
274274

275275
// --- Category 1: Configuration Override ---
276276

277-
@Test
278-
public void inboundStreamToByteString_handlesCompositeBuffers() throws Exception {
279-
io.grpc.internal.CompositeReadableBuffer composite =
280-
new io.grpc.internal.CompositeReadableBuffer();
281-
composite.addBuffer(
282-
io.grpc.internal.ReadableBuffers.wrap("hello ".getBytes(StandardCharsets.UTF_8)));
283-
composite.addBuffer(
284-
io.grpc.internal.ReadableBuffers.wrap("world".getBytes(StandardCharsets.UTF_8)));
285-
286-
InputStream stream = io.grpc.internal.ReadableBuffers.openStream(composite, true);
287-
ByteString result = ExternalProcessorClientInterceptor.inboundStreamToByteString(stream);
288-
289-
assertThat(result.toStringUtf8()).isEqualTo("hello world");
290-
}
291-
292277
@Test
293278
public void givenOverrideConfig_whenGrpcServiceOverridden_thenUsesNewService() throws Exception {
294279
ExternalProcessor parentProto = createBaseProto(extProcServerName)

0 commit comments

Comments
 (0)