cronet: use reflection to load and invoke experimental APIs in ExperimentalBidirectionalStream.Builder (#6111)

* Use reflection to load and invoke setTrafficStats* and addRequestAnnotation methods for ExperimentalBidirectionalStream.Builder.

* Load reflection method lazily and cache for later usages.
This commit is contained in:
Chengyuan Zhang 2019-09-06 10:51:09 -07:00 committed by GitHub
parent 62e37a6e6b
commit a234ada5a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 114 additions and 8 deletions

View File

@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
import android.util.Log;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
@ -31,6 +32,8 @@ import io.grpc.internal.ConnectionClientTransport;
import io.grpc.internal.GrpcUtil; import io.grpc.internal.GrpcUtil;
import io.grpc.internal.SharedResourceHolder; import io.grpc.internal.SharedResourceHolder;
import io.grpc.internal.TransportTracer; import io.grpc.internal.TransportTracer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -46,6 +49,8 @@ import org.chromium.net.ExperimentalCronetEngine;
public final class CronetChannelBuilder extends public final class CronetChannelBuilder extends
AbstractManagedChannelImplBuilder<CronetChannelBuilder> { AbstractManagedChannelImplBuilder<CronetChannelBuilder> {
private static final String LOG_TAG = "CronetChannelBuilder";
/** BidirectionalStream.Builder factory used for getting the gRPC BidirectionalStream. */ /** BidirectionalStream.Builder factory used for getting the gRPC BidirectionalStream. */
public static abstract class StreamBuilderFactory { public static abstract class StreamBuilderFactory {
public abstract BidirectionalStream.Builder newBidirectionalStreamBuilder( public abstract BidirectionalStream.Builder newBidirectionalStreamBuilder(
@ -245,6 +250,11 @@ public final class CronetChannelBuilder extends
* StreamBuilderFactory impl that applies TrafficStats tags to stream builders that are produced. * StreamBuilderFactory impl that applies TrafficStats tags to stream builders that are produced.
*/ */
private static class TaggingStreamFactory extends StreamBuilderFactory { private static class TaggingStreamFactory extends StreamBuilderFactory {
private static volatile boolean loadSetTrafficStatsTagAttempted;
private static volatile boolean loadSetTrafficStatsUidAttempted;
private static volatile Method setTrafficStatsTagMethod;
private static volatile Method setTrafficStatsUidMethod;
private final CronetEngine cronetEngine; private final CronetEngine cronetEngine;
private final boolean trafficStatsTagSet; private final boolean trafficStatsTagSet;
private final int trafficStatsTag; private final int trafficStatsTag;
@ -271,12 +281,70 @@ public final class CronetChannelBuilder extends
((ExperimentalCronetEngine) cronetEngine) ((ExperimentalCronetEngine) cronetEngine)
.newBidirectionalStreamBuilder(url, callback, executor); .newBidirectionalStreamBuilder(url, callback, executor);
if (trafficStatsTagSet) { if (trafficStatsTagSet) {
builder.setTrafficStatsTag(trafficStatsTag); setTrafficStatsTag(builder, trafficStatsTag);
} }
if (trafficStatsUidSet) { if (trafficStatsUidSet) {
builder.setTrafficStatsUid(trafficStatsUid); setTrafficStatsUid(builder, trafficStatsUid);
} }
return builder; return builder;
} }
private static void setTrafficStatsTag(ExperimentalBidirectionalStream.Builder builder,
int tag) {
if (!loadSetTrafficStatsTagAttempted) {
synchronized (TaggingStreamFactory.class) {
if (!loadSetTrafficStatsTagAttempted) {
try {
setTrafficStatsTagMethod = ExperimentalBidirectionalStream.Builder.class
.getMethod("setTrafficStatsTag", int.class);
} catch (NoSuchMethodException e) {
Log.w(LOG_TAG,
"Failed to load method ExperimentalBidirectionalStream.Builder.setTrafficStatsTag",
e);
} finally {
loadSetTrafficStatsTagAttempted = true;
}
}
}
}
if (setTrafficStatsTagMethod != null) {
try {
setTrafficStatsTagMethod.invoke(builder, tag);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause() == null ? e.getTargetException() : e.getCause());
} catch (IllegalAccessException e) {
Log.w(LOG_TAG, "Failed to set traffic stats tag: " + tag, e);
}
}
}
private static void setTrafficStatsUid(ExperimentalBidirectionalStream.Builder builder,
int uid) {
if (!loadSetTrafficStatsUidAttempted) {
synchronized (TaggingStreamFactory.class) {
if (!loadSetTrafficStatsUidAttempted) {
try {
setTrafficStatsUidMethod = ExperimentalBidirectionalStream.Builder.class
.getMethod("setTrafficStatsUid", int.class);
} catch (NoSuchMethodException e) {
Log.w(LOG_TAG,
"Failed to load method ExperimentalBidirectionalStream.Builder.setTrafficStatsUid",
e);
} finally {
loadSetTrafficStatsUidAttempted = true;
}
}
}
}
if (setTrafficStatsUidMethod != null) {
try {
setTrafficStatsUidMethod.invoke(builder, uid);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause() == null ? e.getTargetException() : e.getCause());
} catch (IllegalAccessException e) {
Log.w(LOG_TAG, "Failed to set traffic stats uid: " + uid, e);
}
}
}
} }
} }

View File

@ -40,6 +40,8 @@ import io.grpc.internal.StatsTraceContext;
import io.grpc.internal.TransportFrameUtil; import io.grpc.internal.TransportFrameUtil;
import io.grpc.internal.TransportTracer; import io.grpc.internal.TransportTracer;
import io.grpc.internal.WritableBuffer; import io.grpc.internal.WritableBuffer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
@ -65,6 +67,9 @@ class CronetClientStream extends AbstractClientStream {
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0); private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0);
private static final String LOG_TAG = "grpc-java-cronet"; private static final String LOG_TAG = "grpc-java-cronet";
private static volatile boolean loadAddRequestAnnotationAttempted;
private static volatile Method addRequestAnnotationMethod;
@Deprecated @Deprecated
static final CallOptions.Key<Object> CRONET_ANNOTATION_KEY = static final CallOptions.Key<Object> CRONET_ANNOTATION_KEY =
CallOptions.Key.create("cronet-annotation"); CallOptions.Key.create("cronet-annotation");
@ -182,12 +187,16 @@ class CronetClientStream extends AbstractClientStream {
if (delayRequestHeader) { if (delayRequestHeader) {
builder.delayRequestHeadersUntilFirstFlush(true); builder.delayRequestHeadersUntilFirstFlush(true);
} }
if (annotation != null) { if (annotation != null || annotations != null) {
((ExperimentalBidirectionalStream.Builder) builder).addRequestAnnotation(annotation); ExperimentalBidirectionalStream.Builder expBidiStreamBuilder =
} (ExperimentalBidirectionalStream.Builder) builder;
if (annotations != null) { if (annotation != null) {
for (Object o : annotations) { addRequestAnnotation(expBidiStreamBuilder, annotation);
((ExperimentalBidirectionalStream.Builder) builder).addRequestAnnotation(o); }
if (annotations != null) {
for (Object o : annotations) {
addRequestAnnotation(expBidiStreamBuilder, o);
}
} }
} }
setGrpcHeaders(builder); setGrpcHeaders(builder);
@ -359,6 +368,35 @@ class CronetClientStream extends AbstractClientStream {
&& !TE_HEADER.name().equalsIgnoreCase(key); && !TE_HEADER.name().equalsIgnoreCase(key);
} }
private static void addRequestAnnotation(ExperimentalBidirectionalStream.Builder builder,
Object annotation) {
if (!loadAddRequestAnnotationAttempted) {
synchronized (CronetClientStream.class) {
if (!loadAddRequestAnnotationAttempted) {
try {
addRequestAnnotationMethod = ExperimentalBidirectionalStream.Builder.class
.getMethod("addRequestAnnotation", Object.class);
} catch (NoSuchMethodException e) {
Log.w(LOG_TAG,
"Failed to load method ExperimentalBidirectionalStream.Builder.addRequestAnnotation",
e);
} finally {
loadAddRequestAnnotationAttempted = true;
}
}
}
}
if (addRequestAnnotationMethod != null) {
try {
addRequestAnnotationMethod.invoke(builder, annotation);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause() == null ? e.getTargetException() : e.getCause());
} catch (IllegalAccessException e) {
Log.w(LOG_TAG, "Failed to add request annotation: " + annotation, e);
}
}
}
private void setGrpcHeaders(BidirectionalStream.Builder builder) { private void setGrpcHeaders(BidirectionalStream.Builder builder) {
// Psuedo-headers are set by cronet. // Psuedo-headers are set by cronet.
// All non-pseudo headers must come after pseudo headers. // All non-pseudo headers must come after pseudo headers.