@@ -139,11 +139,11 @@ plugins {
protobuf {
protoc {
- artifact = "com.google.protobuf:protoc:3.17.2"
+ artifact = "com.google.protobuf:protoc:3.17.3"
}
plugins {
grpc {
- artifact = 'io.grpc:protoc-gen-grpc-java:1.39.0'
+ artifact = 'io.grpc:protoc-gen-grpc-java:1.41.0'
}
}
generateProtoTasks {
@@ -172,11 +172,11 @@ plugins {
protobuf {
protoc {
- artifact = "com.google.protobuf:protoc:3.17.2"
+ artifact = "com.google.protobuf:protoc:3.17.3"
}
plugins {
grpc {
- artifact = 'io.grpc:protoc-gen-grpc-java:1.39.0'
+ artifact = 'io.grpc:protoc-gen-grpc-java:1.41.0'
}
}
generateProtoTasks {
diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle
index f1c1d491233..bbf1fcfe99e 100644
--- a/android-interop-testing/build.gradle
+++ b/android-interop-testing/build.gradle
@@ -62,6 +62,7 @@ dependencies {
project(':grpc-protobuf-lite'),
project(':grpc-stub'),
project(':grpc-testing'),
+ libraries.hdrhistogram,
libraries.junit,
libraries.truth,
libraries.opencensus_contrib_grpc_metrics
diff --git a/api/src/main/java/io/grpc/ClientStreamTracer.java b/api/src/main/java/io/grpc/ClientStreamTracer.java
index 6259522487a..bb836ac82e1 100644
--- a/api/src/main/java/io/grpc/ClientStreamTracer.java
+++ b/api/src/main/java/io/grpc/ClientStreamTracer.java
@@ -19,7 +19,6 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.MoreObjects;
-import io.grpc.Grpc;
import javax.annotation.concurrent.ThreadSafe;
/**
@@ -28,6 +27,18 @@
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
@ThreadSafe
public abstract class ClientStreamTracer extends StreamTracer {
+
+ /**
+ * The stream is being created on a ready transport.
+ *
+ * @param headers the mutable initial metadata. Modifications to it will be sent to the socket but
+ * not be seen by client interceptors and the application.
+ *
+ * @since 1.40.0
+ */
+ public void streamCreated(@Grpc.TransportAttr Attributes transportAttrs, Metadata headers) {
+ }
+
/**
* Headers has been sent to the socket.
*/
@@ -54,22 +65,6 @@ public void inboundTrailers(Metadata trailers) {
* Factory class for {@link ClientStreamTracer}.
*/
public abstract static class Factory {
- /**
- * Creates a {@link ClientStreamTracer} for a new client stream.
- *
- * @param callOptions the effective CallOptions of the call
- * @param headers the mutable headers of the stream. It can be safely mutated within this
- * method. It should not be saved because it is not safe for read or write after the
- * method returns.
- *
- * @deprecated use {@link
- * #newClientStreamTracer(io.grpc.ClientStreamTracer.StreamInfo, io.grpc.Metadata)} instead.
- */
- @Deprecated
- public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) {
- throw new UnsupportedOperationException("Not implemented");
- }
-
/**
* Creates a {@link ClientStreamTracer} for a new client stream. This is called inside the
* transport when it's creating the stream.
@@ -81,12 +76,15 @@ public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadat
*
* @since 1.20.0
*/
- @SuppressWarnings("deprecation")
public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
- return newClientStreamTracer(info.getCallOptions(), headers);
+ throw new UnsupportedOperationException("Not implemented");
}
}
+ /** An abstract class for internal use only. */
+ @Internal
+ public abstract static class InternalLimitedInfoFactory extends Factory {}
+
/**
* Information about a stream.
*
@@ -99,15 +97,25 @@ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata header
public static final class StreamInfo {
private final Attributes transportAttrs;
private final CallOptions callOptions;
+ private final int previousAttempts;
+ private final boolean isTransparentRetry;
- StreamInfo(Attributes transportAttrs, CallOptions callOptions) {
+ StreamInfo(
+ Attributes transportAttrs, CallOptions callOptions, int previousAttempts,
+ boolean isTransparentRetry) {
this.transportAttrs = checkNotNull(transportAttrs, "transportAttrs");
this.callOptions = checkNotNull(callOptions, "callOptions");
+ this.previousAttempts = previousAttempts;
+ this.isTransparentRetry = isTransparentRetry;
}
/**
* Returns the attributes of the transport that this stream was created on.
+ *
+ * @deprecated Use {@link ClientStreamTracer#streamCreated(Attributes, Metadata)} to handle
+ * the transport Attributes instead.
*/
+ @Deprecated
@Grpc.TransportAttr
public Attributes getTransportAttrs() {
return transportAttrs;
@@ -120,16 +128,35 @@ public CallOptions getCallOptions() {
return callOptions;
}
+ /**
+ * Returns the number of preceding attempts for the RPC.
+ *
+ * @since 1.40.0
+ */
+ public int getPreviousAttempts() {
+ return previousAttempts;
+ }
+
+ /**
+ * Whether the stream is a transparent retry.
+ *
+ * @since 1.40.0
+ */
+ public boolean isTransparentRetry() {
+ return isTransparentRetry;
+ }
+
/**
* Converts this StreamInfo into a new Builder.
*
* @since 1.21.0
*/
public Builder toBuilder() {
- Builder builder = new Builder();
- builder.setTransportAttrs(transportAttrs);
- builder.setCallOptions(callOptions);
- return builder;
+ return new Builder()
+ .setCallOptions(callOptions)
+ .setTransportAttrs(transportAttrs)
+ .setPreviousAttempts(previousAttempts)
+ .setIsTransparentRetry(isTransparentRetry);
}
/**
@@ -146,6 +173,8 @@ public String toString() {
return MoreObjects.toStringHelper(this)
.add("transportAttrs", transportAttrs)
.add("callOptions", callOptions)
+ .add("previousAttempts", previousAttempts)
+ .add("isTransparentRetry", isTransparentRetry)
.toString();
}
@@ -157,6 +186,8 @@ public String toString() {
public static final class Builder {
private Attributes transportAttrs = Attributes.EMPTY;
private CallOptions callOptions = CallOptions.DEFAULT;
+ private int previousAttempts;
+ private boolean isTransparentRetry;
Builder() {
}
@@ -164,9 +195,12 @@ public static final class Builder {
/**
* Sets the attributes of the transport that this stream was created on. This field is
* optional.
+ *
+ * @deprecated Use {@link ClientStreamTracer#streamCreated(Attributes, Metadata)} to handle
+ * the transport Attributes instead.
*/
- @Grpc.TransportAttr
- public Builder setTransportAttrs(Attributes transportAttrs) {
+ @Deprecated
+ public Builder setTransportAttrs(@Grpc.TransportAttr Attributes transportAttrs) {
this.transportAttrs = checkNotNull(transportAttrs, "transportAttrs cannot be null");
return this;
}
@@ -179,11 +213,31 @@ public Builder setCallOptions(CallOptions callOptions) {
return this;
}
+ /**
+ * Set the number of preceding attempts of the RPC.
+ *
+ * @since 1.40.0
+ */
+ public Builder setPreviousAttempts(int previousAttempts) {
+ this.previousAttempts = previousAttempts;
+ return this;
+ }
+
+ /**
+ * Sets whether the stream is a transparent retry.
+ *
+ * @since 1.40.0
+ */
+ public Builder setIsTransparentRetry(boolean isTransparentRetry) {
+ this.isTransparentRetry = isTransparentRetry;
+ return this;
+ }
+
/**
* Builds a new StreamInfo.
*/
public StreamInfo build() {
- return new StreamInfo(transportAttrs, callOptions);
+ return new StreamInfo(transportAttrs, callOptions, previousAttempts, isTransparentRetry);
}
}
}
diff --git a/api/src/main/java/io/grpc/ManagedChannelBuilder.java b/api/src/main/java/io/grpc/ManagedChannelBuilder.java
index e4a4611541d..98b22807ccc 100644
--- a/api/src/main/java/io/grpc/ManagedChannelBuilder.java
+++ b/api/src/main/java/io/grpc/ManagedChannelBuilder.java
@@ -467,7 +467,6 @@ public T perRpcBufferLimit(long bytes) {
* @return this
* @since 1.11.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3982")
public T disableRetry() {
throw new UnsupportedOperationException();
}
@@ -479,13 +478,9 @@ public T disableRetry() {
* transparent retries, which are safe for non-idempotent RPCs. Service config is ideally provided
* by the name resolver, but may also be specified via {@link #defaultServiceConfig}.
*
- * For the current release, this method may have a side effect that disables Census stats and
- * tracing.
- *
* @return this
* @since 1.11.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3982")
public T enableRetry() {
throw new UnsupportedOperationException();
}
diff --git a/api/src/main/java/io/grpc/NameResolver.java b/api/src/main/java/io/grpc/NameResolver.java
index cd3a137dfca..f4c05aa6a64 100644
--- a/api/src/main/java/io/grpc/NameResolver.java
+++ b/api/src/main/java/io/grpc/NameResolver.java
@@ -50,7 +50,9 @@
*
Implementations don't need to be thread-safe. All methods are guaranteed to
* be called sequentially. Additionally, all methods that have side-effects, i.e.,
* {@link #start(Listener2)}, {@link #shutdown} and {@link #refresh} are called from the same
- * {@link SynchronizationContext} as returned by {@link Args#getSynchronizationContext}.
+ * {@link SynchronizationContext} as returned by {@link Args#getSynchronizationContext}. Do
+ * not block within the synchronization context; blocking I/O and time-consuming tasks
+ * should be offloaded to a separate thread, generally {@link Args#getOffloadExecutor}.
*
* @since 1.0.0
*/
diff --git a/api/src/main/java/io/grpc/ProxyDetector.java b/api/src/main/java/io/grpc/ProxyDetector.java
index 5202516bca7..7c04329c079 100644
--- a/api/src/main/java/io/grpc/ProxyDetector.java
+++ b/api/src/main/java/io/grpc/ProxyDetector.java
@@ -32,7 +32,7 @@
* underlying transport need to work together.
*
*
The {@link NameResolver} should invoke the {@link ProxyDetector} retrieved from the {@link
- * NameResolver.Helper#getProxyDetector}, and pass the returned {@link ProxiedSocketAddress} to
+ * NameResolver.Args#getProxyDetector}, and pass the returned {@link ProxiedSocketAddress} to
* {@link NameResolver.Listener#onAddresses}. The DNS name resolver shipped with gRPC is already
* doing so.
*
diff --git a/api/src/main/java/io/grpc/Server.java b/api/src/main/java/io/grpc/Server.java
index 781455b18ee..31e0a6478ed 100644
--- a/api/src/main/java/io/grpc/Server.java
+++ b/api/src/main/java/io/grpc/Server.java
@@ -43,7 +43,7 @@ public abstract class Server {
* listening socket(s).
*
* @return {@code this} object
- * @throws IllegalStateException if already started
+ * @throws IllegalStateException if already started or shut down
* @throws IOException if unable to bind
* @since 1.0.0
*/
@@ -119,6 +119,9 @@ public List getMutableServices() {
* {@link #awaitTermination()} or {@link #awaitTermination(long, TimeUnit)} needs to be called to
* wait for existing calls to finish.
*
+ * Calling this method before {@code start()} will shut down and terminate the server like
+ * normal, but prevents starting the server in the future.
+ *
* @return {@code this} object
* @since 1.0.0
*/
@@ -130,6 +133,9 @@ public List getMutableServices() {
* return {@code false} immediately after this method returns. After this call returns, this
* server has released the listening socket(s) and may be reused by another server.
*
+ * Calling this method before {@code start()} will shut down and terminate the server like
+ * normal, but prevents starting the server in the future.
+ *
* @return {@code this} object
* @since 1.0.0
*/
@@ -157,6 +163,9 @@ public List getMutableServices() {
/**
* Waits for the server to become terminated, giving up if the timeout is reached.
*
+ * Calling this method before {@code start()} or {@code shutdown()} is permitted and does not
+ * change its behavior.
+ *
* @return whether the server is terminated, as would be done by {@link #isTerminated()}.
*/
public abstract boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
@@ -164,6 +173,9 @@ public List getMutableServices() {
/**
* Waits for the server to become terminated.
*
+ * Calling this method before {@code start()} or {@code shutdown()} is permitted and does not
+ * change its behavior.
+ *
* @since 1.0.0
*/
public abstract void awaitTermination() throws InterruptedException;
diff --git a/api/src/test/java/io/grpc/CallOptionsTest.java b/api/src/test/java/io/grpc/CallOptionsTest.java
index 31861306891..0bc0d357358 100644
--- a/api/src/test/java/io/grpc/CallOptionsTest.java
+++ b/api/src/test/java/io/grpc/CallOptionsTest.java
@@ -30,6 +30,7 @@
import static org.mockito.Mockito.mock;
import com.google.common.base.Objects;
+import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.internal.SerializingExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -271,7 +272,7 @@ public void increment(long period, TimeUnit unit) {
}
}
- private static class FakeTracerFactory extends ClientStreamTracer.Factory {
+ private static class FakeTracerFactory extends ClientStreamTracer.InternalLimitedInfoFactory {
final String name;
FakeTracerFactory(String name) {
@@ -279,8 +280,7 @@ private static class FakeTracerFactory extends ClientStreamTracer.Factory {
}
@Override
- public ClientStreamTracer newClientStreamTracer(
- ClientStreamTracer.StreamInfo info, Metadata headers) {
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
return new ClientStreamTracer() {};
}
diff --git a/binder/build.gradle b/binder/build.gradle
index 537c23a0092..c5bb9885623 100644
--- a/binder/build.gradle
+++ b/binder/build.gradle
@@ -13,6 +13,8 @@ android {
srcDirs += "${projectDir}/../core/src/test/java/"
setIncludes(["io/grpc/internal/FakeClock.java",
"io/grpc/binder/**"])
+ exclude 'io/grpc/binder/ServerSecurityPolicyTest.java'
+ exclude 'io/grpc/binder/SecurityPoliciesTest.java'
}
}
androidTest {
diff --git a/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java b/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java
index 0d3c3bf4b51..b99114bb501 100644
--- a/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java
+++ b/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java
@@ -30,6 +30,7 @@
import com.google.protobuf.Empty;
import io.grpc.Attributes;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.Server;
@@ -68,6 +69,10 @@
*/
@RunWith(AndroidJUnit4.class)
public final class BinderClientTransportTest {
+ private static final ClientStreamTracer[] tracers = new ClientStreamTracer[] {
+ new ClientStreamTracer() {}
+ };
+
private final Context appContext = ApplicationProvider.getApplicationContext();
MethodDescriptor.Marshaller marshaller =
@@ -155,7 +160,8 @@ public void tearDown() throws Exception {
@Test
public void testShutdownBeforeStreamStart_b153326034() throws Exception {
- ClientStream stream = transport.newStream(methodDesc, new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = transport.newStream(
+ methodDesc, new Metadata(), CallOptions.DEFAULT, tracers);
transport.shutdownNow(Status.UNKNOWN.withDescription("reasons"));
// This shouldn't throw an exception.
@@ -165,7 +171,7 @@ public void testShutdownBeforeStreamStart_b153326034() throws Exception {
@Test
public void testRequestWhileStreamIsWaitingOnCall_b154088869() throws Exception {
ClientStream stream =
- transport.newStream(streamingMethodDesc, new Metadata(), CallOptions.DEFAULT);
+ transport.newStream(streamingMethodDesc, new Metadata(), CallOptions.DEFAULT, tracers);
stream.start(streamListener);
stream.writeMessage(marshaller.stream(Empty.getDefaultInstance()));
@@ -183,7 +189,7 @@ public void testRequestWhileStreamIsWaitingOnCall_b154088869() throws Exception
@Test
public void testTransactionForDiscardedCall_b155244043() throws Exception {
ClientStream stream =
- transport.newStream(streamingMethodDesc, new Metadata(), CallOptions.DEFAULT);
+ transport.newStream(streamingMethodDesc, new Metadata(), CallOptions.DEFAULT, tracers);
stream.start(streamListener);
stream.writeMessage(marshaller.stream(Empty.getDefaultInstance()));
@@ -201,7 +207,7 @@ public void testTransactionForDiscardedCall_b155244043() throws Exception {
@Test
public void testBadTransactionStreamThroughput_b163053382() throws Exception {
ClientStream stream =
- transport.newStream(streamingMethodDesc, new Metadata(), CallOptions.DEFAULT);
+ transport.newStream(streamingMethodDesc, new Metadata(), CallOptions.DEFAULT, tracers);
stream.start(streamListener);
stream.writeMessage(marshaller.stream(Empty.getDefaultInstance()));
@@ -220,7 +226,7 @@ public void testBadTransactionStreamThroughput_b163053382() throws Exception {
@Test
public void testMessageProducerClosedAfterStream_b169313545() {
ClientStream stream =
- transport.newStream(methodDesc, new Metadata(), CallOptions.DEFAULT);
+ transport.newStream(methodDesc, new Metadata(), CallOptions.DEFAULT, tracers);
stream.start(streamListener);
stream.writeMessage(marshaller.stream(Empty.getDefaultInstance()));
diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java
index 04070ddfcef..b132844069c 100644
--- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java
+++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java
@@ -32,6 +32,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.Attributes;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.Grpc;
import io.grpc.Internal;
import io.grpc.InternalChannelz.SocketStats;
@@ -632,28 +633,28 @@ public synchronized Runnable start(ManagedClientTransport.Listener clientTranspo
public synchronized ClientStream newStream(
final MethodDescriptor, ?> method,
final Metadata headers,
- final CallOptions callOptions) {
+ final CallOptions callOptions,
+ ClientStreamTracer[] tracers) {
if (isShutdown()) {
- return newFailingClientStream(shutdownStatus, callOptions, attributes, headers);
+ return newFailingClientStream(shutdownStatus, attributes, headers, tracers);
} else {
int callId = latestCallId++;
if (latestCallId == LAST_CALL_ID) {
latestCallId = FIRST_CALL_ID;
}
+ StatsTraceContext statsTraceContext =
+ StatsTraceContext.newClientContext(tracers, attributes, headers);
Inbound.ClientInbound inbound =
new Inbound.ClientInbound(
this, attributes, callId, GrpcUtil.shouldBeCountedForInUse(callOptions));
if (ongoingCalls.putIfAbsent(callId, inbound) != null) {
Status failure = Status.INTERNAL.withDescription("Clashing call IDs");
shutdownInternal(failure, true);
- return newFailingClientStream(failure, callOptions, attributes, headers);
+ return newFailingClientStream(failure, attributes, headers, tracers);
} else {
if (inbound.countsForInUse() && numInUseStreams.getAndIncrement() == 0) {
clientTransportListener.transportInUse(true);
}
- StatsTraceContext statsTraceContext =
- StatsTraceContext.newClientContext(callOptions, attributes, headers);
-
Outbound.ClientOutbound outbound =
new Outbound.ClientOutbound(this, callId, method, headers, statsTraceContext);
if (method.getType().clientSendsOneMessage()) {
@@ -763,12 +764,12 @@ protected void handlePingResponse(Parcel parcel) {
}
private static ClientStream newFailingClientStream(
- Status failure, CallOptions callOptions, Attributes attributes, Metadata headers) {
+ Status failure, Attributes attributes, Metadata headers,
+ ClientStreamTracer[] tracers) {
StatsTraceContext statsTraceContext =
- StatsTraceContext.newClientContext(callOptions, attributes, headers);
+ StatsTraceContext.newClientContext(tracers, attributes, headers);
statsTraceContext.clientOutboundHeaders();
- statsTraceContext.streamClosed(failure);
- return new FailingClientStream(failure);
+ return new FailingClientStream(failure, tracers);
}
private static InternalLogId buildLogId(
diff --git a/build.gradle b/build.gradle
index 94590cfa97c..001ebd148c0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -18,7 +18,7 @@ subprojects {
apply plugin: "net.ltgt.errorprone"
group = "io.grpc"
- version = "1.40.0-SNAPSHOT" // CURRENT_GRPC_VERSION
+ version = "1.41.0" // CURRENT_GRPC_VERSION
repositories {
maven { // The google mirror is less flaky than mavenCentral()
@@ -145,9 +145,9 @@ subprojects {
animalsniffer_annotations: "org.codehaus.mojo:animal-sniffer-annotations:1.19",
autovalue: "com.google.auto.value:auto-value:${autovalueVersion}",
autovalue_annotation: "com.google.auto.value:auto-value-annotations:${autovalueVersion}",
- errorprone: "com.google.errorprone:error_prone_annotations:2.4.0",
- cronet_api: 'org.chromium.net:cronet-api:76.3809.111',
- cronet_embedded: 'org.chromium.net:cronet-embedded:66.3359.158',
+ errorprone: "com.google.errorprone:error_prone_annotations:2.9.0",
+ cronet_api: 'org.chromium.net:cronet-api:92.4515.131',
+ cronet_embedded: 'org.chromium.net:cronet-embedded:92.4515.131',
gson: "com.google.code.gson:gson:2.8.6",
guava: "com.google.guava:guava:${guavaVersion}",
javax_annotation: 'org.apache.tomcat:annotations-api:6.0.53',
@@ -269,11 +269,7 @@ subprojects {
jar.manifest {
attributes('Implementation-Title': name,
- 'Implementation-Version': version,
- 'Built-By': System.getProperty('user.name'),
- 'Built-JDK': System.getProperty('java.version'),
- 'Source-Compatibility': sourceCompatibility,
- 'Target-Compatibility': targetCompatibility)
+ 'Implementation-Version': version)
}
javadoc.options {
diff --git a/buildscripts/kokoro/android-interop.sh b/buildscripts/kokoro/android-interop.sh
index 8a8a2bc7bc5..5d9774bb12f 100755
--- a/buildscripts/kokoro/android-interop.sh
+++ b/buildscripts/kokoro/android-interop.sh
@@ -38,18 +38,3 @@ gcloud firebase test android run \
--device model=Nexus6P,version=23,locale=en,orientation=portrait \
--device model=Nexus6,version=22,locale=en,orientation=portrait \
--device model=Nexus6,version=21,locale=en,orientation=portrait
-
-# Build and run binder transport instrumentation tests on Firebase Test Lab
-cd ../binder
-../gradlew assembleDebug
-../gradlew assembleDebugAndroidTest
-gcloud firebase test android run \
- --type instrumentation \
- --test build/outputs/apk/androidTest/debug/grpc-binder-debug-androidTest.apk \
- --device model=Nexus6P,version=27,locale=en,orientation=portrait \
- --device model=Nexus6P,version=26,locale=en,orientation=portrait \
- --device model=Nexus6P,version=25,locale=en,orientation=portrait \
- --device model=Nexus6P,version=24,locale=en,orientation=portrait \
- --device model=Nexus6P,version=23,locale=en,orientation=portrait \
- --device model=Nexus6,version=22,locale=en,orientation=portrait \
- --device model=Nexus6,version=21,locale=en,orientation=portrait
diff --git a/buildscripts/kokoro/android.sh b/buildscripts/kokoro/android.sh
index 50337ef878b..7b9e7f53885 100755
--- a/buildscripts/kokoro/android.sh
+++ b/buildscripts/kokoro/android.sh
@@ -18,8 +18,9 @@ export OS_NAME=$(uname)
cat <> gradle.properties
# defaults to -Xmx512m -XX:MaxMetaspaceSize=256m
# https://docs.gradle.org/current/userguide/build_environment.html#sec:configuring_jvm_memory
-# Increased due to java.lang.OutOfMemoryError: Metaspace failures
-org.gradle.jvmargs=-Xmx512m -XX:MaxMetaspaceSize=512m
+# Increased due to java.lang.OutOfMemoryError: Metaspace failures, "JVM heap
+# space is exhausted", and to increase build speed
+org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
EOF
echo y | ${ANDROID_HOME}/tools/bin/sdkmanager "build-tools;28.0.3"
@@ -31,6 +32,8 @@ buildscripts/make_dependencies.sh
:grpc-android-interop-testing:build \
:grpc-android:build \
:grpc-cronet:build \
+ :grpc-binder:build \
+ assembleAndroidTest \
publishToMavenLocal
if [[ ! -z $(git status --porcelain) ]]; then
diff --git a/buildscripts/kokoro/xds-k8s.sh b/buildscripts/kokoro/xds-k8s.sh
index cafd884ccaf..0a234f2c6ef 100755
--- a/buildscripts/kokoro/xds-k8s.sh
+++ b/buildscripts/kokoro/xds-k8s.sh
@@ -54,10 +54,16 @@ build_test_app_docker_images() {
cp -v "${docker_dir}/"*.Dockerfile "${build_dir}"
cp -v "${docker_dir}/"*.properties "${build_dir}"
cp -rv "${SRC_DIR}/${BUILD_APP_PATH}" "${build_dir}"
+ # Pick a branch name for the built image
+ if [[ -n $KOKORO_JOB_NAME ]]; then
+ branch_name=$(echo "$KOKORO_JOB_NAME" | sed -E 's|^grpc/java/([^/]+)/.*|\1|')
+ else
+ branch_name='experimental'
+ fi
# Run Google Cloud Build
gcloud builds submit "${build_dir}" \
--config "${docker_dir}/cloudbuild.yaml" \
- --substitutions "_SERVER_IMAGE_NAME=${SERVER_IMAGE_NAME},_CLIENT_IMAGE_NAME=${CLIENT_IMAGE_NAME},COMMIT_SHA=${GIT_COMMIT}"
+ --substitutions "_SERVER_IMAGE_NAME=${SERVER_IMAGE_NAME},_CLIENT_IMAGE_NAME=${CLIENT_IMAGE_NAME},COMMIT_SHA=${GIT_COMMIT},BRANCH_NAME=${branch_name}"
# TODO(sergiitk): extra "cosmetic" tags for versioned branches, e.g. v1.34.x
# TODO(sergiitk): do this when adding support for custom configs per version
}
diff --git a/buildscripts/kokoro/xds_url_map.cfg b/buildscripts/kokoro/xds_url_map.cfg
index 36ff8398b0c..4b5be84f880 100644
--- a/buildscripts/kokoro/xds_url_map.cfg
+++ b/buildscripts/kokoro/xds_url_map.cfg
@@ -2,7 +2,7 @@
# Location of the continuous shell script in repository.
build_file: "grpc-java/buildscripts/kokoro/xds_url_map.sh"
-timeout_mins: 60
+timeout_mins: 90
action {
define_artifacts {
diff --git a/buildscripts/kokoro/xds_url_map.sh b/buildscripts/kokoro/xds_url_map.sh
index cbb1552835b..d8487582980 100755
--- a/buildscripts/kokoro/xds_url_map.sh
+++ b/buildscripts/kokoro/xds_url_map.sh
@@ -4,8 +4,8 @@ set -eo pipefail
# Constants
readonly GITHUB_REPOSITORY_NAME="grpc-java"
# GKE Cluster
-readonly GKE_CLUSTER_NAME="interop-test-psm-sec-v2-us-central1-a"
-readonly GKE_CLUSTER_ZONE="us-central1-a"
+readonly GKE_CLUSTER_NAME="interop-test-psm-basic"
+readonly GKE_CLUSTER_ZONE="us-central1-c"
## xDS test client Docker images
readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-client"
readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}"
@@ -54,7 +54,7 @@ build_test_app_docker_images() {
# Run Google Cloud Build
gcloud builds submit "${build_dir}" \
--config "${docker_dir}/cloudbuild.yaml" \
- --substitutions "_CLIENT_IMAGE_NAME=${CLIENT_IMAGE_NAME},COMMIT_SHA=${GIT_COMMIT}"
+ --substitutions "_CLIENT_IMAGE_NAME=${CLIENT_IMAGE_NAME},COMMIT_SHA=${GIT_COMMIT},BRANCH_NAME=experimental"
# TODO(sergiitk): extra "cosmetic" tags for versioned branches, e.g. v1.34.x
# TODO(sergiitk): do this when adding support for custom configs per version
}
diff --git a/buildscripts/xds-k8s/cloudbuild.yaml b/buildscripts/xds-k8s/cloudbuild.yaml
index 03c57489214..577ed73ce58 100644
--- a/buildscripts/xds-k8s/cloudbuild.yaml
+++ b/buildscripts/xds-k8s/cloudbuild.yaml
@@ -3,6 +3,7 @@ steps:
args:
- 'build'
- '--tag=${_SERVER_IMAGE_NAME}:${COMMIT_SHA}'
+ - '--tag=${_SERVER_IMAGE_NAME}:${BRANCH_NAME}'
- '--file=test-server.Dockerfile'
- '.'
@@ -10,6 +11,7 @@ steps:
args:
- 'build'
- '--tag=${_CLIENT_IMAGE_NAME}:${COMMIT_SHA}'
+ - '--tag=${_CLIENT_IMAGE_NAME}:${BRANCH_NAME}'
- '--file=test-client.Dockerfile'
- '.'
@@ -19,4 +21,6 @@ substitutions:
images:
- '${_SERVER_IMAGE_NAME}:${COMMIT_SHA}'
+ - '${_SERVER_IMAGE_NAME}:${BRANCH_NAME}'
- '${_CLIENT_IMAGE_NAME}:${COMMIT_SHA}'
+ - '${_CLIENT_IMAGE_NAME}:${BRANCH_NAME}'
diff --git a/census/src/main/java/io/grpc/census/CensusStatsModule.java b/census/src/main/java/io/grpc/census/CensusStatsModule.java
index d625a6f5c6f..de860d0854c 100644
--- a/census/src/main/java/io/grpc/census/CensusStatsModule.java
+++ b/census/src/main/java/io/grpc/census/CensusStatsModule.java
@@ -17,26 +17,30 @@
package io.grpc.census;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier;
+import io.grpc.Attributes;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ClientStreamTracer;
+import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.Context;
+import io.grpc.Deadline;
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.ServerStreamTracer;
import io.grpc.Status;
+import io.grpc.Status.Code;
import io.grpc.StreamTracer;
import io.grpc.census.internal.DeprecatedCensusConstants;
import io.opencensus.contrib.grpc.metrics.RpcMeasureConstants;
+import io.opencensus.stats.Measure;
import io.opencensus.stats.Measure.MeasureDouble;
import io.opencensus.stats.Measure.MeasureLong;
import io.opencensus.stats.MeasureMap;
@@ -50,19 +54,22 @@
import io.opencensus.tags.propagation.TagContextSerializationException;
import io.opencensus.tags.unsafe.ContextUtils;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
/**
* Provides factories for {@link StreamTracer} that records stats to Census.
*
- * On the client-side, a factory is created for each call, because ClientCall starts earlier than
- * the ClientStream, and in some cases may even not create a ClientStream at all. Therefore, it's
- * the factory that reports the summary to Census.
+ *
On the client-side, a factory is created for each call, and the factory creates a stream
+ * tracer for each attempt. If there is no stream created when the call is ended, we still create a
+ * tracer. It's the tracer that reports per-attempt stats, and the factory that reports the stats
+ * of the overall RPC, such as RETRIES_PER_CALL, to Census.
*
*
On the server-side, there is only one ServerStream per each ServerCall, and ServerStream
* starts earlier than the ServerCall. Therefore, only one tracer is created per stream/call and
@@ -138,15 +145,6 @@ public TagContext parseBytes(byte[] serialized) {
});
}
- /**
- * Creates a {@link ClientCallTracer} for a new call.
- */
- @VisibleForTesting
- ClientCallTracer newClientCallTracer(
- TagContext parentCtx, String fullMethodName) {
- return new ClientCallTracer(this, parentCtx, fullMethodName);
- }
-
/**
* Returns the server tracer factory.
*/
@@ -176,7 +174,6 @@ private void recordRealTimeMetric(TagContext ctx, MeasureLong measure, long valu
}
private static final class ClientTracer extends ClientStreamTracer {
-
@Nullable private static final AtomicLongFieldUpdater outboundMessageCountUpdater;
@Nullable private static final AtomicLongFieldUpdater inboundMessageCountUpdater;
@Nullable private static final AtomicLongFieldUpdater outboundWireSizeUpdater;
@@ -230,19 +227,41 @@ private static final class ClientTracer extends ClientStreamTracer {
inboundUncompressedSizeUpdater = tmpInboundUncompressedSizeUpdater;
}
- private final CensusStatsModule module;
- private final TagContext startCtx;
-
+ final Stopwatch stopwatch;
+ final CallAttemptsTracerFactory attemptsState;
+ final AtomicBoolean inboundReceivedOrClosed = new AtomicBoolean();
+ final CensusStatsModule module;
+ final TagContext parentCtx;
+ final TagContext startCtx;
+ final StreamInfo info;
volatile long outboundMessageCount;
volatile long inboundMessageCount;
volatile long outboundWireSize;
volatile long inboundWireSize;
volatile long outboundUncompressedSize;
volatile long inboundUncompressedSize;
+ long roundtripNanos;
+ Code statusCode;
+
+ ClientTracer(
+ CallAttemptsTracerFactory attemptsState, CensusStatsModule module, TagContext parentCtx,
+ TagContext startCtx, StreamInfo info) {
+ this.attemptsState = attemptsState;
+ this.module = module;
+ this.parentCtx = parentCtx;
+ this.startCtx = startCtx;
+ this.info = info;
+ this.stopwatch = module.stopwatchSupplier.get().start();
+ }
- ClientTracer(CensusStatsModule module, TagContext startCtx) {
- this.module = checkNotNull(module, "module");
- this.startCtx = checkNotNull(startCtx, "startCtx");
+ @Override
+ public void streamCreated(Attributes transportAttrs, Metadata headers) {
+ if (module.propagateTags) {
+ headers.discardAll(module.statsHeader);
+ if (!module.tagger.empty().equals(parentCtx)) {
+ headers.put(module.statsHeader, parentCtx);
+ }
+ }
}
@Override
@@ -292,6 +311,11 @@ public void inboundUncompressedSize(long bytes) {
@Override
@SuppressWarnings("NonAtomicVolatileUpdate")
public void inboundMessage(int seqNo) {
+ if (inboundReceivedOrClosed.compareAndSet(false, true)) {
+ // Because inboundUncompressedSize() might be called after streamClosed(),
+ // we will report stats in callEnded(). Note that this attempt is already committed.
+ attemptsState.inboundMetricTracer = this;
+ }
if (inboundMessageCountUpdater != null) {
inboundMessageCountUpdater.getAndIncrement(this);
} else {
@@ -312,55 +336,109 @@ public void outboundMessage(int seqNo) {
module.recordRealTimeMetric(
startCtx, RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_METHOD, 1);
}
- }
- @VisibleForTesting
- static final class ClientCallTracer extends ClientStreamTracer.Factory {
- @Nullable
- private static final AtomicReferenceFieldUpdater
- streamTracerUpdater;
-
- @Nullable private static final AtomicIntegerFieldUpdater callEndedUpdater;
+ @Override
+ public void streamClosed(Status status) {
+ attemptsState.attemptEnded();
+ stopwatch.stop();
+ roundtripNanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
+ Deadline deadline = info.getCallOptions().getDeadline();
+ statusCode = status.getCode();
+ if (statusCode == Status.Code.CANCELLED && deadline != null) {
+ // When the server's deadline expires, it can only reset the stream with CANCEL and no
+ // description. Since our timer may be delayed in firing, we double-check the deadline and
+ // turn the failure into the likely more helpful DEADLINE_EXCEEDED status.
+ if (deadline.isExpired()) {
+ statusCode = Code.DEADLINE_EXCEEDED;
+ }
+ }
+ if (inboundReceivedOrClosed.compareAndSet(false, true)) {
+ if (module.recordFinishedRpcs) {
+ // Stream is closed early. So no need to record metrics for any inbound events after this
+ // point.
+ recordFinishedAttempt();
+ }
+ } // Otherwise will report stats in callEnded() to guarantee all inbound metrics are recorded.
+ }
- /**
- * When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their
- * JDK reflection API that triggers a NoSuchFieldException. When this occurs, we fallback to
- * (potentially racy) direct updates of the volatile variables.
- */
- static {
- AtomicReferenceFieldUpdater tmpStreamTracerUpdater;
- AtomicIntegerFieldUpdater tmpCallEndedUpdater;
- try {
- tmpStreamTracerUpdater =
- AtomicReferenceFieldUpdater.newUpdater(
- ClientCallTracer.class, ClientTracer.class, "streamTracer");
- tmpCallEndedUpdater =
- AtomicIntegerFieldUpdater.newUpdater(ClientCallTracer.class, "callEnded");
- } catch (Throwable t) {
- logger.log(Level.SEVERE, "Creating atomic field updaters failed", t);
- tmpStreamTracerUpdater = null;
- tmpCallEndedUpdater = null;
+ void recordFinishedAttempt() {
+ MeasureMap measureMap = module.statsRecorder.newMeasureMap()
+ // TODO(songya): remove the deprecated measure constants once they are completed removed.
+ .put(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT, 1)
+ // The latency is double value
+ .put(
+ DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY,
+ roundtripNanos / NANOS_PER_MILLI)
+ .put(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT, outboundMessageCount)
+ .put(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT, inboundMessageCount)
+ .put(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES, outboundWireSize)
+ .put(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES, inboundWireSize)
+ .put(
+ DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES,
+ outboundUncompressedSize)
+ .put(
+ DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES,
+ inboundUncompressedSize);
+ if (statusCode != Code.OK) {
+ measureMap.put(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT, 1);
}
- streamTracerUpdater = tmpStreamTracerUpdater;
- callEndedUpdater = tmpCallEndedUpdater;
+ TagValue statusTag = TagValue.create(statusCode.toString());
+ measureMap.record(
+ module
+ .tagger
+ .toBuilder(startCtx)
+ .putLocal(RpcMeasureConstants.GRPC_CLIENT_STATUS, statusTag)
+ .build());
}
+ }
+ @VisibleForTesting
+ static final class CallAttemptsTracerFactory extends
+ ClientStreamTracer.InternalLimitedInfoFactory {
+ static final MeasureLong RETRIES_PER_CALL =
+ Measure.MeasureLong.create(
+ "grpc.io/client/retries_per_call", "Number of retries per call", "1");
+ static final MeasureLong TRANSPARENT_RETRIES_PER_CALL =
+ Measure.MeasureLong.create(
+ "grpc.io/client/transparent_retries_per_call", "Transparent retries per call", "1");
+ static final MeasureDouble RETRY_DELAY_PER_CALL =
+ Measure.MeasureDouble.create(
+ "grpc.io/client/retry_delay_per_call", "Retry delay per call", "ms");
+
+ ClientTracer inboundMetricTracer;
private final CensusStatsModule module;
private final Stopwatch stopwatch;
- private volatile ClientTracer streamTracer;
- private volatile int callEnded;
+ @GuardedBy("lock")
+ private boolean callEnded;
private final TagContext parentCtx;
private final TagContext startCtx;
-
- ClientCallTracer(CensusStatsModule module, TagContext parentCtx, String fullMethodName) {
- this.module = checkNotNull(module);
- this.parentCtx = checkNotNull(parentCtx);
+ private final String fullMethodName;
+
+ // TODO(zdapeng): optimize memory allocation using AtomicFieldUpdater.
+ private final AtomicLong attemptsPerCall = new AtomicLong();
+ private final AtomicLong transparentRetriesPerCall = new AtomicLong();
+ // write happens before read
+ private Status status;
+ private final Object lock = new Object();
+ // write @GuardedBy("lock") and happens before read
+ private long retryDelayNanos;
+ @GuardedBy("lock")
+ private int activeStreams;
+ @GuardedBy("lock")
+ private boolean finishedCallToBeRecorded;
+
+ CallAttemptsTracerFactory(
+ CensusStatsModule module, TagContext parentCtx, String fullMethodName) {
+ this.module = checkNotNull(module, "module");
+ this.parentCtx = checkNotNull(parentCtx, "parentCtx");
+ this.fullMethodName = checkNotNull(fullMethodName, "fullMethodName");
+ this.stopwatch = module.stopwatchSupplier.get();
TagValue methodTag = TagValue.create(fullMethodName);
- this.startCtx = module.tagger.toBuilder(parentCtx)
+ startCtx = module.tagger.toBuilder(parentCtx)
.putLocal(RpcMeasureConstants.GRPC_CLIENT_METHOD, methodTag)
.build();
- this.stopwatch = module.stopwatchSupplier.get().start();
if (module.recordStartedRpcs) {
+ // Record here in case newClientStreamTracer() would never be called.
module.statsRecorder.newMeasureMap()
.put(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT, 1)
.record(startCtx);
@@ -368,81 +446,97 @@ static final class ClientCallTracer extends ClientStreamTracer.Factory {
}
@Override
- public ClientStreamTracer newClientStreamTracer(
- ClientStreamTracer.StreamInfo info, Metadata headers) {
- ClientTracer tracer = new ClientTracer(module, startCtx);
- // TODO(zhangkun83): Once retry or hedging is implemented, a ClientCall may start more than
- // one streams. We will need to update this file to support them.
- if (streamTracerUpdater != null) {
- checkState(
- streamTracerUpdater.compareAndSet(this, null, tracer),
- "Are you creating multiple streams per call? This class doesn't yet support this case");
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata metadata) {
+ synchronized (lock) {
+ if (finishedCallToBeRecorded) {
+ // This can be the case when the called is cancelled but a retry attempt is created.
+ return new ClientStreamTracer() {};
+ }
+ if (++activeStreams == 1 && stopwatch.isRunning()) {
+ stopwatch.stop();
+ retryDelayNanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
+ }
+ }
+ if (module.recordStartedRpcs && attemptsPerCall.get() > 0) {
+ module.statsRecorder.newMeasureMap()
+ .put(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT, 1)
+ .record(startCtx);
+ }
+ if (info.isTransparentRetry()) {
+ transparentRetriesPerCall.incrementAndGet();
} else {
- checkState(
- streamTracer == null,
- "Are you creating multiple streams per call? This class doesn't yet support this case");
- streamTracer = tracer;
+ attemptsPerCall.incrementAndGet();
}
- if (module.propagateTags) {
- headers.discardAll(module.statsHeader);
- if (!module.tagger.empty().equals(parentCtx)) {
- headers.put(module.statsHeader, parentCtx);
+ return new ClientTracer(this, module, parentCtx, startCtx, info);
+ }
+
+ // Called whenever each attempt is ended.
+ void attemptEnded() {
+ if (!module.recordFinishedRpcs) {
+ return;
+ }
+ boolean shouldRecordFinishedCall = false;
+ synchronized (lock) {
+ if (--activeStreams == 0) {
+ stopwatch.start();
+ if (callEnded && !finishedCallToBeRecorded) {
+ shouldRecordFinishedCall = true;
+ finishedCallToBeRecorded = true;
+ }
}
}
- return tracer;
+ if (shouldRecordFinishedCall) {
+ recordFinishedCall();
+ }
}
- /**
- * Record a finished call and mark the current time as the end time.
- *
- * Can be called from any thread without synchronization. Calling it the second time or more
- * is a no-op.
- */
void callEnded(Status status) {
- if (callEndedUpdater != null) {
- if (callEndedUpdater.getAndSet(this, 1) != 0) {
+ if (!module.recordFinishedRpcs) {
+ return;
+ }
+ this.status = status;
+ boolean shouldRecordFinishedCall = false;
+ synchronized (lock) {
+ if (callEnded) {
+ // TODO(https://github.com/grpc/grpc-java/issues/7921): this shouldn't happen
return;
}
- } else {
- if (callEnded != 0) {
- return;
+ callEnded = true;
+ if (activeStreams == 0 && !finishedCallToBeRecorded) {
+ shouldRecordFinishedCall = true;
+ finishedCallToBeRecorded = true;
}
- callEnded = 1;
}
- if (!module.recordFinishedRpcs) {
- return;
+ if (shouldRecordFinishedCall) {
+ recordFinishedCall();
}
- stopwatch.stop();
- long roundtripNanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
- ClientTracer tracer = streamTracer;
- if (tracer == null) {
- tracer = new ClientTracer(module, startCtx);
+ }
+
+ void recordFinishedCall() {
+ if (attemptsPerCall.get() == 0) {
+ ClientTracer tracer = new ClientTracer(this, module, parentCtx, startCtx, null);
+ tracer.roundtripNanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
+ tracer.statusCode = status.getCode();
+ tracer.recordFinishedAttempt();
+ } else if (inboundMetricTracer != null) {
+ inboundMetricTracer.recordFinishedAttempt();
}
- MeasureMap measureMap = module.statsRecorder.newMeasureMap()
- // TODO(songya): remove the deprecated measure constants once they are completed removed.
- .put(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT, 1)
- // The latency is double value
- .put(
- DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY,
- roundtripNanos / NANOS_PER_MILLI)
- .put(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT, tracer.outboundMessageCount)
- .put(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT, tracer.inboundMessageCount)
- .put(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES, tracer.outboundWireSize)
- .put(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES, tracer.inboundWireSize)
- .put(
- DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES,
- tracer.outboundUncompressedSize)
- .put(
- DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES,
- tracer.inboundUncompressedSize);
- if (!status.isOk()) {
- measureMap.put(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT, 1);
+
+ long retriesPerCall = 0;
+ long attempts = attemptsPerCall.get();
+ if (attempts > 0) {
+ retriesPerCall = attempts - 1;
}
+ MeasureMap measureMap = module.statsRecorder.newMeasureMap()
+ .put(RETRIES_PER_CALL, retriesPerCall)
+ .put(TRANSPARENT_RETRIES_PER_CALL, transparentRetriesPerCall.get())
+ .put(RETRY_DELAY_PER_CALL, retryDelayNanos / NANOS_PER_MILLI);
+ TagValue methodTag = TagValue.create(fullMethodName);
TagValue statusTag = TagValue.create(status.getCode().toString());
measureMap.record(
- module
- .tagger
- .toBuilder(startCtx)
+ module.tagger
+ .toBuilder(parentCtx)
+ .putLocal(RpcMeasureConstants.GRPC_CLIENT_METHOD, methodTag)
.putLocal(RpcMeasureConstants.GRPC_CLIENT_STATUS, statusTag)
.build());
}
@@ -686,8 +780,8 @@ public ClientCall interceptCall(
MethodDescriptor method, CallOptions callOptions, Channel next) {
// New RPCs on client-side inherit the tag context from the current Context.
TagContext parentCtx = tagger.getCurrentTagContext();
- final ClientCallTracer tracerFactory =
- newClientCallTracer(parentCtx, method.getFullMethodName());
+ final CallAttemptsTracerFactory tracerFactory = new CallAttemptsTracerFactory(
+ CensusStatsModule.this, parentCtx, method.getFullMethodName());
ClientCall call =
next.newCall(method, callOptions.withStreamTracerFactory(tracerFactory));
return new SimpleForwardingClientCall(call) {
diff --git a/census/src/main/java/io/grpc/census/CensusTracingModule.java b/census/src/main/java/io/grpc/census/CensusTracingModule.java
index fc35d89db55..08d5fe3ca97 100644
--- a/census/src/main/java/io/grpc/census/CensusTracingModule.java
+++ b/census/src/main/java/io/grpc/census/CensusTracingModule.java
@@ -19,6 +19,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Attributes;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
@@ -31,6 +32,7 @@
import io.grpc.MethodDescriptor;
import io.grpc.ServerStreamTracer;
import io.grpc.StreamTracer;
+import io.opencensus.trace.AttributeValue;
import io.opencensus.trace.BlankSpan;
import io.opencensus.trace.EndSpanOptions;
import io.opencensus.trace.MessageEvent;
@@ -59,7 +61,8 @@
final class CensusTracingModule {
private static final Logger logger = Logger.getLogger(CensusTracingModule.class.getName());
- @Nullable private static final AtomicIntegerFieldUpdater callEndedUpdater;
+ @Nullable
+ private static final AtomicIntegerFieldUpdater callEndedUpdater;
@Nullable private static final AtomicIntegerFieldUpdater streamClosedUpdater;
@@ -69,11 +72,11 @@ final class CensusTracingModule {
* (potentially racy) direct updates of the volatile variables.
*/
static {
- AtomicIntegerFieldUpdater tmpCallEndedUpdater;
+ AtomicIntegerFieldUpdater tmpCallEndedUpdater;
AtomicIntegerFieldUpdater tmpStreamClosedUpdater;
try {
tmpCallEndedUpdater =
- AtomicIntegerFieldUpdater.newUpdater(ClientCallTracer.class, "callEnded");
+ AtomicIntegerFieldUpdater.newUpdater(CallAttemptsTracerFactory.class, "callEnded");
tmpStreamClosedUpdater =
AtomicIntegerFieldUpdater.newUpdater(ServerTracer.class, "streamClosed");
} catch (Throwable t) {
@@ -115,11 +118,12 @@ public SpanContext parseBytes(byte[] serialized) {
}
/**
- * Creates a {@link ClientCallTracer} for a new call.
+ * Creates a {@link CallAttemptsTracerFactory} for a new call.
*/
@VisibleForTesting
- ClientCallTracer newClientCallTracer(@Nullable Span parentSpan, MethodDescriptor, ?> method) {
- return new ClientCallTracer(parentSpan, method);
+ CallAttemptsTracerFactory newClientCallTracer(
+ @Nullable Span parentSpan, MethodDescriptor, ?> method) {
+ return new CallAttemptsTracerFactory(parentSpan, method);
}
/**
@@ -222,19 +226,21 @@ private static void recordMessageEvent(
}
@VisibleForTesting
- final class ClientCallTracer extends ClientStreamTracer.Factory {
+ final class CallAttemptsTracerFactory extends ClientStreamTracer.InternalLimitedInfoFactory {
volatile int callEnded;
private final boolean isSampledToLocalTracing;
private final Span span;
+ private final String fullMethodName;
- ClientCallTracer(@Nullable Span parentSpan, MethodDescriptor, ?> method) {
+ CallAttemptsTracerFactory(@Nullable Span parentSpan, MethodDescriptor, ?> method) {
checkNotNull(method, "method");
this.isSampledToLocalTracing = method.isSampledToLocalTracing();
+ this.fullMethodName = method.getFullMethodName();
this.span =
censusTracer
.spanBuilderWithExplicitParent(
- generateTraceSpanName(false, method.getFullMethodName()),
+ generateTraceSpanName(false, fullMethodName),
parentSpan)
.setRecordEvents(true)
.startSpan();
@@ -243,11 +249,17 @@ final class ClientCallTracer extends ClientStreamTracer.Factory {
@Override
public ClientStreamTracer newClientStreamTracer(
ClientStreamTracer.StreamInfo info, Metadata headers) {
- if (span != BlankSpan.INSTANCE) {
- headers.discardAll(tracingHeader);
- headers.put(tracingHeader, span.getContext());
- }
- return new ClientTracer(span);
+ Span attemptSpan = censusTracer
+ .spanBuilderWithExplicitParent(
+ "Attempt." + fullMethodName.replace('/', '.'),
+ span)
+ .setRecordEvents(true)
+ .startSpan();
+ attemptSpan.putAttribute(
+ "previous-rpc-attempts", AttributeValue.longAttributeValue(info.getPreviousAttempts()));
+ attemptSpan.putAttribute(
+ "transparent-retry", AttributeValue.booleanAttributeValue(info.isTransparentRetry()));
+ return new ClientTracer(attemptSpan, tracingHeader, isSampledToLocalTracing);
}
/**
@@ -273,9 +285,22 @@ void callEnded(io.grpc.Status status) {
private static final class ClientTracer extends ClientStreamTracer {
private final Span span;
+ final Metadata.Key tracingHeader;
+ final boolean isSampledToLocalTracing;
- ClientTracer(Span span) {
+ ClientTracer(
+ Span span, Metadata.Key tracingHeader, boolean isSampledToLocalTracing) {
this.span = checkNotNull(span, "span");
+ this.tracingHeader = tracingHeader;
+ this.isSampledToLocalTracing = isSampledToLocalTracing;
+ }
+
+ @Override
+ public void streamCreated(Attributes transportAtts, Metadata headers) {
+ if (span != BlankSpan.INSTANCE) {
+ headers.discardAll(tracingHeader);
+ headers.put(tracingHeader, span.getContext());
+ }
}
@Override
@@ -291,6 +316,11 @@ public void inboundMessageRead(
recordMessageEvent(
span, MessageEvent.Type.RECEIVED, seqNo, optionalWireSize, optionalUncompressedSize);
}
+
+ @Override
+ public void streamClosed(io.grpc.Status status) {
+ span.end(createEndSpanOptions(status, isSampledToLocalTracing));
+ }
}
@@ -381,7 +411,7 @@ public ClientCall interceptCall(
// Safe usage of the unsafe trace API because CONTEXT_SPAN_KEY.get() returns the same value
// as Tracer.getCurrentSpan() except when no value available when the return value is null
// for the direct access and BlankSpan when Tracer API is used.
- final ClientCallTracer tracerFactory =
+ final CallAttemptsTracerFactory tracerFactory =
newClientCallTracer(ContextUtils.getValue(Context.current()), method);
ClientCall call =
next.newCall(
diff --git a/census/src/test/java/io/grpc/census/CensusModulesTest.java b/census/src/test/java/io/grpc/census/CensusModulesTest.java
index fbbcd44150c..d285c8fe8c2 100644
--- a/census/src/test/java/io/grpc/census/CensusModulesTest.java
+++ b/census/src/test/java/io/grpc/census/CensusModulesTest.java
@@ -18,6 +18,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static io.grpc.census.CensusStatsModule.CallAttemptsTracerFactory.RETRIES_PER_CALL;
+import static io.grpc.census.CensusStatsModule.CallAttemptsTracerFactory.RETRY_DELAY_PER_CALL;
+import static io.grpc.census.CensusStatsModule.CallAttemptsTracerFactory.TRANSPARENT_RETRIES_PER_CALL;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -58,6 +61,7 @@
import io.grpc.ServerStreamTracer;
import io.grpc.ServerStreamTracer.ServerCallInfo;
import io.grpc.Status;
+import io.grpc.census.CensusTracingModule.CallAttemptsTracerFactory;
import io.grpc.census.internal.DeprecatedCensusConstants;
import io.grpc.internal.FakeClock;
import io.grpc.internal.testing.StatsTestUtils;
@@ -81,6 +85,7 @@
import io.opencensus.stats.View;
import io.opencensus.tags.TagContext;
import io.opencensus.tags.TagValue;
+import io.opencensus.trace.AttributeValue;
import io.opencensus.trace.BlankSpan;
import io.opencensus.trace.EndSpanOptions;
import io.opencensus.trace.MessageEvent;
@@ -173,10 +178,12 @@ public String parse(InputStream stream) {
private final Random random = new Random(1234);
private final Span fakeClientParentSpan = MockableSpan.generateRandomSpan(random);
private final Span spyClientSpan = spy(MockableSpan.generateRandomSpan(random));
- private final SpanContext fakeClientSpanContext = spyClientSpan.getContext();
+ private final Span spyAttemptSpan = spy(MockableSpan.generateRandomSpan(random));
+ private final SpanContext fakeAttemptSpanContext = spyAttemptSpan.getContext();
private final Span spyServerSpan = spy(MockableSpan.generateRandomSpan(random));
private final byte[] binarySpanContext = new byte[]{3, 1, 5};
private final SpanBuilder spyClientSpanBuilder = spy(new MockableSpan.Builder());
+ private final SpanBuilder spyAttemptSpanBuilder = spy(new MockableSpan.Builder());
private final SpanBuilder spyServerSpanBuilder = spy(new MockableSpan.Builder());
@Rule
@@ -201,15 +208,20 @@ public String parse(InputStream stream) {
@Before
public void setUp() throws Exception {
when(spyClientSpanBuilder.startSpan()).thenReturn(spyClientSpan);
- when(tracer.spanBuilderWithExplicitParent(anyString(), ArgumentMatchers.any()))
+ when(spyAttemptSpanBuilder.startSpan()).thenReturn(spyAttemptSpan);
+ when(tracer.spanBuilderWithExplicitParent(
+ eq("Sent.package1.service2.method3"), ArgumentMatchers.any()))
.thenReturn(spyClientSpanBuilder);
+ when(tracer.spanBuilderWithExplicitParent(
+ eq("Attempt.package1.service2.method3"), ArgumentMatchers.any()))
+ .thenReturn(spyAttemptSpanBuilder);
when(spyServerSpanBuilder.startSpan()).thenReturn(spyServerSpan);
when(tracer.spanBuilderWithRemoteParent(anyString(), ArgumentMatchers.any()))
.thenReturn(spyServerSpanBuilder);
when(mockTracingPropagationHandler.toByteArray(any(SpanContext.class)))
.thenReturn(binarySpanContext);
when(mockTracingPropagationHandler.fromByteArray(any(byte[].class)))
- .thenReturn(fakeClientSpanContext);
+ .thenReturn(fakeAttemptSpanContext);
censusStats =
new CensusStatsModule(
tagger, tagCtxSerializer, statsRecorder, fakeClock.getStopwatchSupplier(),
@@ -292,10 +304,10 @@ public ClientCall interceptCall(
assertEquals(2, capturedCallOptions.get().getStreamTracerFactories().size());
assertTrue(
capturedCallOptions.get().getStreamTracerFactories().get(0)
- instanceof CensusTracingModule.ClientCallTracer);
+ instanceof CallAttemptsTracerFactory);
assertTrue(
capturedCallOptions.get().getStreamTracerFactories().get(1)
- instanceof CensusStatsModule.ClientCallTracer);
+ instanceof CensusStatsModule.CallAttemptsTracerFactory);
// Make the call
Metadata headers = new Metadata();
@@ -355,6 +367,7 @@ record = statsRecorder.pollRecord();
.setSampleToLocalSpanStore(false)
.build());
verify(spyClientSpan, never()).end();
+ assertZeroRetryRecorded();
}
@Test
@@ -388,11 +401,12 @@ private void subtestClientBasicStatsDefaultContext(
new CensusStatsModule(
tagger, tagCtxSerializer, statsRecorder, fakeClock.getStopwatchSupplier(),
true, recordStarts, recordFinishes, recordRealTime);
- CensusStatsModule.ClientCallTracer callTracer =
- localCensusStats.newClientCallTracer(
- tagger.empty(), method.getFullMethodName());
+ CensusStatsModule.CallAttemptsTracerFactory callAttemptsTracerFactory =
+ new CensusStatsModule.CallAttemptsTracerFactory(
+ localCensusStats, tagger.empty(), method.getFullMethodName());
Metadata headers = new Metadata();
- ClientStreamTracer tracer = callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ ClientStreamTracer tracer =
+ callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, headers);
if (recordStarts) {
StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
@@ -455,7 +469,7 @@ private void subtestClientBasicStatsDefaultContext(
tracer.inboundUncompressedSize(552);
tracer.streamClosed(Status.OK);
- callTracer.callEnded(Status.OK);
+ callAttemptsTracerFactory.callEnded(Status.OK);
if (recordFinishes) {
StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
@@ -488,11 +502,200 @@ private void subtestClientBasicStatsDefaultContext(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
assertEquals(30 + 100 + 16 + 24,
record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+ assertZeroRetryRecorded();
} else {
assertNull(statsRecorder.pollRecord());
}
}
+ // This test is only unit-testing the stat recording logic. The retry behavior is faked.
+ @Test
+ public void recordRetryStats() {
+ CensusStatsModule localCensusStats =
+ new CensusStatsModule(
+ tagger, tagCtxSerializer, statsRecorder, fakeClock.getStopwatchSupplier(),
+ true, true, true, true);
+ CensusStatsModule.CallAttemptsTracerFactory callAttemptsTracerFactory =
+ new CensusStatsModule.CallAttemptsTracerFactory(
+ localCensusStats, tagger.empty(), method.getFullMethodName());
+ ClientStreamTracer tracer =
+ callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, new Metadata());
+
+ StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
+ assertEquals(1, record.tags.size());
+ TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
+ assertEquals(method.getFullMethodName(), methodTag.asString());
+ assertEquals(
+ 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+
+ fakeClock.forwardTime(30, MILLISECONDS);
+ tracer.outboundHeaders();
+ fakeClock.forwardTime(100, MILLISECONDS);
+ tracer.outboundMessage(0);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_METHOD, 1, true, true);
+ tracer.outboundMessage(1);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_METHOD, 1, true, true);
+ tracer.outboundWireSize(1028);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_METHOD, 1028, true, true);
+ tracer.outboundUncompressedSize(1128);
+ fakeClock.forwardTime(24, MILLISECONDS);
+ tracer.streamClosed(Status.UNAVAILABLE);
+ record = statsRecorder.pollRecord();
+ methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
+ assertEquals(method.getFullMethodName(), methodTag.asString());
+ TagValue statusTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_STATUS);
+ assertEquals(Status.Code.UNAVAILABLE.toString(), statusTag.asString());
+ assertEquals(
+ 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
+ assertEquals(
+ 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ assertEquals(
+ 1028, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+ assertEquals(
+ 1128,
+ record.getMetricAsLongOrFail(
+ DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
+ assertEquals(
+ 30 + 100 + 24,
+ record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+
+ // faking retry
+ fakeClock.forwardTime(1000, MILLISECONDS);
+ tracer = callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, new Metadata());
+ record = statsRecorder.pollRecord();
+ assertEquals(1, record.tags.size());
+ methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
+ assertEquals(method.getFullMethodName(), methodTag.asString());
+ assertEquals(
+ 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+ tracer.outboundHeaders();
+ tracer.outboundMessage(0);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_METHOD, 1, true, true);
+ tracer.outboundMessage(1);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_METHOD, 1, true, true);
+ tracer.outboundWireSize(1028);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_METHOD, 1028, true, true);
+ tracer.outboundUncompressedSize(1128);
+ fakeClock.forwardTime(100, MILLISECONDS);
+ tracer.streamClosed(Status.NOT_FOUND);
+ record = statsRecorder.pollRecord();
+ methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
+ assertEquals(method.getFullMethodName(), methodTag.asString());
+ statusTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_STATUS);
+ assertEquals(Status.Code.NOT_FOUND.toString(), statusTag.asString());
+ assertEquals(
+ 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
+ assertEquals(
+ 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ assertEquals(
+ 1028, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+ assertEquals(
+ 1128,
+ record.getMetricAsLongOrFail(
+ DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
+ assertEquals(
+ 100 ,
+ record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+
+ // fake transparent retry
+ fakeClock.forwardTime(10, MILLISECONDS);
+ tracer = callAttemptsTracerFactory.newClientStreamTracer(
+ STREAM_INFO.toBuilder().setIsTransparentRetry(true).build(), new Metadata());
+ record = statsRecorder.pollRecord();
+ assertEquals(1, record.tags.size());
+ methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
+ assertEquals(method.getFullMethodName(), methodTag.asString());
+ assertEquals(
+ 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+ tracer.streamClosed(Status.UNAVAILABLE);
+ record = statsRecorder.pollRecord();
+ statusTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_STATUS);
+ assertEquals(Status.Code.UNAVAILABLE.toString(), statusTag.asString());
+ assertEquals(
+ 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
+ assertEquals(
+ 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ assertEquals(
+ 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+
+ // fake another transparent retry
+ fakeClock.forwardTime(10, MILLISECONDS);
+ tracer = callAttemptsTracerFactory.newClientStreamTracer(
+ STREAM_INFO.toBuilder().setIsTransparentRetry(true).build(), new Metadata());
+ record = statsRecorder.pollRecord();
+ assertEquals(1, record.tags.size());
+ assertEquals(
+ 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+ tracer.outboundHeaders();
+ tracer.outboundMessage(0);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_METHOD, 1, true, true);
+ tracer.outboundMessage(1);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_METHOD, 1, true, true);
+ tracer.outboundWireSize(1028);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_METHOD, 1028, true, true);
+ tracer.outboundUncompressedSize(1128);
+ fakeClock.forwardTime(16, MILLISECONDS);
+ tracer.inboundMessage(0);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_METHOD, 1, true, true);
+ tracer.inboundWireSize(33);
+ assertRealTimeMetric(
+ RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_METHOD, 33, true, true);
+ tracer.inboundUncompressedSize(67);
+ fakeClock.forwardTime(24, MILLISECONDS);
+ // RPC succeeded
+ tracer.streamClosed(Status.OK);
+ callAttemptsTracerFactory.callEnded(Status.OK);
+
+ record = statsRecorder.pollRecord();
+ statusTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_STATUS);
+ assertEquals(Status.Code.OK.toString(), statusTag.asString());
+ assertEquals(
+ 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT));
+ assertThat(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT)).isNull();
+ assertEquals(
+ 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ assertEquals(
+ 1028, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+ assertEquals(
+ 1128,
+ record.getMetricAsLongOrFail(
+ DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
+ assertEquals(
+ 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT));
+ assertEquals(
+ 33,
+ record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES));
+ assertEquals(
+ 67,
+ record.getMetricAsLongOrFail(
+ DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
+ assertEquals(
+ 16 + 24 ,
+ record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+
+ record = statsRecorder.pollRecord();
+ methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
+ assertEquals(method.getFullMethodName(), methodTag.asString());
+ statusTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_STATUS);
+ assertEquals(Status.Code.OK.toString(), statusTag.asString());
+ assertThat(record.getMetric(RETRIES_PER_CALL)).isEqualTo(1);
+ assertThat(record.getMetric(TRANSPARENT_RETRIES_PER_CALL)).isEqualTo(2);
+ assertThat(record.getMetric(RETRY_DELAY_PER_CALL)).isEqualTo(1000D + 10 + 10);
+ }
+
private void assertRealTimeMetric(
Measure measure, long expectedValue, boolean recordRealTimeMetrics, boolean clientSide) {
StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
@@ -516,15 +719,28 @@ private void assertRealTimeMetric(
assertEquals(expectedValue, record.getMetricAsLongOrFail(measure));
}
+ private void assertZeroRetryRecorded() {
+ StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
+ TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
+ assertEquals(method.getFullMethodName(), methodTag.asString());
+ assertThat(record.getMetric(RETRIES_PER_CALL)).isEqualTo(0);
+ assertThat(record.getMetric(TRANSPARENT_RETRIES_PER_CALL)).isEqualTo(0);
+ assertThat(record.getMetric(RETRY_DELAY_PER_CALL)).isEqualTo(0D);
+ }
+
@Test
public void clientBasicTracingDefaultSpan() {
- CensusTracingModule.ClientCallTracer callTracer =
+ CallAttemptsTracerFactory callTracer =
censusTracing.newClientCallTracer(null, method);
Metadata headers = new Metadata();
ClientStreamTracer clientStreamTracer = callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ clientStreamTracer.streamCreated(Attributes.EMPTY, headers);
verify(tracer).spanBuilderWithExplicitParent(
eq("Sent.package1.service2.method3"), ArgumentMatchers.isNull());
+ verify(tracer).spanBuilderWithExplicitParent(
+ eq("Attempt.package1.service2.method3"), eq(spyClientSpan));
verify(spyClientSpan, never()).end(any(EndSpanOptions.class));
+ verify(spyAttemptSpan, never()).end(any(EndSpanOptions.class));
clientStreamTracer.outboundMessage(0);
clientStreamTracer.outboundMessageSent(0, 882, -1);
@@ -536,8 +752,12 @@ public void clientBasicTracingDefaultSpan() {
clientStreamTracer.streamClosed(Status.OK);
callTracer.callEnded(Status.OK);
- InOrder inOrder = inOrder(spyClientSpan);
- inOrder.verify(spyClientSpan, times(3)).addMessageEvent(messageEventCaptor.capture());
+ InOrder inOrder = inOrder(spyClientSpan, spyAttemptSpan);
+ inOrder.verify(spyAttemptSpan)
+ .putAttribute("previous-rpc-attempts", AttributeValue.longAttributeValue(0));
+ inOrder.verify(spyAttemptSpan)
+ .putAttribute("transparent-retry", AttributeValue.booleanAttributeValue(false));
+ inOrder.verify(spyAttemptSpan, times(3)).addMessageEvent(messageEventCaptor.capture());
List events = messageEventCaptor.getAllValues();
assertEquals(
MessageEvent.builder(MessageEvent.Type.SENT, 0).setCompressedMessageSize(882).build(),
@@ -551,18 +771,23 @@ public void clientBasicTracingDefaultSpan() {
.setUncompressedMessageSize(90)
.build(),
events.get(2));
+ inOrder.verify(spyAttemptSpan).end(
+ EndSpanOptions.builder()
+ .setStatus(io.opencensus.trace.Status.OK)
+ .setSampleToLocalSpanStore(false)
+ .build());
inOrder.verify(spyClientSpan).end(
EndSpanOptions.builder()
.setStatus(io.opencensus.trace.Status.OK)
.setSampleToLocalSpanStore(false)
.build());
- verifyNoMoreInteractions(spyClientSpan);
+ inOrder.verifyNoMoreInteractions();
verifyNoMoreInteractions(tracer);
}
@Test
public void clientTracingSampledToLocalSpanStore() {
- CensusTracingModule.ClientCallTracer callTracer =
+ CallAttemptsTracerFactory callTracer =
censusTracing.newClientCallTracer(null, sampledMethod);
callTracer.callEnded(Status.OK);
@@ -575,11 +800,15 @@ public void clientTracingSampledToLocalSpanStore() {
@Test
public void clientStreamNeverCreatedStillRecordStats() {
- CensusStatsModule.ClientCallTracer callTracer =
- censusStats.newClientCallTracer(tagger.empty(), method.getFullMethodName());
-
+ CensusStatsModule.CallAttemptsTracerFactory callAttemptsTracerFactory =
+ new CensusStatsModule.CallAttemptsTracerFactory(
+ censusStats, tagger.empty(), method.getFullMethodName());
+ ClientStreamTracer streamTracer =
+ callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, new Metadata());
fakeClock.forwardTime(3000, MILLISECONDS);
- callTracer.callEnded(Status.DEADLINE_EXCEEDED.withDescription("3 seconds"));
+ Status status = Status.DEADLINE_EXCEEDED.withDescription("3 seconds");
+ streamTracer.streamClosed(status);
+ callAttemptsTracerFactory.callEnded(status);
// Upstart record
StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
@@ -625,11 +854,12 @@ record = statsRecorder.pollRecord();
3000,
record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_SERVER_ELAPSED_TIME));
+ assertZeroRetryRecorded();
}
@Test
public void clientStreamNeverCreatedStillRecordTracing() {
- CensusTracingModule.ClientCallTracer callTracer =
+ CallAttemptsTracerFactory callTracer =
censusTracing.newClientCallTracer(fakeClientParentSpan, method);
verify(tracer).spanBuilderWithExplicitParent(
eq("Sent.package1.service2.method3"), same(fakeClientParentSpan));
@@ -680,10 +910,13 @@ private void subtestStatsHeadersPropagateTags(boolean propagate, boolean recordS
fakeClock.getStopwatchSupplier(),
propagate, recordStats, recordStats, recordStats);
Metadata headers = new Metadata();
- CensusStatsModule.ClientCallTracer callTracer =
- census.newClientCallTracer(clientCtx, method.getFullMethodName());
+ CensusStatsModule.CallAttemptsTracerFactory callAttemptsTracerFactory =
+ new CensusStatsModule.CallAttemptsTracerFactory(
+ census, clientCtx, method.getFullMethodName());
// This propagates clientCtx to headers if propagates==true
- callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ ClientStreamTracer streamTracer =
+ callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, headers);
+ streamTracer.streamCreated(Attributes.EMPTY, headers);
if (recordStats) {
// Client upstart record
StatsTestUtils.MetricsRecord clientRecord = statsRecorder.pollRecord();
@@ -746,7 +979,8 @@ private void subtestStatsHeadersPropagateTags(boolean propagate, boolean recordS
// Verifies that the client tracer factory uses clientCtx, which includes the custom tags, to
// record stats.
- callTracer.callEnded(Status.OK);
+ streamTracer.streamClosed(Status.OK);
+ callAttemptsTracerFactory.callEnded(Status.OK);
if (recordStats) {
// Client completion record
@@ -760,6 +994,7 @@ private void subtestStatsHeadersPropagateTags(boolean propagate, boolean recordS
assertNull(clientRecord.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
TagValue clientPropagatedTag = clientRecord.tags.get(StatsTestUtils.EXTRA_TAG);
assertEquals("extra-tag-value-897", clientPropagatedTag.asString());
+ assertZeroRetryRecorded();
}
if (!recordStats) {
@@ -769,10 +1004,12 @@ private void subtestStatsHeadersPropagateTags(boolean propagate, boolean recordS
@Test
public void statsHeadersNotPropagateDefaultContext() {
- CensusStatsModule.ClientCallTracer callTracer =
- censusStats.newClientCallTracer(tagger.empty(), method.getFullMethodName());
+ CensusStatsModule.CallAttemptsTracerFactory callAttemptsTracerFactory =
+ new CensusStatsModule.CallAttemptsTracerFactory(
+ censusStats, tagger.empty(), method.getFullMethodName());
Metadata headers = new Metadata();
- callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, headers)
+ .streamCreated(Attributes.EMPTY, headers);
assertFalse(headers.containsKey(censusStats.statsHeader));
// Clear recorded stats to satisfy the assertions in wrapUp()
statsRecorder.rolloverRecords();
@@ -800,15 +1037,18 @@ public void statsHeaderMalformed() {
@Test
public void traceHeadersPropagateSpanContext() throws Exception {
- CensusTracingModule.ClientCallTracer callTracer =
+ CallAttemptsTracerFactory callTracer =
censusTracing.newClientCallTracer(fakeClientParentSpan, method);
Metadata headers = new Metadata();
- callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ ClientStreamTracer streamTracer = callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ streamTracer.streamCreated(Attributes.EMPTY, headers);
- verify(mockTracingPropagationHandler).toByteArray(same(fakeClientSpanContext));
+ verify(mockTracingPropagationHandler).toByteArray(same(fakeAttemptSpanContext));
verifyNoMoreInteractions(mockTracingPropagationHandler);
verify(tracer).spanBuilderWithExplicitParent(
eq("Sent.package1.service2.method3"), same(fakeClientParentSpan));
+ verify(tracer).spanBuilderWithExplicitParent(
+ eq("Attempt.package1.service2.method3"), same(spyClientSpan));
verify(spyClientSpanBuilder).setRecordEvents(eq(true));
verifyNoMoreInteractions(tracer);
assertTrue(headers.containsKey(censusTracing.tracingHeader));
@@ -818,7 +1058,7 @@ public void traceHeadersPropagateSpanContext() throws Exception {
method.getFullMethodName(), headers);
verify(mockTracingPropagationHandler).fromByteArray(same(binarySpanContext));
verify(tracer).spanBuilderWithRemoteParent(
- eq("Recv.package1.service2.method3"), same(spyClientSpan.getContext()));
+ eq("Recv.package1.service2.method3"), same(spyAttemptSpan.getContext()));
verify(spyServerSpanBuilder).setRecordEvents(eq(true));
Context filteredContext = serverTracer.filterContext(Context.ROOT);
@@ -827,11 +1067,12 @@ public void traceHeadersPropagateSpanContext() throws Exception {
@Test
public void traceHeaders_propagateSpanContext() throws Exception {
- CensusTracingModule.ClientCallTracer callTracer =
+ CallAttemptsTracerFactory callTracer =
censusTracing.newClientCallTracer(fakeClientParentSpan, method);
Metadata headers = new Metadata();
- callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ ClientStreamTracer streamTracer = callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ streamTracer.streamCreated(Attributes.EMPTY, headers);
assertThat(headers.keys()).isNotEmpty();
}
@@ -840,12 +1081,14 @@ public void traceHeaders_propagateSpanContext() throws Exception {
public void traceHeaders_missingCensusImpl_notPropagateSpanContext()
throws Exception {
reset(spyClientSpanBuilder);
+ reset(spyAttemptSpanBuilder);
when(spyClientSpanBuilder.startSpan()).thenReturn(BlankSpan.INSTANCE);
+ when(spyAttemptSpanBuilder.startSpan()).thenReturn(BlankSpan.INSTANCE);
Metadata headers = new Metadata();
- CensusTracingModule.ClientCallTracer callTracer =
+ CallAttemptsTracerFactory callTracer =
censusTracing.newClientCallTracer(BlankSpan.INSTANCE, method);
- callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ callTracer.newClientStreamTracer(STREAM_INFO, headers).streamCreated(Attributes.EMPTY, headers);
assertThat(headers.keys()).isEmpty();
}
@@ -853,16 +1096,18 @@ public void traceHeaders_missingCensusImpl_notPropagateSpanContext()
@Test
public void traceHeaders_clientMissingCensusImpl_preservingHeaders() throws Exception {
reset(spyClientSpanBuilder);
+ reset(spyAttemptSpanBuilder);
when(spyClientSpanBuilder.startSpan()).thenReturn(BlankSpan.INSTANCE);
+ when(spyAttemptSpanBuilder.startSpan()).thenReturn(BlankSpan.INSTANCE);
Metadata headers = new Metadata();
headers.put(
Metadata.Key.of("never-used-key-bin", Metadata.BINARY_BYTE_MARSHALLER),
new byte[] {});
Set originalHeaderKeys = new HashSet<>(headers.keys());
- CensusTracingModule.ClientCallTracer callTracer =
+ CallAttemptsTracerFactory callTracer =
censusTracing.newClientCallTracer(BlankSpan.INSTANCE, method);
- callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ callTracer.newClientStreamTracer(STREAM_INFO, headers).streamCreated(Attributes.EMPTY, headers);
assertThat(headers.keys()).containsExactlyElementsIn(originalHeaderKeys);
}
@@ -871,9 +1116,9 @@ public void traceHeaders_clientMissingCensusImpl_preservingHeaders() throws Exce
public void traceHeaderMalformed() throws Exception {
// As comparison, normal header parsing
Metadata headers = new Metadata();
- headers.put(censusTracing.tracingHeader, fakeClientSpanContext);
+ headers.put(censusTracing.tracingHeader, fakeAttemptSpanContext);
// mockTracingPropagationHandler was stubbed to always return fakeServerParentSpanContext
- assertSame(spyClientSpan.getContext(), headers.get(censusTracing.tracingHeader));
+ assertSame(spyAttemptSpan.getContext(), headers.get(censusTracing.tracingHeader));
// Make BinaryPropagationHandler always throw when parsing the header
when(mockTracingPropagationHandler.fromByteArray(any(byte[].class)))
@@ -881,7 +1126,7 @@ public void traceHeaderMalformed() throws Exception {
headers = new Metadata();
assertNull(headers.get(censusTracing.tracingHeader));
- headers.put(censusTracing.tracingHeader, fakeClientSpanContext);
+ headers.put(censusTracing.tracingHeader, fakeAttemptSpanContext);
assertSame(SpanContext.INVALID, headers.get(censusTracing.tracingHeader));
assertNotSame(spyClientSpan.getContext(), SpanContext.INVALID);
@@ -1186,13 +1431,18 @@ public void newTagsPopulateOldViews() throws InterruptedException {
tagger, tagCtxSerializer, localStats.getStatsRecorder(), fakeClock.getStopwatchSupplier(),
false, false, true, false /* real-time */);
- CensusStatsModule.ClientCallTracer callTracer =
- localCensusStats.newClientCallTracer(
- tagger.empty(), method.getFullMethodName());
+ CensusStatsModule.CallAttemptsTracerFactory callAttemptsTracerFactory =
+ new CensusStatsModule.CallAttemptsTracerFactory(
+ localCensusStats, tagger.empty(), method.getFullMethodName());
- callTracer.newClientStreamTracer(STREAM_INFO, new Metadata());
+ Metadata headers = new Metadata();
+ ClientStreamTracer tracer =
+ callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, headers);
+ tracer.streamCreated(Attributes.EMPTY, headers);
fakeClock.forwardTime(30, MILLISECONDS);
- callTracer.callEnded(Status.PERMISSION_DENIED.withDescription("No you don't"));
+ Status status = Status.PERMISSION_DENIED.withDescription("No you don't");
+ tracer.streamClosed(status);
+ callAttemptsTracerFactory.callEnded(status);
// Give OpenCensus a chance to update the views asynchronously.
Thread.sleep(100);
diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt
index ecf5e3889dd..2beed7b2b7f 100644
--- a/compiler/src/test/golden/TestDeprecatedService.java.txt
+++ b/compiler/src/test/golden/TestDeprecatedService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.40.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.41.0)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
@java.lang.Deprecated
diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt
index 6abbd4732fc..ba2c37f4b81 100644
--- a/compiler/src/test/golden/TestService.java.txt
+++ b/compiler/src/test/golden/TestService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.40.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.41.0)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
public final class TestServiceGrpc {
diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt
index 4de0f949c59..72d1b428efb 100644
--- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt
+++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.40.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.41.0)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
@java.lang.Deprecated
diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt
index b2481063ca6..bc1d50acecc 100644
--- a/compiler/src/testLite/golden/TestService.java.txt
+++ b/compiler/src/testLite/golden/TestService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.40.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.41.0)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
public final class TestServiceGrpc {
diff --git a/core/BUILD.bazel b/core/BUILD.bazel
index c50e86a511c..60a08798d58 100644
--- a/core/BUILD.bazel
+++ b/core/BUILD.bazel
@@ -60,6 +60,7 @@ java_library(
"@com_google_code_findbugs_jsr305//jar",
"@com_google_guava_guava//jar",
"@com_google_j2objc_j2objc_annotations//jar",
+ "@org_codehaus_mojo_animal_sniffer_annotations//jar",
],
)
diff --git a/core/src/jmh/java/io/grpc/internal/StatsTraceContextBenchmark.java b/core/src/jmh/java/io/grpc/internal/StatsTraceContextBenchmark.java
index aec2659f024..4d4349eef1b 100644
--- a/core/src/jmh/java/io/grpc/internal/StatsTraceContextBenchmark.java
+++ b/core/src/jmh/java/io/grpc/internal/StatsTraceContextBenchmark.java
@@ -17,7 +17,7 @@
package io.grpc.internal;
import io.grpc.Attributes;
-import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.ServerStreamTracer;
@@ -50,7 +50,8 @@ public class StatsTraceContextBenchmark {
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public StatsTraceContext newClientContext() {
- return StatsTraceContext.newClientContext(CallOptions.DEFAULT, Attributes.EMPTY, emptyMetadata);
+ return StatsTraceContext.newClientContext(
+ new ClientStreamTracer[] { new ClientStreamTracer() {} }, Attributes.EMPTY, emptyMetadata);
}
/**
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java
index 58df4371e72..895b709559b 100644
--- a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java
+++ b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java
@@ -26,6 +26,7 @@
import com.google.common.util.concurrent.SettableFuture;
import io.grpc.Attributes;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.Compressor;
import io.grpc.Deadline;
import io.grpc.Decompressor;
@@ -205,10 +206,12 @@ public void run() {
@Override
public synchronized ClientStream newStream(
- final MethodDescriptor, ?> method, final Metadata headers, final CallOptions callOptions) {
+ MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions,
+ ClientStreamTracer[] tracers) {
+ StatsTraceContext statsTraceContext =
+ StatsTraceContext.newClientContext(tracers, getAttributes(), headers);
if (shutdownStatus != null) {
- return failedClientStream(
- StatsTraceContext.newClientContext(callOptions, attributes, headers), shutdownStatus);
+ return failedClientStream(statsTraceContext, shutdownStatus);
}
headers.put(GrpcUtil.USER_AGENT_KEY, userAgent);
@@ -226,12 +229,12 @@ public synchronized ClientStream newStream(
"Request metadata larger than %d: %d",
serverMaxInboundMetadataSize,
metadataSize));
- return failedClientStream(
- StatsTraceContext.newClientContext(callOptions, attributes, headers), status);
+ return failedClientStream(statsTraceContext, status);
}
}
- return new InProcessStream(method, headers, callOptions, authority).clientStream;
+ return new InProcessStream(method, headers, callOptions, authority, statsTraceContext)
+ .clientStream;
}
private ClientStream failedClientStream(
@@ -377,12 +380,12 @@ private class InProcessStream {
private InProcessStream(
MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions,
- String authority) {
+ String authority , StatsTraceContext statsTraceContext) {
this.method = checkNotNull(method, "method");
this.headers = checkNotNull(headers, "headers");
this.callOptions = checkNotNull(callOptions, "callOptions");
this.authority = authority;
- this.clientStream = new InProcessClientStream(callOptions, headers);
+ this.clientStream = new InProcessClientStream(callOptions, statsTraceContext);
this.serverStream = new InProcessServerStream(method, headers);
}
@@ -673,9 +676,10 @@ private class InProcessClientStream implements ClientStream {
@GuardedBy("this")
private int outboundSeqNo;
- InProcessClientStream(CallOptions callOptions, Metadata headers) {
+ InProcessClientStream(
+ CallOptions callOptions, StatsTraceContext statsTraceContext) {
this.callOptions = callOptions;
- statsTraceCtx = StatsTraceContext.newClientContext(callOptions, attributes, headers);
+ statsTraceCtx = statsTraceContext;
}
private synchronized void setListener(ServerStreamListener listener) {
diff --git a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
index 0b1ce3514a2..6b6472825d2 100644
--- a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
+++ b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
@@ -25,6 +25,7 @@
import io.grpc.CallOptions;
import io.grpc.ChannelCredentials;
import io.grpc.ChannelLogger;
+import io.grpc.ClientStreamTracer;
import io.grpc.CompositeCallCredentials;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
@@ -104,7 +105,8 @@ protected ConnectionClientTransport delegate() {
@Override
@SuppressWarnings("deprecation")
public ClientStream newStream(
- final MethodDescriptor, ?> method, Metadata headers, final CallOptions callOptions) {
+ final MethodDescriptor, ?> method, Metadata headers, final CallOptions callOptions,
+ ClientStreamTracer[] tracers) {
CallCredentials creds = callOptions.getCredentials();
if (creds == null) {
creds = channelCallCredentials;
@@ -113,10 +115,10 @@ public ClientStream newStream(
}
if (creds != null) {
MetadataApplierImpl applier = new MetadataApplierImpl(
- delegate, method, headers, callOptions, applierListener);
+ delegate, method, headers, callOptions, applierListener, tracers);
if (pendingApplier.incrementAndGet() > 0) {
applierListener.onComplete();
- return new FailingClientStream(shutdownStatus);
+ return new FailingClientStream(shutdownStatus, tracers);
}
RequestInfo requestInfo = new RequestInfo() {
@Override
@@ -152,9 +154,9 @@ public Attributes getTransportAttrs() {
return applier.returnStream();
} else {
if (pendingApplier.get() >= 0) {
- return new FailingClientStream(shutdownStatus);
+ return new FailingClientStream(shutdownStatus, tracers);
}
- return delegate.newStream(method, headers, callOptions);
+ return delegate.newStream(method, headers, callOptions, tracers);
}
}
diff --git a/core/src/main/java/io/grpc/internal/ClientCallImpl.java b/core/src/main/java/io/grpc/internal/ClientCallImpl.java
index c2e1bd2b1f2..dd17244e2a5 100644
--- a/core/src/main/java/io/grpc/internal/ClientCallImpl.java
+++ b/core/src/main/java/io/grpc/internal/ClientCallImpl.java
@@ -33,6 +33,7 @@
import io.grpc.Attributes;
import io.grpc.CallOptions;
import io.grpc.ClientCall;
+import io.grpc.ClientStreamTracer;
import io.grpc.Codec;
import io.grpc.Compressor;
import io.grpc.CompressorRegistry;
@@ -254,9 +255,12 @@ public void runInContext() {
effectiveDeadline, context.getDeadline(), callOptions.getDeadline());
stream = clientStreamProvider.newStream(method, callOptions, headers, context);
} else {
+ ClientStreamTracer[] tracers =
+ GrpcUtil.getClientStreamTracers(callOptions, headers, 0, false);
stream = new FailingClientStream(
DEADLINE_EXCEEDED.withDescription(
- "ClientCall started after deadline exceeded: " + effectiveDeadline));
+ "ClientCall started after deadline exceeded: " + effectiveDeadline),
+ tracers);
}
if (callExecutorIsDirect) {
diff --git a/core/src/main/java/io/grpc/internal/ClientTransport.java b/core/src/main/java/io/grpc/internal/ClientTransport.java
index cc8471ab6a3..a569a7922df 100644
--- a/core/src/main/java/io/grpc/internal/ClientTransport.java
+++ b/core/src/main/java/io/grpc/internal/ClientTransport.java
@@ -17,6 +17,7 @@
package io.grpc.internal;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.InternalChannelz.SocketStats;
import io.grpc.InternalInstrumented;
import io.grpc.Metadata;
@@ -46,10 +47,15 @@ public interface ClientTransport extends InternalInstrumented {
* @param method the descriptor of the remote method to be called for this stream.
* @param headers to send at the beginning of the call
* @param callOptions runtime options of the call
+ * @param tracers a non-empty array of tracers. The last element in it is reserved to be set by
+ * the load balancer's pick result and otherwise is a no-op tracer.
* @return the newly created stream.
*/
// TODO(nmittler): Consider also throwing for stopping.
- ClientStream newStream(MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions);
+ ClientStream newStream(
+ MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions,
+ // Using array for tracers instead of a list or composition for better performance.
+ ClientStreamTracer[] tracers);
/**
* Pings a remote endpoint. When an acknowledgement is received, the given callback will be
diff --git a/core/src/main/java/io/grpc/internal/DelayedClientTransport.java b/core/src/main/java/io/grpc/internal/DelayedClientTransport.java
index 6a72eb7c21e..2b1145d1c4b 100644
--- a/core/src/main/java/io/grpc/internal/DelayedClientTransport.java
+++ b/core/src/main/java/io/grpc/internal/DelayedClientTransport.java
@@ -20,6 +20,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.Context;
import io.grpc.InternalChannelz.SocketStats;
import io.grpc.InternalLogId;
@@ -133,7 +134,8 @@ public void run() {
*/
@Override
public final ClientStream newStream(
- MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions) {
+ MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions,
+ ClientStreamTracer[] tracers) {
try {
PickSubchannelArgs args = new PickSubchannelArgsImpl(method, headers, callOptions);
SubchannelPicker picker = null;
@@ -141,14 +143,14 @@ public final ClientStream newStream(
while (true) {
synchronized (lock) {
if (shutdownStatus != null) {
- return new FailingClientStream(shutdownStatus);
+ return new FailingClientStream(shutdownStatus, tracers);
}
if (lastPicker == null) {
- return createPendingStream(args);
+ return createPendingStream(args, tracers);
}
// Check for second time through the loop, and whether anything changed
if (picker != null && pickerVersion == lastPickerVersion) {
- return createPendingStream(args);
+ return createPendingStream(args, tracers);
}
picker = lastPicker;
pickerVersion = lastPickerVersion;
@@ -158,7 +160,8 @@ public final ClientStream newStream(
callOptions.isWaitForReady());
if (transport != null) {
return transport.newStream(
- args.getMethodDescriptor(), args.getHeaders(), args.getCallOptions());
+ args.getMethodDescriptor(), args.getHeaders(), args.getCallOptions(),
+ tracers);
}
// This picker's conclusion is "buffer". If there hasn't been a newer picker set (possible
// race with reprocess()), we will buffer it. Otherwise, will try with the new picker.
@@ -173,8 +176,9 @@ public final ClientStream newStream(
* schedule tasks on syncContext.
*/
@GuardedBy("lock")
- private PendingStream createPendingStream(PickSubchannelArgs args) {
- PendingStream pendingStream = new PendingStream(args);
+ private PendingStream createPendingStream(
+ PickSubchannelArgs args, ClientStreamTracer[] tracers) {
+ PendingStream pendingStream = new PendingStream(args, tracers);
pendingStreams.add(pendingStream);
if (getPendingStreamsCount() == 1) {
syncContext.executeLater(reportTransportInUse);
@@ -239,7 +243,8 @@ public final void shutdownNow(Status status) {
}
if (savedReportTransportTerminated != null) {
for (PendingStream stream : savedPendingStreams) {
- Runnable runnable = stream.setStream(new FailingClientStream(status, RpcProgress.REFUSED));
+ Runnable runnable = stream.setStream(
+ new FailingClientStream(status, RpcProgress.REFUSED, stream.tracers));
if (runnable != null) {
// Drain in-line instead of using an executor as failing stream just throws everything
// away. This is essentially the same behavior as DelayedStream.cancel() but can be done
@@ -346,9 +351,11 @@ public InternalLogId getLogId() {
private class PendingStream extends DelayedStream {
private final PickSubchannelArgs args;
private final Context context = Context.current();
+ private final ClientStreamTracer[] tracers;
- private PendingStream(PickSubchannelArgs args) {
+ private PendingStream(PickSubchannelArgs args, ClientStreamTracer[] tracers) {
this.args = args;
+ this.tracers = tracers;
}
/** Runnable may be null. */
@@ -357,7 +364,8 @@ private Runnable createRealStream(ClientTransport transport) {
Context origContext = context.attach();
try {
realStream = transport.newStream(
- args.getMethodDescriptor(), args.getHeaders(), args.getCallOptions());
+ args.getMethodDescriptor(), args.getHeaders(), args.getCallOptions(),
+ tracers);
} finally {
context.detach(origContext);
}
@@ -382,6 +390,13 @@ public void cancel(Status reason) {
syncContext.drain();
}
+ @Override
+ protected void onEarlyCancellation(Status reason) {
+ for (ClientStreamTracer tracer : tracers) {
+ tracer.streamClosed(reason);
+ }
+ }
+
@Override
public void appendTimeoutInsight(InsightBuilder insight) {
if (args.getCallOptions().isWaitForReady()) {
diff --git a/core/src/main/java/io/grpc/internal/DelayedStream.java b/core/src/main/java/io/grpc/internal/DelayedStream.java
index f0a378e8124..28ce2764c75 100644
--- a/core/src/main/java/io/grpc/internal/DelayedStream.java
+++ b/core/src/main/java/io/grpc/internal/DelayedStream.java
@@ -324,11 +324,15 @@ public void run() {
});
} else {
drainPendingCalls();
+ onEarlyCancellation(reason);
// Note that listener is a DelayedStreamListener
listener.closed(reason, RpcProgress.PROCESSED, new Metadata());
}
}
+ protected void onEarlyCancellation(Status reason) {
+ }
+
@GuardedBy("this")
private void setRealStream(ClientStream realStream) {
checkState(this.realStream == null, "realStream already set to %s", this.realStream);
diff --git a/core/src/main/java/io/grpc/internal/FailingClientStream.java b/core/src/main/java/io/grpc/internal/FailingClientStream.java
index 6d368b6975f..6388ef8b6ee 100644
--- a/core/src/main/java/io/grpc/internal/FailingClientStream.java
+++ b/core/src/main/java/io/grpc/internal/FailingClientStream.java
@@ -18,6 +18,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.internal.ClientStreamListener.RpcProgress;
@@ -30,27 +31,33 @@ public final class FailingClientStream extends NoopClientStream {
private boolean started;
private final Status error;
private final RpcProgress rpcProgress;
+ private final ClientStreamTracer[] tracers;
/**
* Creates a {@code FailingClientStream} that would fail with the given error.
*/
- public FailingClientStream(Status error) {
- this(error, RpcProgress.PROCESSED);
+ public FailingClientStream(Status error, ClientStreamTracer[] tracers) {
+ this(error, RpcProgress.PROCESSED, tracers);
}
/**
* Creates a {@code FailingClientStream} that would fail with the given error.
*/
- public FailingClientStream(Status error, RpcProgress rpcProgress) {
+ public FailingClientStream(
+ Status error, RpcProgress rpcProgress, ClientStreamTracer[] tracers) {
Preconditions.checkArgument(!error.isOk(), "error must not be OK");
this.error = error;
this.rpcProgress = rpcProgress;
+ this.tracers = tracers;
}
@Override
public void start(ClientStreamListener listener) {
Preconditions.checkState(!started, "already started");
started = true;
+ for (ClientStreamTracer tracer : tracers) {
+ tracer.streamClosed(error);
+ }
listener.closed(error, rpcProgress, new Metadata());
}
diff --git a/core/src/main/java/io/grpc/internal/FailingClientTransport.java b/core/src/main/java/io/grpc/internal/FailingClientTransport.java
index 25d20017c92..5b31e6e5073 100644
--- a/core/src/main/java/io/grpc/internal/FailingClientTransport.java
+++ b/core/src/main/java/io/grpc/internal/FailingClientTransport.java
@@ -21,6 +21,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.InternalChannelz.SocketStats;
import io.grpc.InternalLogId;
import io.grpc.Metadata;
@@ -45,8 +46,9 @@ class FailingClientTransport implements ClientTransport {
@Override
public ClientStream newStream(
- MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions) {
- return new FailingClientStream(error, rpcProgress);
+ MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions,
+ ClientStreamTracer[] tracers) {
+ return new FailingClientStream(error, rpcProgress, tracers);
}
@Override
diff --git a/core/src/main/java/io/grpc/internal/ForwardingClientStreamTracer.java b/core/src/main/java/io/grpc/internal/ForwardingClientStreamTracer.java
new file mode 100644
index 00000000000..fd03564d396
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ForwardingClientStreamTracer.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2021 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.MoreObjects;
+import io.grpc.Attributes;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Metadata;
+import io.grpc.Status;
+
+public abstract class ForwardingClientStreamTracer extends ClientStreamTracer {
+
+ /**
+ * Returns the underlying {@code ClientStreamTracer}.
+ */
+ protected abstract ClientStreamTracer delegate();
+
+ @Override
+ public void streamCreated(Attributes transportAttrs, Metadata headers) {
+ delegate().streamCreated(transportAttrs, headers);
+ }
+
+ @Override
+ public void outboundHeaders() {
+ delegate().outboundHeaders();
+ }
+
+ @Override
+ public void inboundHeaders() {
+ delegate().inboundHeaders();
+ }
+
+ @Override
+ public void inboundTrailers(Metadata trailers) {
+ delegate().inboundTrailers(trailers);
+ }
+
+ @Override
+ public void streamClosed(Status status) {
+ delegate().streamClosed(status);
+ }
+
+ @Override
+ public void outboundMessage(int seqNo) {
+ delegate().outboundMessage(seqNo);
+ }
+
+ @Override
+ public void inboundMessage(int seqNo) {
+ delegate().inboundMessage(seqNo);
+ }
+
+ @Override
+ public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+ delegate().outboundMessageSent(seqNo, optionalWireSize, optionalUncompressedSize);
+ }
+
+ @Override
+ public void inboundMessageRead(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+ delegate().inboundMessageRead(seqNo, optionalWireSize, optionalUncompressedSize);
+ }
+
+ @Override
+ public void outboundWireSize(long bytes) {
+ delegate().outboundWireSize(bytes);
+ }
+
+ @Override
+ public void outboundUncompressedSize(long bytes) {
+ delegate().outboundUncompressedSize(bytes);
+ }
+
+ @Override
+ public void inboundWireSize(long bytes) {
+ delegate().inboundWireSize(bytes);
+ }
+
+ @Override
+ public void inboundUncompressedSize(long bytes) {
+ delegate().inboundUncompressedSize(bytes);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+ }
+}
diff --git a/core/src/main/java/io/grpc/internal/ForwardingConnectionClientTransport.java b/core/src/main/java/io/grpc/internal/ForwardingConnectionClientTransport.java
index e54f8b169d6..bfdccbe5d6a 100644
--- a/core/src/main/java/io/grpc/internal/ForwardingConnectionClientTransport.java
+++ b/core/src/main/java/io/grpc/internal/ForwardingConnectionClientTransport.java
@@ -20,6 +20,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.Attributes;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.InternalChannelz.SocketStats;
import io.grpc.InternalLogId;
import io.grpc.Metadata;
@@ -45,8 +46,9 @@ public void shutdownNow(Status status) {
@Override
public ClientStream newStream(
- MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions) {
- return delegate().newStream(method, headers, callOptions);
+ MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions,
+ ClientStreamTracer[] tracers) {
+ return delegate().newStream(method, headers, callOptions, tracers);
}
@Override
diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java
index 45c0fce7122..12ae8954ce5 100644
--- a/core/src/main/java/io/grpc/internal/GrpcUtil.java
+++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java
@@ -17,6 +17,7 @@
package io.grpc.internal;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
@@ -26,8 +27,11 @@
import com.google.common.base.Supplier;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import io.grpc.Attributes;
import io.grpc.CallOptions;
import io.grpc.ClientStreamTracer;
+import io.grpc.ClientStreamTracer.InternalLimitedInfoFactory;
+import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.InternalChannelz.SocketStats;
import io.grpc.InternalLogId;
import io.grpc.InternalMetadata;
@@ -54,6 +58,7 @@
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Collection;
+import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -197,7 +202,7 @@ public byte[] parseAsciiString(byte[] serialized) {
public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
- private static final String IMPLEMENTATION_VERSION = "1.40.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
+ private static final String IMPLEMENTATION_VERSION = "1.41.0"; // CURRENT_GRPC_VERSION
/**
* The default timeout in nanos for a keepalive ping request.
@@ -253,6 +258,8 @@ public ProxiedSocketAddress proxyFor(SocketAddress targetServerAddress) {
public static final CallOptions.Key CALL_OPTIONS_RPC_OWNED_BY_BALANCER =
CallOptions.Key.create("io.grpc.internal.CALL_OPTIONS_RPC_OWNED_BY_BALANCER");
+ private static final ClientStreamTracer NOOP_TRACER = new ClientStreamTracer() {};
+
/**
* Returns true if an RPC with the given properties should be counted when calculating the
* in-use state of a transport.
@@ -711,9 +718,14 @@ static ClientTransport getTransportFromPickResult(PickResult result, boolean isW
return new ClientTransport() {
@Override
public ClientStream newStream(
- MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions) {
- return transport.newStream(
- method, headers, callOptions.withStreamTracerFactory(streamTracerFactory));
+ MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions,
+ ClientStreamTracer[] tracers) {
+ StreamInfo info = StreamInfo.newBuilder().setCallOptions(callOptions).build();
+ ClientStreamTracer streamTracer =
+ newClientStreamTracer(streamTracerFactory, info, headers);
+ checkState(tracers[tracers.length - 1] == NOOP_TRACER, "lb tracer already assigned");
+ tracers[tracers.length - 1] = streamTracer;
+ return transport.newStream(method, headers, callOptions, tracers);
}
@Override
@@ -743,6 +755,72 @@ public ListenableFuture getStats() {
return null;
}
+ /** Gets stream tracers based on CallOptions. */
+ public static ClientStreamTracer[] getClientStreamTracers(
+ CallOptions callOptions, Metadata headers, int previousAttempts, boolean isTransparentRetry) {
+ List factories = callOptions.getStreamTracerFactories();
+ ClientStreamTracer[] tracers = new ClientStreamTracer[factories.size() + 1];
+ StreamInfo streamInfo = StreamInfo.newBuilder()
+ .setCallOptions(callOptions)
+ .setPreviousAttempts(previousAttempts)
+ .setIsTransparentRetry(isTransparentRetry)
+ .build();
+ for (int i = 0; i < factories.size(); i++) {
+ tracers[i] = newClientStreamTracer(factories.get(i), streamInfo, headers);
+ }
+ // Reserved to be set later by the lb as per the API contract of ClientTransport.newStream().
+ // See also GrpcUtil.getTransportFromPickResult()
+ tracers[tracers.length - 1] = NOOP_TRACER;
+ return tracers;
+ }
+
+ // A util function for backward compatibility to support deprecated StreamInfo.getAttributes().
+ @VisibleForTesting
+ static ClientStreamTracer newClientStreamTracer(
+ final ClientStreamTracer.Factory streamTracerFactory, final StreamInfo info,
+ final Metadata headers) {
+ ClientStreamTracer streamTracer;
+ if (streamTracerFactory instanceof InternalLimitedInfoFactory) {
+ streamTracer = streamTracerFactory.newClientStreamTracer(info, headers);
+ } else {
+ streamTracer = new ForwardingClientStreamTracer() {
+ final ClientStreamTracer noop = new ClientStreamTracer() {};
+ volatile ClientStreamTracer delegate = noop;
+
+ void maybeInit(StreamInfo info, Metadata headers) {
+ if (delegate != noop) {
+ return;
+ }
+ synchronized (this) {
+ if (delegate == noop) {
+ delegate = streamTracerFactory.newClientStreamTracer(info, headers);
+ }
+ }
+ }
+
+ @Override
+ protected ClientStreamTracer delegate() {
+ return delegate;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void streamCreated(Attributes transportAttrs, Metadata headers) {
+ StreamInfo streamInfo = info.toBuilder().setTransportAttrs(transportAttrs).build();
+ maybeInit(streamInfo, headers);
+ delegate().streamCreated(transportAttrs, headers);
+ }
+
+ @Override
+ public void streamClosed(Status status) {
+ maybeInit(info, headers);
+ delegate().streamClosed(status);
+ }
+ };
+ }
+ return streamTracer;
+ }
+
/** Quietly closes all messages in MessageProducer. */
static void closeQuietly(MessageProducer producer) {
InputStream message;
diff --git a/core/src/main/java/io/grpc/internal/InUseStateAggregator.java b/core/src/main/java/io/grpc/internal/InUseStateAggregator.java
index f4f3a186d88..f3d870e8797 100644
--- a/core/src/main/java/io/grpc/internal/InUseStateAggregator.java
+++ b/core/src/main/java/io/grpc/internal/InUseStateAggregator.java
@@ -53,6 +53,21 @@ public final boolean isInUse() {
return !inUseObjects.isEmpty();
}
+ /**
+ * Returns {@code true} if any of the given objects are in use.
+ *
+ * @param objects The objects to consider.
+ * @return {@code true} if any of the given objects are in use.
+ */
+ public final boolean anyObjectInUse(Object... objects) {
+ for (Object object : objects) {
+ if (inUseObjects.contains(object)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Called when the aggregated in-use state has changed to true, which means at least one object is
* in use.
diff --git a/core/src/main/java/io/grpc/internal/InternalSubchannel.java b/core/src/main/java/io/grpc/internal/InternalSubchannel.java
index 331add6c8c4..fa2bf2e46bc 100644
--- a/core/src/main/java/io/grpc/internal/InternalSubchannel.java
+++ b/core/src/main/java/io/grpc/internal/InternalSubchannel.java
@@ -34,6 +34,7 @@
import io.grpc.CallOptions;
import io.grpc.ChannelLogger;
import io.grpc.ChannelLogger.ChannelLogLevel;
+import io.grpc.ClientStreamTracer;
import io.grpc.ConnectivityState;
import io.grpc.ConnectivityStateInfo;
import io.grpc.EquivalentAddressGroup;
@@ -667,8 +668,9 @@ protected ConnectionClientTransport delegate() {
@Override
public ClientStream newStream(
- MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions) {
- final ClientStream streamDelegate = super.newStream(method, headers, callOptions);
+ MethodDescriptor, ?> method, Metadata headers, CallOptions callOptions,
+ ClientStreamTracer[] tracers) {
+ final ClientStream streamDelegate = super.newStream(method, headers, callOptions, tracers);
return new ForwardingClientStream() {
@Override
protected ClientStream delegate() {
diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
index a9d24cd247a..2e079078fc7 100644
--- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
+++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
@@ -423,7 +423,10 @@ private void enterIdleMode() {
delayedTransport.reprocess(null);
channelLogger.log(ChannelLogLevel.INFO, "Entering IDLE state");
channelStateManager.gotoState(IDLE);
- if (inUseStateAggregator.isInUse()) {
+ // If the inUseStateAggregator still considers pending calls to be queued up or the delayed
+ // transport to be holding some we need to exit idle mode to give these calls a chance to
+ // be processed.
+ if (inUseStateAggregator.anyObjectInUse(pendingCallsInUseObject, delayedTransport)) {
exitIdleMode();
}
}
@@ -532,8 +535,10 @@ public ClientStream newStream(
ClientTransport transport =
getTransport(new PickSubchannelArgsImpl(method, headers, callOptions));
Context origContext = context.attach();
+ ClientStreamTracer[] tracers = GrpcUtil.getClientStreamTracers(
+ callOptions, headers, 0, /* isTransparentRetry= */ false);
try {
- return transport.newStream(method, headers, callOptions);
+ return transport.newStream(method, headers, callOptions, tracers);
} finally {
context.detach(origContext);
}
@@ -569,13 +574,17 @@ void postCommit() {
}
@Override
- ClientStream newSubstream(ClientStreamTracer.Factory tracerFactory, Metadata newHeaders) {
- CallOptions newOptions = callOptions.withStreamTracerFactory(tracerFactory);
+ ClientStream newSubstream(
+ Metadata newHeaders, ClientStreamTracer.Factory factory, int previousAttempts,
+ boolean isTransparentRetry) {
+ CallOptions newOptions = callOptions.withStreamTracerFactory(factory);
+ ClientStreamTracer[] tracers = GrpcUtil.getClientStreamTracers(
+ newOptions, newHeaders, previousAttempts, isTransparentRetry);
ClientTransport transport =
getTransport(new PickSubchannelArgsImpl(method, newHeaders, newOptions));
Context origContext = context.attach();
try {
- return transport.newStream(method, newHeaders, newOptions);
+ return transport.newStream(method, newHeaders, newOptions, tracers);
} finally {
context.detach(origContext);
}
@@ -619,7 +628,7 @@ ClientStream newSubstream(ClientStreamTracer.Factory tracerFactory, Metadata new
channelLogger = new ChannelLoggerImpl(channelTracer, timeProvider);
ProxyDetector proxyDetector =
builder.proxyDetector != null ? builder.proxyDetector : GrpcUtil.DEFAULT_PROXY_DETECTOR;
- this.retryEnabled = builder.retryEnabled && !builder.temporarilyDisableRetry;
+ this.retryEnabled = builder.retryEnabled;
this.loadBalancerFactory = new AutoConfiguredLoadBalancerFactory(builder.defaultLbPolicy);
this.offloadExecutorHolder =
new ExecutorHolder(
diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java
index d42b3832136..26c48fc8596 100644
--- a/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java
+++ b/core/src/main/java/io/grpc/internal/ManagedChannelImplBuilder.java
@@ -142,11 +142,7 @@ public static ManagedChannelBuilder> forTarget(String target) {
int maxHedgedAttempts = 5;
long retryBufferSize = DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES;
long perRpcBufferLimit = DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES;
- boolean retryEnabled = false; // TODO(zdapeng): default to true
- // Temporarily disable retry when stats or tracing is enabled to avoid breakage, until we know
- // what should be the desired behavior for retry + stats/tracing.
- // TODO(zdapeng): delete me
- boolean temporarilyDisableRetry;
+ boolean retryEnabled = true;
InternalChannelz channelz = InternalChannelz.instance();
int maxTraceEvents;
@@ -460,8 +456,6 @@ public ManagedChannelImplBuilder disableRetry() {
@Override
public ManagedChannelImplBuilder enableRetry() {
retryEnabled = true;
- statsEnabled = false;
- tracingEnabled = false;
return this;
}
@@ -592,9 +586,6 @@ public void setStatsRecordRealTimeMetrics(boolean value) {
/**
* Disable or enable tracing features. Enabled by default.
- *
- * For the current release, calling {@code setTracingEnabled(true)} may have a side effect that
- * disables retry.
*/
public void setTracingEnabled(boolean value) {
tracingEnabled = value;
@@ -642,9 +633,7 @@ public ManagedChannel build() {
List getEffectiveInterceptors() {
List effectiveInterceptors =
new ArrayList<>(this.interceptors);
- temporarilyDisableRetry = false;
if (statsEnabled) {
- temporarilyDisableRetry = true;
ClientInterceptor statsInterceptor = null;
try {
Class> censusStatsAccessor =
@@ -679,7 +668,6 @@ List getEffectiveInterceptors() {
}
}
if (tracingEnabled) {
- temporarilyDisableRetry = true;
ClientInterceptor tracingInterceptor = null;
try {
Class> censusTracingAccessor =
diff --git a/core/src/main/java/io/grpc/internal/MessageDeframer.java b/core/src/main/java/io/grpc/internal/MessageDeframer.java
index 9a523746e50..534398315e8 100644
--- a/core/src/main/java/io/grpc/internal/MessageDeframer.java
+++ b/core/src/main/java/io/grpc/internal/MessageDeframer.java
@@ -517,8 +517,8 @@ private void reportCount() {
private void verifySize() {
if (count > maxMessageSize) {
throw Status.RESOURCE_EXHAUSTED.withDescription(String.format(
- "Compressed gRPC message exceeds maximum size %d: %d bytes read",
- maxMessageSize, count)).asRuntimeException();
+ "Decompressed gRPC message exceeds maximum size %d",
+ maxMessageSize)).asRuntimeException();
}
}
}
diff --git a/core/src/main/java/io/grpc/internal/MessageFramer.java b/core/src/main/java/io/grpc/internal/MessageFramer.java
index 83592e691a9..2042bddca03 100644
--- a/core/src/main/java/io/grpc/internal/MessageFramer.java
+++ b/core/src/main/java/io/grpc/internal/MessageFramer.java
@@ -267,7 +267,7 @@ private static int writeToOutputStream(InputStream message, OutputStream outputS
return ((Drainable) message).drainTo(outputStream);
} else {
// This makes an unnecessary copy of the bytes when bytebuf supports array(). However, we
- // expect performance-critical code to support flushTo().
+ // expect performance-critical code to support drainTo().
@SuppressWarnings("BetaApi") // ByteStreams is not Beta in v27
long written = ByteStreams.copy(message, outputStream);
checkArgument(written <= Integer.MAX_VALUE, "Message size overflow: %s", written);
diff --git a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
index 76d280b2d00..6893713c1d2 100644
--- a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
+++ b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
@@ -22,6 +22,7 @@
import io.grpc.CallCredentials.MetadataApplier;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.Context;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
@@ -36,7 +37,7 @@ final class MetadataApplierImpl extends MetadataApplier {
private final CallOptions callOptions;
private final Context ctx;
private final MetadataApplierListener listener;
-
+ private final ClientStreamTracer[] tracers;
private final Object lock = new Object();
// null if neither apply() or returnStream() are called.
@@ -52,13 +53,14 @@ final class MetadataApplierImpl extends MetadataApplier {
MetadataApplierImpl(
ClientTransport transport, MethodDescriptor, ?> method, Metadata origHeaders,
- CallOptions callOptions, MetadataApplierListener listener) {
+ CallOptions callOptions, MetadataApplierListener listener, ClientStreamTracer[] tracers) {
this.transport = transport;
this.method = method;
this.origHeaders = origHeaders;
this.callOptions = callOptions;
this.ctx = Context.current();
this.listener = listener;
+ this.tracers = tracers;
}
@Override
@@ -69,7 +71,7 @@ public void apply(Metadata headers) {
ClientStream realStream;
Context origCtx = ctx.attach();
try {
- realStream = transport.newStream(method, origHeaders, callOptions);
+ realStream = transport.newStream(method, origHeaders, callOptions, tracers);
} finally {
ctx.detach(origCtx);
}
@@ -80,7 +82,7 @@ public void apply(Metadata headers) {
public void fail(Status status) {
checkArgument(!status.isOk(), "Cannot fail with OK status");
checkState(!finalized, "apply() or fail() already called");
- finalizeWith(new FailingClientStream(status));
+ finalizeWith(new FailingClientStream(status, tracers));
}
private void finalizeWith(ClientStream stream) {
diff --git a/core/src/main/java/io/grpc/internal/OobChannel.java b/core/src/main/java/io/grpc/internal/OobChannel.java
index f69fd17e5c4..589824ae10e 100644
--- a/core/src/main/java/io/grpc/internal/OobChannel.java
+++ b/core/src/main/java/io/grpc/internal/OobChannel.java
@@ -26,6 +26,7 @@
import io.grpc.Attributes;
import io.grpc.CallOptions;
import io.grpc.ClientCall;
+import io.grpc.ClientStreamTracer;
import io.grpc.ConnectivityState;
import io.grpc.ConnectivityStateInfo;
import io.grpc.Context;
@@ -86,12 +87,14 @@ final class OobChannel extends ManagedChannel implements InternalInstrumented method,
CallOptions callOptions, Metadata headers, Context context) {
+ ClientStreamTracer[] tracers = GrpcUtil.getClientStreamTracers(
+ callOptions, headers, 0, /* isTransparentRetry= */ false);
Context origContext = context.attach();
// delayed transport's newStream() always acquires a lock, but concurrent performance doesn't
// matter here because OOB communication should be sparse, and it's not on application RPC's
// critical path.
try {
- return delayedTransport.newStream(method, headers, callOptions);
+ return delayedTransport.newStream(method, headers, callOptions, tracers);
} finally {
context.detach(origContext);
}
diff --git a/core/src/main/java/io/grpc/internal/RetriableStream.java b/core/src/main/java/io/grpc/internal/RetriableStream.java
index 9d752b86576..1fb8d3c43bd 100644
--- a/core/src/main/java/io/grpc/internal/RetriableStream.java
+++ b/core/src/main/java/io/grpc/internal/RetriableStream.java
@@ -30,8 +30,10 @@
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.Status;
+import io.grpc.SynchronizationContext;
import io.grpc.internal.ClientStreamListener.RpcProgress;
import java.io.InputStream;
+import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -64,6 +66,16 @@ abstract class RetriableStream implements ClientStream {
private final MethodDescriptor method;
private final Executor callExecutor;
+ private final Executor listenerSerializeExecutor = new SynchronizationContext(
+ new UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ throw Status.fromThrowable(e)
+ .withDescription("Uncaught exception in the SynchronizationContext. Re-thrown.")
+ .asRuntimeException();
+ }
+ }
+ );
private final ScheduledExecutorService scheduledExecutorService;
// Must not modify it.
private final Metadata headers;
@@ -104,6 +116,8 @@ abstract class RetriableStream implements ClientStream {
@GuardedBy("lock")
private FutureCanceller scheduledHedging;
private long nextBackoffIntervalNanos;
+ private Status cancellationStatus;
+ private boolean isClosed;
RetriableStream(
MethodDescriptor method, Metadata headers,
@@ -203,11 +217,11 @@ private void commitAndRun(Substream winningSubstream) {
}
}
- private Substream createSubstream(int previousAttemptCount) {
+ private Substream createSubstream(int previousAttemptCount, boolean isTransparentRetry) {
Substream sub = new Substream(previousAttemptCount);
// one tracer per substream
final ClientStreamTracer bufferSizeTracer = new BufferSizeTracer(sub);
- ClientStreamTracer.Factory tracerFactory = new ClientStreamTracer.Factory() {
+ ClientStreamTracer.Factory tracerFactory = new ClientStreamTracer.InternalLimitedInfoFactory() {
@Override
public ClientStreamTracer newClientStreamTracer(
ClientStreamTracer.StreamInfo info, Metadata headers) {
@@ -217,7 +231,7 @@ public ClientStreamTracer newClientStreamTracer(
Metadata newHeaders = updateHeaders(headers, previousAttemptCount);
// NOTICE: This set _must_ be done before stream.start() and it actually is.
- sub.stream = newSubstream(tracerFactory, newHeaders);
+ sub.stream = newSubstream(newHeaders, tracerFactory, previousAttemptCount, isTransparentRetry);
return sub;
}
@@ -226,7 +240,8 @@ public ClientStreamTracer newClientStreamTracer(
* Client stream is not yet started.
*/
abstract ClientStream newSubstream(
- ClientStreamTracer.Factory tracerFactory, Metadata headers);
+ Metadata headers, ClientStreamTracer.Factory tracerFactory, int previousAttempts,
+ boolean isTransparentRetry);
/** Adds grpc-previous-rpc-attempts in the headers of a retry/hedging RPC. */
@VisibleForTesting
@@ -244,19 +259,37 @@ private void drain(Substream substream) {
int index = 0;
int chunk = 0x80;
List list = null;
+ boolean streamStarted = false;
+ Runnable onReadyRunnable = null;
while (true) {
State savedState;
synchronized (lock) {
savedState = state;
- if (savedState.winningSubstream != null && savedState.winningSubstream != substream) {
- // committed but not me
- break;
+ if (streamStarted) {
+ if (savedState.winningSubstream != null && savedState.winningSubstream != substream) {
+ // committed but not me, to be cancelled
+ break;
+ }
+ if (savedState.cancelled) {
+ break;
+ }
}
if (index == savedState.buffer.size()) { // I'm drained
state = savedState.substreamDrained(substream);
- return;
+ if (!isReady()) {
+ return;
+ }
+ onReadyRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!isClosed) {
+ masterListener.onReady();
+ }
+ }
+ };
+ break;
}
if (substream.closed) {
@@ -274,22 +307,30 @@ private void drain(Substream substream) {
}
for (BufferEntry bufferEntry : list) {
- savedState = state;
- if (savedState.winningSubstream != null && savedState.winningSubstream != substream) {
- // committed but not me
- break;
+ bufferEntry.runWith(substream);
+ if (bufferEntry instanceof RetriableStream.StartEntry) {
+ streamStarted = true;
}
- if (savedState.cancelled) {
- checkState(
- savedState.winningSubstream == substream,
- "substream should be CANCELLED_BECAUSE_COMMITTED already");
- return;
+ if (streamStarted) {
+ savedState = state;
+ if (savedState.winningSubstream != null && savedState.winningSubstream != substream) {
+ // committed but not me, to be cancelled
+ break;
+ }
+ if (savedState.cancelled) {
+ break;
+ }
}
- bufferEntry.runWith(substream);
}
}
- substream.stream.cancel(CANCELLED_BECAUSE_COMMITTED);
+ if (onReadyRunnable != null) {
+ listenerSerializeExecutor.execute(onReadyRunnable);
+ return;
+ }
+
+ substream.stream.cancel(
+ state.winningSubstream == substream ? cancellationStatus : CANCELLED_BECAUSE_COMMITTED);
}
/**
@@ -299,6 +340,13 @@ private void drain(Substream substream) {
@Nullable
abstract Status prestart();
+ class StartEntry implements BufferEntry {
+ @Override
+ public void runWith(Substream substream) {
+ substream.stream.start(new Sublistener(substream));
+ }
+ }
+
/** Starts the first PRC attempt. */
@Override
public final void start(ClientStreamListener listener) {
@@ -311,18 +359,11 @@ public final void start(ClientStreamListener listener) {
return;
}
- class StartEntry implements BufferEntry {
- @Override
- public void runWith(Substream substream) {
- substream.stream.start(new Sublistener(substream));
- }
- }
-
synchronized (lock) {
state.buffer.add(new StartEntry());
}
- Substream substream = createSubstream(0);
+ Substream substream = createSubstream(0, false);
if (isHedging) {
FutureCanceller scheduledHedgingRef = null;
@@ -399,7 +440,7 @@ public void run() {
// If this run is not cancelled, the value of state.hedgingAttemptCount won't change
// until state.addActiveHedge() is called subsequently, even the state could possibly
// change.
- Substream newSubstream = createSubstream(state.hedgingAttemptCount);
+ Substream newSubstream = createSubstream(state.hedgingAttemptCount, false);
boolean cancelled = false;
FutureCanceller future = null;
@@ -439,22 +480,37 @@ public void run() {
}
@Override
- public final void cancel(Status reason) {
+ public final void cancel(final Status reason) {
Substream noopSubstream = new Substream(0 /* previousAttempts doesn't matter here */);
noopSubstream.stream = new NoopClientStream();
Runnable runnable = commit(noopSubstream);
if (runnable != null) {
- masterListener.closed(reason, RpcProgress.PROCESSED, new Metadata());
runnable.run();
+ listenerSerializeExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ isClosed = true;
+ masterListener.closed(reason, RpcProgress.PROCESSED, new Metadata());
+
+ }
+ });
return;
}
- state.winningSubstream.stream.cancel(reason);
+ Substream winningSubstreamToCancel = null;
synchronized (lock) {
- // This is not required, but causes a short-circuit in the draining process.
+ if (state.drainedSubstreams.contains(state.winningSubstream)) {
+ winningSubstreamToCancel = state.winningSubstream;
+ } else { // the winningSubstream will be cancelled while draining
+ cancellationStatus = reason;
+ }
state = state.cancelled();
}
+ if (winningSubstreamToCancel != null) {
+ winningSubstreamToCancel.stream.cancel(reason);
+ }
}
private void delayOrExecute(BufferEntry bufferEntry) {
@@ -753,18 +809,25 @@ private final class Sublistener implements ClientStreamListener {
}
@Override
- public void headersRead(Metadata headers) {
+ public void headersRead(final Metadata headers) {
commitAndRun(substream);
if (state.winningSubstream == substream) {
- masterListener.headersRead(headers);
if (throttle != null) {
throttle.onSuccess();
}
+ listenerSerializeExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ masterListener.headersRead(headers);
+ }
+ });
}
}
@Override
- public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
+ public void closed(
+ final Status status, final RpcProgress rpcProgress, final Metadata trailers) {
synchronized (lock) {
state = state.substreamClosed(substream);
closedSubstreamsInsight.append(status.getCode());
@@ -775,7 +838,14 @@ public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
if (substream.bufferLimitExceeded) {
commitAndRun(substream);
if (state.winningSubstream == substream) {
- masterListener.closed(status, rpcProgress, trailers);
+ listenerSerializeExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ isClosed = true;
+ masterListener.closed(status, rpcProgress, trailers);
+ }
+ });
}
return;
}
@@ -784,8 +854,7 @@ public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
if (rpcProgress == RpcProgress.REFUSED
&& noMoreTransparentRetry.compareAndSet(false, true)) {
// transparent retry
- final Substream newSubstream = createSubstream(
- substream.previousAttemptCount);
+ final Substream newSubstream = createSubstream(substream.previousAttemptCount, true);
if (isHedging) {
boolean commit = false;
synchronized (lock) {
@@ -853,23 +922,26 @@ public void run() {
synchronized (lock) {
scheduledRetry = scheduledRetryCopy = new FutureCanceller(lock);
}
- scheduledRetryCopy.setFuture(
- scheduledExecutorService.schedule(
+ class RetryBackoffRunnable implements Runnable {
+ @Override
+ public void run() {
+ callExecutor.execute(
new Runnable() {
@Override
public void run() {
- callExecutor.execute(
- new Runnable() {
- @Override
- public void run() {
- // retry
- Substream newSubstream =
- createSubstream(substream.previousAttemptCount + 1);
- drain(newSubstream);
- }
- });
+ // retry
+ Substream newSubstream = createSubstream(
+ substream.previousAttemptCount + 1,
+ false);
+ drain(newSubstream);
}
- },
+ });
+ }
+ }
+
+ scheduledRetryCopy.setFuture(
+ scheduledExecutorService.schedule(
+ new RetryBackoffRunnable(),
retryPlan.backoffNanos,
TimeUnit.NANOSECONDS));
return;
@@ -880,7 +952,14 @@ public void run() {
commitAndRun(substream);
if (state.winningSubstream == substream) {
- masterListener.closed(status, rpcProgress, trailers);
+ listenerSerializeExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ isClosed = true;
+ masterListener.closed(status, rpcProgress, trailers);
+ }
+ });
}
}
@@ -950,22 +1029,37 @@ private Integer getPushbackMills(Metadata trailer) {
}
@Override
- public void messagesAvailable(MessageProducer producer) {
+ public void messagesAvailable(final MessageProducer producer) {
State savedState = state;
checkState(
savedState.winningSubstream != null, "Headers should be received prior to messages.");
if (savedState.winningSubstream != substream) {
return;
}
- masterListener.messagesAvailable(producer);
+ listenerSerializeExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ masterListener.messagesAvailable(producer);
+ }
+ });
}
@Override
public void onReady() {
// FIXME(#7089): hedging case is broken.
- // TODO(zdapeng): optimization: if the substream is not drained yet, delay onReady() once
- // drained and if is still ready.
- masterListener.onReady();
+ if (!isReady()) {
+ return;
+ }
+ listenerSerializeExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (!isClosed) {
+ masterListener.onReady();
+ }
+ }
+ });
}
}
diff --git a/core/src/main/java/io/grpc/internal/ServerCallImpl.java b/core/src/main/java/io/grpc/internal/ServerCallImpl.java
index 6f123e76678..f82d87cade0 100644
--- a/core/src/main/java/io/grpc/internal/ServerCallImpl.java
+++ b/core/src/main/java/io/grpc/internal/ServerCallImpl.java
@@ -279,7 +279,11 @@ public ServerStreamListenerImpl(
new Context.CancellationListener() {
@Override
public void cancelled(Context context) {
- ServerStreamListenerImpl.this.call.cancelled = true;
+ // If the context has a cancellation cause then something exceptional happened
+ // and we should also mark the call as cancelled.
+ if (context.cancellationCause() != null) {
+ ServerStreamListenerImpl.this.call.cancelled = true;
+ }
}
},
MoreExecutors.directExecutor());
@@ -355,6 +359,8 @@ private void closedInternal(Status status) {
} finally {
// Cancel context after delivering RPC closure notification to allow the application to
// clean up and update any state based on whether onComplete or onCancel was called.
+ // Note that in failure situations JumpToApplicationThreadServerStreamListener has already
+ // closed the context. In these situations this cancel() call will be a no-op.
context.cancel(null);
}
}
diff --git a/core/src/main/java/io/grpc/internal/StatsTraceContext.java b/core/src/main/java/io/grpc/internal/StatsTraceContext.java
index adb0b63ec8a..33e84e5a0b8 100644
--- a/core/src/main/java/io/grpc/internal/StatsTraceContext.java
+++ b/core/src/main/java/io/grpc/internal/StatsTraceContext.java
@@ -20,7 +20,6 @@
import com.google.common.annotations.VisibleForTesting;
import io.grpc.Attributes;
-import io.grpc.CallOptions;
import io.grpc.ClientStreamTracer;
import io.grpc.Context;
import io.grpc.Metadata;
@@ -48,21 +47,12 @@ public final class StatsTraceContext {
* Factory method for the client-side.
*/
public static StatsTraceContext newClientContext(
- final CallOptions callOptions, final Attributes transportAttrs, Metadata headers) {
- List factories = callOptions.getStreamTracerFactories();
- if (factories.isEmpty()) {
- return NOOP;
+ ClientStreamTracer[] tracers, Attributes transportAtts, Metadata headers) {
+ StatsTraceContext ctx = new StatsTraceContext(tracers);
+ for (ClientStreamTracer tracer : tracers) {
+ tracer.streamCreated(transportAtts, headers);
}
- ClientStreamTracer.StreamInfo info =
- ClientStreamTracer.StreamInfo.newBuilder()
- .setTransportAttrs(transportAttrs).setCallOptions(callOptions).build();
- // This array will be iterated multiple times per RPC. Use primitive array instead of Collection
- // so that for-each doesn't create an Iterator every time.
- StreamTracer[] tracers = new StreamTracer[factories.size()];
- for (int i = 0; i < tracers.length; i++) {
- tracers[i] = factories.get(i).newClientStreamTracer(info, headers);
- }
- return new StatsTraceContext(tracers);
+ return ctx;
}
/**
diff --git a/core/src/main/java/io/grpc/internal/SubchannelChannel.java b/core/src/main/java/io/grpc/internal/SubchannelChannel.java
index 6c316e4f185..a1d454ed2fb 100644
--- a/core/src/main/java/io/grpc/internal/SubchannelChannel.java
+++ b/core/src/main/java/io/grpc/internal/SubchannelChannel.java
@@ -22,6 +22,7 @@
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
+import io.grpc.ClientStreamTracer;
import io.grpc.Context;
import io.grpc.InternalConfigSelector;
import io.grpc.Metadata;
@@ -57,9 +58,11 @@ public ClientStream newStream(MethodDescriptor, ?> method,
if (transport == null) {
transport = notReadyTransport;
}
+ ClientStreamTracer[] tracers = GrpcUtil.getClientStreamTracers(
+ callOptions, headers, 0, /* isTransparentRetry= */ false);
Context origContext = context.attach();
try {
- return transport.newStream(method, headers, callOptions);
+ return transport.newStream(method, headers, callOptions, tracers);
} finally {
context.detach(origContext);
}
diff --git a/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java b/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
new file mode 100644
index 00000000000..adaa1e6e69a
--- /dev/null
+++ b/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2021 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.ExperimentalApi;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedKeyManager;
+
+/**
+ * AdvancedTlsX509KeyManager is an {@code X509ExtendedKeyManager} that allows users to configure
+ * advanced TLS features, such as private key and certificate chain reloading, etc.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8024")
+public final class AdvancedTlsX509KeyManager extends X509ExtendedKeyManager {
+ private static final Logger log = Logger.getLogger(AdvancedTlsX509KeyManager.class.getName());
+
+ // The credential information sent to peers to prove our identity.
+ private volatile KeyInfo keyInfo;
+
+ /**
+ * Constructs an AdvancedTlsX509KeyManager.
+ */
+ public AdvancedTlsX509KeyManager() throws CertificateException { }
+
+ @Override
+ public PrivateKey getPrivateKey(String alias) {
+ if (alias.equals("default")) {
+ return this.keyInfo.key;
+ }
+ return null;
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain(String alias) {
+ if (alias.equals("default")) {
+ return Arrays.copyOf(this.keyInfo.certs, this.keyInfo.certs.length);
+ }
+ return null;
+ }
+
+ @Override
+ public String[] getClientAliases(String keyType, Principal[] issuers) {
+ return new String[] {"default"};
+ }
+
+ @Override
+ public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
+ return "default";
+ }
+
+ @Override
+ public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
+ return "default";
+ }
+
+ @Override
+ public String[] getServerAliases(String keyType, Principal[] issuers) {
+ return new String[] {"default"};
+ }
+
+ @Override
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+ return "default";
+ }
+
+ @Override
+ public String chooseEngineServerAlias(String keyType, Principal[] issuers,
+ SSLEngine engine) {
+ return "default";
+ }
+
+ /**
+ * Updates the current cached private key and cert chains.
+ *
+ * @param key the private key that is going to be used
+ * @param certs the certificate chain that is going to be used
+ */
+ public void updateIdentityCredentials(PrivateKey key, X509Certificate[] certs)
+ throws CertificateException {
+ // TODO(ZhenLian): explore possibilities to do a crypto check here.
+ this.keyInfo = new KeyInfo(checkNotNull(key, "key"), checkNotNull(certs, "certs"));
+ }
+
+ /**
+ * Schedules a {@code ScheduledExecutorService} to read private key and certificate chains from
+ * the local file paths periodically, and update the cached identity credentials if they are both
+ * updated.
+ *
+ * @param keyFile the file on disk holding the private key
+ * @param certFile the file on disk holding the certificate chain
+ * @param period the period between successive read-and-update executions
+ * @param unit the time unit of the initialDelay and period parameters
+ * @param executor the execute service we use to read and update the credentials
+ * @return an object that caller should close when the file refreshes are not needed
+ */
+ public Closeable updateIdentityCredentialsFromFile(File keyFile, File certFile,
+ long period, TimeUnit unit, ScheduledExecutorService executor) {
+ final ScheduledFuture> future =
+ executor.scheduleWithFixedDelay(
+ new LoadFilePathExecution(keyFile, certFile), 0, period, unit);
+ return new Closeable() {
+ @Override public void close() {
+ future.cancel(false);
+ }
+ };
+ }
+
+ private static class KeyInfo {
+ // The private key and the cert chain we will use to send to peers to prove our identity.
+ final PrivateKey key;
+ final X509Certificate[] certs;
+
+ public KeyInfo(PrivateKey key, X509Certificate[] certs) {
+ this.key = key;
+ this.certs = certs;
+ }
+ }
+
+ private class LoadFilePathExecution implements Runnable {
+ File keyFile;
+ File certFile;
+ long currentKeyTime;
+ long currentCertTime;
+
+ public LoadFilePathExecution(File keyFile, File certFile) {
+ this.keyFile = keyFile;
+ this.certFile = certFile;
+ this.currentKeyTime = 0;
+ this.currentCertTime = 0;
+ }
+
+ @Override
+ public void run() {
+ try {
+ UpdateResult newResult = readAndUpdate(this.keyFile, this.certFile, this.currentKeyTime,
+ this.currentCertTime);
+ if (newResult.success) {
+ this.currentKeyTime = newResult.keyTime;
+ this.currentCertTime = newResult.certTime;
+ }
+ } catch (CertificateException | IOException | NoSuchAlgorithmException
+ | InvalidKeySpecException e) {
+ log.log(Level.SEVERE, "Failed refreshing private key and certificate chain from files. "
+ + "Using previous ones", e);
+ }
+ }
+ }
+
+ private static class UpdateResult {
+ boolean success;
+ long keyTime;
+ long certTime;
+
+ public UpdateResult(boolean success, long keyTime, long certTime) {
+ this.success = success;
+ this.keyTime = keyTime;
+ this.certTime = certTime;
+ }
+ }
+
+ /**
+ * Reads the private key and certificates specified in the path locations. Updates {@code key} and
+ * {@code cert} if both of their modified time changed since last read.
+ *
+ * @param keyFile the file on disk holding the private key
+ * @param certFile the file on disk holding the certificate chain
+ * @param oldKeyTime the time when the private key file is modified during last execution
+ * @param oldCertTime the time when the certificate chain file is modified during last execution
+ * @return the result of this update execution
+ */
+ private UpdateResult readAndUpdate(File keyFile, File certFile, long oldKeyTime, long oldCertTime)
+ throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeySpecException {
+ long newKeyTime = keyFile.lastModified();
+ long newCertTime = certFile.lastModified();
+ // We only update when both the key and the certs are updated.
+ if (newKeyTime != oldKeyTime && newCertTime != oldCertTime) {
+ FileInputStream keyInputStream = new FileInputStream(keyFile);
+ try {
+ PrivateKey key = CertificateUtils.getPrivateKey(keyInputStream);
+ FileInputStream certInputStream = new FileInputStream(certFile);
+ try {
+ X509Certificate[] certs = CertificateUtils.getX509Certificates(certInputStream);
+ updateIdentityCredentials(key, certs);
+ return new UpdateResult(true, newKeyTime, newCertTime);
+ } finally {
+ certInputStream.close();
+ }
+ } finally {
+ keyInputStream.close();
+ }
+ }
+ return new UpdateResult(false, oldKeyTime, oldCertTime);
+ }
+
+ /**
+ * Mainly used to avoid throwing IO Exceptions in java.io.Closeable.
+ */
+ public interface Closeable extends java.io.Closeable {
+ @Override public void close();
+ }
+}
+
diff --git a/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java b/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
new file mode 100644
index 00000000000..f6e366d3219
--- /dev/null
+++ b/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2021 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.util;
+
+import io.grpc.ExperimentalApi;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedTrustManager;
+import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
+
+/**
+ * AdvancedTlsX509TrustManager is an {@code X509ExtendedTrustManager} that allows users to configure
+ * advanced TLS features, such as root certificate reloading, peer cert custom verification, etc.
+ * For Android users: this class is only supported in API level 24 and above.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8024")
+@IgnoreJRERequirement
+public final class AdvancedTlsX509TrustManager extends X509ExtendedTrustManager {
+ private static final Logger log = Logger.getLogger(AdvancedTlsX509TrustManager.class.getName());
+
+ private final Verification verification;
+ private final SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier;
+
+ // The delegated trust manager used to perform traditional certificate verification.
+ private volatile X509ExtendedTrustManager delegateManager = null;
+
+ private AdvancedTlsX509TrustManager(Verification verification,
+ SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier) throws CertificateException {
+ this.verification = verification;
+ this.socketAndEnginePeerVerifier = socketAndEnginePeerVerifier;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ throw new CertificateException(
+ "Not enough information to validate peer. SSLEngine or Socket required.");
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
+ throws CertificateException {
+ checkTrusted(chain, authType, null, socket, false);
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
+ throws CertificateException {
+ checkTrusted(chain, authType, engine, null, false);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
+ throws CertificateException {
+ checkTrusted(chain, authType, engine, null, true);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ throw new CertificateException(
+ "Not enough information to validate peer. SSLEngine or Socket required.");
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
+ throws CertificateException {
+ checkTrusted(chain, authType, null, socket, true);
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ if (this.delegateManager == null) {
+ return new X509Certificate[0];
+ }
+ return this.delegateManager.getAcceptedIssuers();
+ }
+
+ /**
+ * Uses the default trust certificates stored on user's local system.
+ * After this is used, functions that will provide new credential
+ * data(e.g. updateTrustCredentials(), updateTrustCredentialsFromFile()) should not be called.
+ */
+ public void useSystemDefaultTrustCerts() throws CertificateException, KeyStoreException,
+ NoSuchAlgorithmException {
+ // Passing a null value of KeyStore would make {@code TrustManagerFactory} attempt to use
+ // system-default trust CA certs.
+ this.delegateManager = createDelegateTrustManager(null);
+ }
+
+ /**
+ * Updates the current cached trust certificates as well as the key store.
+ *
+ * @param trustCerts the trust certificates that are going to be used
+ */
+ public void updateTrustCredentials(X509Certificate[] trustCerts) throws CertificateException,
+ KeyStoreException, NoSuchAlgorithmException, IOException {
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keyStore.load(null, null);
+ int i = 1;
+ for (X509Certificate cert: trustCerts) {
+ String alias = Integer.toString(i);
+ keyStore.setCertificateEntry(alias, cert);
+ i++;
+ }
+ X509ExtendedTrustManager newDelegateManager = createDelegateTrustManager(keyStore);
+ this.delegateManager = newDelegateManager;
+ }
+
+ private static X509ExtendedTrustManager createDelegateTrustManager(KeyStore keyStore)
+ throws CertificateException, KeyStoreException, NoSuchAlgorithmException {
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(keyStore);
+ X509ExtendedTrustManager delegateManager = null;
+ TrustManager[] tms = tmf.getTrustManagers();
+ // Iterate over the returned trust managers, looking for an instance of X509TrustManager.
+ // If found, use that as the delegate trust manager.
+ for (int j = 0; j < tms.length; j++) {
+ if (tms[j] instanceof X509ExtendedTrustManager) {
+ delegateManager = (X509ExtendedTrustManager) tms[j];
+ break;
+ }
+ }
+ if (delegateManager == null) {
+ throw new CertificateException(
+ "Failed to find X509ExtendedTrustManager with default TrustManager algorithm "
+ + TrustManagerFactory.getDefaultAlgorithm());
+ }
+ return delegateManager;
+ }
+
+ private void checkTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine,
+ Socket socket, boolean checkingServer) throws CertificateException {
+ if (chain == null || chain.length == 0) {
+ throw new IllegalArgumentException(
+ "Want certificate verification but got null or empty certificates");
+ }
+ if (sslEngine == null && socket == null) {
+ throw new CertificateException(
+ "Not enough information to validate peer. SSLEngine or Socket required.");
+ }
+ if (this.verification != Verification.INSECURELY_SKIP_ALL_VERIFICATION) {
+ X509ExtendedTrustManager currentDelegateManager = this.delegateManager;
+ if (currentDelegateManager == null) {
+ throw new CertificateException("No trust roots configured");
+ }
+ if (checkingServer) {
+ String algorithm = this.verification == Verification.CERTIFICATE_AND_HOST_NAME_VERIFICATION
+ ? "HTTPS" : "";
+ if (sslEngine != null) {
+ SSLParameters sslParams = sslEngine.getSSLParameters();
+ sslParams.setEndpointIdentificationAlgorithm(algorithm);
+ sslEngine.setSSLParameters(sslParams);
+ currentDelegateManager.checkServerTrusted(chain, authType, sslEngine);
+ } else {
+ if (!(socket instanceof SSLSocket)) {
+ throw new CertificateException("socket is not a type of SSLSocket");
+ }
+ SSLSocket sslSocket = (SSLSocket)socket;
+ SSLParameters sslParams = sslSocket.getSSLParameters();
+ sslParams.setEndpointIdentificationAlgorithm(algorithm);
+ sslSocket.setSSLParameters(sslParams);
+ currentDelegateManager.checkServerTrusted(chain, authType, sslSocket);
+ }
+ } else {
+ currentDelegateManager.checkClientTrusted(chain, authType, sslEngine);
+ }
+ }
+ // Perform the additional peer cert check.
+ if (socketAndEnginePeerVerifier != null) {
+ if (sslEngine != null) {
+ socketAndEnginePeerVerifier.verifyPeerCertificate(chain, authType, sslEngine);
+ } else {
+ socketAndEnginePeerVerifier.verifyPeerCertificate(chain, authType, socket);
+ }
+ }
+ }
+
+ /**
+ * Schedules a {@code ScheduledExecutorService} to read trust certificates from a local file path
+ * periodically, and update the cached trust certs if there is an update.
+ *
+ * @param trustCertFile the file on disk holding the trust certificates
+ * @param period the period between successive read-and-update executions
+ * @param unit the time unit of the initialDelay and period parameters
+ * @param executor the execute service we use to read and update the credentials
+ * @return an object that caller should close when the file refreshes are not needed
+ */
+ public Closeable updateTrustCredentialsFromFile(File trustCertFile, long period, TimeUnit unit,
+ ScheduledExecutorService executor) {
+ final ScheduledFuture> future =
+ executor.scheduleWithFixedDelay(
+ new LoadFilePathExecution(trustCertFile), 0, period, unit);
+ return new Closeable() {
+ @Override public void close() {
+ future.cancel(false);
+ }
+ };
+ }
+
+ private class LoadFilePathExecution implements Runnable {
+ File file;
+ long currentTime;
+
+ public LoadFilePathExecution(File file) {
+ this.file = file;
+ this.currentTime = 0;
+ }
+
+ @Override
+ public void run() {
+ try {
+ this.currentTime = readAndUpdate(this.file, this.currentTime);
+ } catch (CertificateException | IOException | KeyStoreException
+ | NoSuchAlgorithmException e) {
+ log.log(Level.SEVERE, "Failed refreshing trust CAs from file. Using previous CAs", e);
+ }
+ }
+ }
+
+ /**
+ * Reads the trust certificates specified in the path location, and update the key store if the
+ * modified time has changed since last read.
+ *
+ * @param trustCertFile the file on disk holding the trust certificates
+ * @param oldTime the time when the trust file is modified during last execution
+ * @return oldTime if failed or the modified time is not changed, otherwise the new modified time
+ */
+ private long readAndUpdate(File trustCertFile, long oldTime)
+ throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException {
+ long newTime = trustCertFile.lastModified();
+ if (newTime == oldTime) {
+ return oldTime;
+ }
+ FileInputStream inputStream = new FileInputStream(trustCertFile);
+ try {
+ X509Certificate[] certificates = CertificateUtils.getX509Certificates(inputStream);
+ updateTrustCredentials(certificates);
+ return newTime;
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ // Mainly used to avoid throwing IO Exceptions in java.io.Closeable.
+ public interface Closeable extends java.io.Closeable {
+ @Override public void close();
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ // The verification mode when authenticating the peer certificate.
+ public enum Verification {
+ // This is the DEFAULT and RECOMMENDED mode for most applications.
+ // Setting this on the client side will do the certificate and hostname verification, while
+ // setting this on the server side will only do the certificate verification.
+ CERTIFICATE_AND_HOST_NAME_VERIFICATION,
+ // This SHOULD be chosen only when you know what the implication this will bring, and have a
+ // basic understanding about TLS.
+ // It SHOULD be accompanied with proper additional peer identity checks set through
+ // {@code PeerVerifier}(nit: why this @code not working?). Failing to do so will leave
+ // applications to MITM attack.
+ // Also note that this will only take effect if the underlying SDK implementation invokes
+ // checkClientTrusted/checkServerTrusted with the {@code SSLEngine} parameter while doing
+ // verification.
+ // Setting this on either side will only do the certificate verification.
+ CERTIFICATE_ONLY_VERIFICATION,
+ // Setting is very DANGEROUS. Please try to avoid this in a real production environment, unless
+ // you are a super advanced user intended to re-implement the whole verification logic on your
+ // own. A secure verification might include:
+ // 1. proper verification on the peer certificate chain
+ // 2. proper checks on the identity of the peer certificate
+ INSECURELY_SKIP_ALL_VERIFICATION,
+ }
+
+ // Additional custom peer verification check.
+ // It will be used when checkClientTrusted/checkServerTrusted is called with the {@code Socket} or
+ // the {@code SSLEngine} parameter.
+ public interface SslSocketAndEnginePeerVerifier {
+ /**
+ * Verifies the peer certificate chain. For more information, please refer to
+ * {@code X509ExtendedTrustManager}.
+ *
+ * @param peerCertChain the certificate chain sent from the peer
+ * @param authType the key exchange algorithm used, e.g. "RSA", "DHE_DSS", etc
+ * @param socket the socket used for this connection. This parameter can be null, which
+ * indicates that implementations need not check the ssl parameters
+ */
+ void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType, Socket socket)
+ throws CertificateException;
+
+ /**
+ * Verifies the peer certificate chain. For more information, please refer to
+ * {@code X509ExtendedTrustManager}.
+ *
+ * @param peerCertChain the certificate chain sent from the peer
+ * @param authType the key exchange algorithm used, e.g. "RSA", "DHE_DSS", etc
+ * @param engine the engine used for this connection. This parameter can be null, which
+ * indicates that implementations need not check the ssl parameters
+ */
+ void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType, SSLEngine engine)
+ throws CertificateException;
+ }
+
+ public static final class Builder {
+
+ private Verification verification = Verification.CERTIFICATE_AND_HOST_NAME_VERIFICATION;
+ private SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier;
+
+ private Builder() {}
+
+ public Builder setVerification(Verification verification) {
+ this.verification = verification;
+ return this;
+ }
+
+ public Builder setSslSocketAndEnginePeerVerifier(SslSocketAndEnginePeerVerifier verifier) {
+ this.socketAndEnginePeerVerifier = verifier;
+ return this;
+ }
+
+ public AdvancedTlsX509TrustManager build() throws CertificateException {
+ return new AdvancedTlsX509TrustManager(this.verification, this.socketAndEnginePeerVerifier);
+ }
+ }
+}
+
diff --git a/core/src/main/java/io/grpc/util/CertificateUtils.java b/core/src/main/java/io/grpc/util/CertificateUtils.java
index e8bbc90cb36..980862d3836 100644
--- a/core/src/main/java/io/grpc/util/CertificateUtils.java
+++ b/core/src/main/java/io/grpc/util/CertificateUtils.java
@@ -65,36 +65,24 @@ public static X509Certificate[] getX509Certificates(InputStream inputStream)
public static PrivateKey getPrivateKey(InputStream inputStream)
throws UnsupportedEncodingException, IOException, NoSuchAlgorithmException,
InvalidKeySpecException {
- InputStreamReader isr = null;
- BufferedReader reader = null;
- try {
- isr = new InputStreamReader(inputStream, "UTF-8");
- reader = new BufferedReader(isr);
- String line;
- while ((line = reader.readLine()) != null) {
- if ("-----BEGIN PRIVATE KEY-----".equals(line)) {
- break;
- }
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if ("-----BEGIN PRIVATE KEY-----".equals(line)) {
+ break;
}
- StringBuilder keyContent = new StringBuilder();
- while ((line = reader.readLine()) != null) {
- if ("-----END PRIVATE KEY-----".equals(line)) {
- break;
- }
- keyContent.append(line);
- }
- byte[] decodedKeyBytes = BaseEncoding.base64().decode(keyContent.toString());
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKeyBytes);
- return keyFactory.generatePrivate(keySpec);
- } finally {
- if (null != reader) {
- reader.close();
- }
- if (null != isr) {
- isr.close();
+ }
+ StringBuilder keyContent = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ if ("-----END PRIVATE KEY-----".equals(line)) {
+ break;
}
+ keyContent.append(line);
}
+ byte[] decodedKeyBytes = BaseEncoding.base64().decode(keyContent.toString());
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKeyBytes);
+ return keyFactory.generatePrivate(keySpec);
}
}
diff --git a/core/src/main/java/io/grpc/util/ForwardingClientStreamTracer.java b/core/src/main/java/io/grpc/util/ForwardingClientStreamTracer.java
index de7d12e397c..7bb9d8cf71a 100644
--- a/core/src/main/java/io/grpc/util/ForwardingClientStreamTracer.java
+++ b/core/src/main/java/io/grpc/util/ForwardingClientStreamTracer.java
@@ -17,6 +17,7 @@
package io.grpc.util;
import com.google.common.base.MoreObjects;
+import io.grpc.Attributes;
import io.grpc.ClientStreamTracer;
import io.grpc.ExperimentalApi;
import io.grpc.Metadata;
@@ -27,6 +28,11 @@ public abstract class ForwardingClientStreamTracer extends ClientStreamTracer {
/** Returns the underlying {@code ClientStreamTracer}. */
protected abstract ClientStreamTracer delegate();
+ @Override
+ public void streamCreated(Attributes transportAttrs, Metadata headers) {
+ delegate().streamCreated(transportAttrs, headers);
+ }
+
@Override
public void outboundHeaders() {
delegate().outboundHeaders();
diff --git a/core/src/test/java/io/grpc/ClientStreamTracerTest.java b/core/src/test/java/io/grpc/ClientStreamTracerTest.java
index 2008a3de5c7..df450adc630 100644
--- a/core/src/test/java/io/grpc/ClientStreamTracerTest.java
+++ b/core/src/test/java/io/grpc/ClientStreamTracerTest.java
@@ -34,6 +34,7 @@ public class ClientStreamTracerTest {
Attributes.newBuilder().set(TRANSPORT_ATTR_KEY, "value").build();
@Test
+ @SuppressWarnings("deprecation") // info.getTransportAttrs()
public void streamInfo_empty() {
StreamInfo info = StreamInfo.newBuilder().build();
assertThat(info.getCallOptions()).isSameInstanceAs(CallOptions.DEFAULT);
@@ -41,6 +42,7 @@ public void streamInfo_empty() {
}
@Test
+ @SuppressWarnings("deprecation") // info.getTransportAttrs()
public void streamInfo_withInfo() {
StreamInfo info = StreamInfo.newBuilder()
.setCallOptions(callOptions).setTransportAttrs(transportAttrs).build();
@@ -49,6 +51,7 @@ public void streamInfo_withInfo() {
}
@Test
+ @SuppressWarnings("deprecation") // info.setTransportAttrs()
public void streamInfo_noEquality() {
StreamInfo info1 = StreamInfo.newBuilder()
.setCallOptions(callOptions).setTransportAttrs(transportAttrs).build();
@@ -60,6 +63,7 @@ public void streamInfo_noEquality() {
}
@Test
+ @SuppressWarnings("deprecation") // info.getTransportAttrs()
public void streamInfo_toBuilder() {
StreamInfo info1 = StreamInfo.newBuilder()
.setCallOptions(callOptions).setTransportAttrs(transportAttrs).build();
diff --git a/core/src/test/java/io/grpc/internal/AbstractTransportTest.java b/core/src/test/java/io/grpc/internal/AbstractTransportTest.java
index 091415efadc..cd522181311 100644
--- a/core/src/test/java/io/grpc/internal/AbstractTransportTest.java
+++ b/core/src/test/java/io/grpc/internal/AbstractTransportTest.java
@@ -48,7 +48,6 @@
import io.grpc.CallOptions;
import io.grpc.ChannelLogger;
import io.grpc.ClientStreamTracer;
-import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.Grpc;
import io.grpc.InternalChannelz.SocketStats;
import io.grpc.InternalChannelz.TransportStats;
@@ -172,7 +171,7 @@ public void log(ChannelLogLevel level, String messageFormat, Object... args) {}
.setRequestMarshaller(StringMarshaller.INSTANCE)
.setResponseMarshaller(StringMarshaller.INSTANCE)
.build();
- private CallOptions callOptions;
+ private final CallOptions callOptions = CallOptions.DEFAULT;
private Metadata.Key asciiKey = Metadata.Key.of(
"ascii-key", Metadata.ASCII_STRING_MARSHALLER);
@@ -186,24 +185,14 @@ public void log(ChannelLogLevel level, String messageFormat, Object... args) {}
= mock(ManagedClientTransport.Listener.class);
private MockServerListener serverListener = new MockServerListener();
private ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
- private final TestClientStreamTracer clientStreamTracer1 = new TestClientStreamTracer();
- private final TestClientStreamTracer clientStreamTracer2 = new TestClientStreamTracer();
- private final ClientStreamTracer.Factory clientStreamTracerFactory = mock(
- ClientStreamTracer.Factory.class,
- delegatesTo(new ClientStreamTracer.Factory() {
- final ArrayDeque tracers =
- new ArrayDeque<>(Arrays.asList(clientStreamTracer1, clientStreamTracer2));
-
- @Override
- public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata metadata) {
- metadata.put(tracerHeaderKey, tracerKeyValue);
- TestClientStreamTracer tracer = tracers.poll();
- if (tracer != null) {
- return tracer;
- }
- return new TestClientStreamTracer();
- }
- }));
+ private final TestClientStreamTracer clientStreamTracer1 = new TestHeaderClientStreamTracer();
+ private final TestClientStreamTracer clientStreamTracer2 = new TestHeaderClientStreamTracer();
+ private final ClientStreamTracer[] tracers = new ClientStreamTracer[] {
+ clientStreamTracer1, clientStreamTracer2
+ };
+ private final ClientStreamTracer[] noopTracers = new ClientStreamTracer[] {
+ new ClientStreamTracer() {}
+ };
private final TestServerStreamTracer serverStreamTracer1 = new TestServerStreamTracer();
private final TestServerStreamTracer serverStreamTracer2 = new TestServerStreamTracer();
@@ -230,7 +219,6 @@ public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata
@Before
public void setUp() {
server = newServer(Arrays.asList(serverStreamTracerFactory));
- callOptions = CallOptions.DEFAULT.withStreamTracerFactory(clientStreamTracerFactory);
}
@After
@@ -291,7 +279,8 @@ public void frameAfterRstStreamShouldNotBreakClientChannel() throws Exception {
// after having sent a RST_STREAM to the server. Previously, this would have broken the
// Netty channel.
- ClientStream stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream stream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, noopTracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
stream.start(clientStreamListener);
StreamCreation serverStreamCreation
@@ -314,7 +303,8 @@ public void frameAfterRstStreamShouldNotBreakClientChannel() throws Exception {
// Test that the channel is still usable i.e. we can receive headers from the server on a
// new stream.
- stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ stream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, noopTracers);
stream.start(mockClientStreamListener2);
serverStreamCreation
= serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -449,7 +439,8 @@ public void openStreamPreventsTermination() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
@@ -501,7 +492,8 @@ public void shutdownNowKillsClientStream() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
@@ -539,7 +531,8 @@ public void shutdownNowKillsServerStream() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
@@ -594,7 +587,8 @@ public void ping_duringShutdown() throws Exception {
client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
// Stream prevents termination
- ClientStream stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream stream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
stream.start(clientStreamListener);
client.shutdown(Status.UNAVAILABLE);
@@ -633,22 +627,19 @@ public void ping_afterTermination() throws Exception {
@Test
public void newStream_duringShutdown() throws Exception {
- InOrder inOrder = inOrder(clientStreamTracerFactory);
server.start(serverListener);
client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
// Stream prevents termination
- ClientStream stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
- inOrder.verify(clientStreamTracerFactory).newClientStreamTracer(
- any(ClientStreamTracer.StreamInfo.class), any(Metadata.class));
+ ClientStream stream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, noopTracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
stream.start(clientStreamListener);
client.shutdown(Status.UNAVAILABLE);
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportShutdown(any(Status.class));
- ClientStream stream2 = client.newStream(methodDescriptor, new Metadata(), callOptions);
- inOrder.verify(clientStreamTracerFactory).newClientStreamTracer(
- any(ClientStreamTracer.StreamInfo.class), any(Metadata.class));
+ ClientStream stream2 = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener2 = new ClientStreamListenerBase();
stream2.start(clientStreamListener2);
Status clientStreamStatus2 =
@@ -683,15 +674,14 @@ public void newStream_afterTermination() throws Exception {
client.shutdown(shutdownReason);
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportTerminated();
Thread.sleep(100);
- ClientStream stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream stream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
stream.start(clientStreamListener);
assertEquals(
shutdownReason, clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
verify(mockClientTransportListener, never()).transportInUse(anyBoolean());
- verify(clientStreamTracerFactory).newClientStreamTracer(
- any(ClientStreamTracer.StreamInfo.class), any(Metadata.class));
assertNull(clientStreamTracer1.getInboundTrailers());
assertSame(shutdownReason, clientStreamTracer1.getStatus());
// Assert no interactions
@@ -708,7 +698,8 @@ public void transportInUse_balancerRpcsNotCounted() throws Exception {
// CALL_OPTIONS_RPC_OWNED_BY_BALANCER in CallOptions. It won't be counted for in-use signal.
ClientStream stream1 = client.newStream(
methodDescriptor, new Metadata(),
- callOptions.withOption(GrpcUtil.CALL_OPTIONS_RPC_OWNED_BY_BALANCER, Boolean.TRUE));
+ callOptions.withOption(GrpcUtil.CALL_OPTIONS_RPC_OWNED_BY_BALANCER, Boolean.TRUE),
+ noopTracers);
ClientStreamListenerBase clientStreamListener1 = new ClientStreamListenerBase();
stream1.start(clientStreamListener1);
MockServerTransportListener serverTransportListener
@@ -717,7 +708,8 @@ methodDescriptor, new Metadata(),
= serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
// stream2 is the normal RPC, and will be counted for in-use
- ClientStream stream2 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream stream2 = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, noopTracers);
ClientStreamListenerBase clientStreamListener2 = new ClientStreamListenerBase();
stream2.start(clientStreamListener2);
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
@@ -743,7 +735,8 @@ public void transportInUse_normalClose() throws Exception {
server.start(serverListener);
client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
- ClientStream stream1 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream stream1 = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, noopTracers);
ClientStreamListenerBase clientStreamListener1 = new ClientStreamListenerBase();
stream1.start(clientStreamListener1);
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
@@ -751,7 +744,8 @@ public void transportInUse_normalClose() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
StreamCreation serverStreamCreation1
= serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- ClientStream stream2 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream stream2 = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, noopTracers);
ClientStreamListenerBase clientStreamListener2 = new ClientStreamListenerBase();
stream2.start(clientStreamListener2);
StreamCreation serverStreamCreation2
@@ -773,11 +767,13 @@ public void transportInUse_clientCancel() throws Exception {
server.start(serverListener);
client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
- ClientStream stream1 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream stream1 = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, noopTracers);
ClientStreamListenerBase clientStreamListener1 = new ClientStreamListenerBase();
stream1.start(clientStreamListener1);
verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
- ClientStream stream2 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream stream2 = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, noopTracers);
ClientStreamListenerBase clientStreamListener2 = new ClientStreamListenerBase();
stream2.start(clientStreamListener2);
@@ -792,7 +788,6 @@ public void transportInUse_clientCancel() throws Exception {
@Test
public void basicStream() throws Exception {
- InOrder clientInOrder = inOrder(clientStreamTracerFactory);
InOrder serverInOrder = inOrder(serverStreamTracerFactory);
server.start(serverListener);
client = newClientTransport(server);
@@ -816,14 +811,10 @@ public void basicStream() throws Exception {
Metadata clientHeadersCopy = new Metadata();
clientHeadersCopy.merge(clientHeaders);
- ClientStream clientStream = client.newStream(methodDescriptor, clientHeaders, callOptions);
- ArgumentCaptor streamInfoCaptor = ArgumentCaptor.forClass(null);
- clientInOrder.verify(clientStreamTracerFactory).newClientStreamTracer(
- streamInfoCaptor.capture(), same(clientHeaders));
- ClientStreamTracer.StreamInfo streamInfo = streamInfoCaptor.getValue();
- assertThat(streamInfo.getTransportAttrs()).isSameInstanceAs(
- ((ConnectionClientTransport) client).getAttributes());
- assertThat(streamInfo.getCallOptions()).isSameInstanceAs(callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, clientHeaders, callOptions, tracers);
+ assertThat(((TestHeaderClientStreamTracer) clientStreamTracer1).transportAttrs)
+ .isSameInstanceAs(((ConnectionClientTransport) client).getAttributes());
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
@@ -974,7 +965,8 @@ public void authorityPropagation() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
Metadata clientHeaders = new Metadata();
- ClientStream clientStream = client.newStream(methodDescriptor, clientHeaders, callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, clientHeaders, callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation
@@ -1005,7 +997,8 @@ public void zeroMessageStream() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation
@@ -1044,7 +1037,8 @@ public void earlyServerClose_withServerHeaders() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation
@@ -1080,7 +1074,8 @@ public void earlyServerClose_noServerHeaders() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation
@@ -1122,7 +1117,8 @@ public void earlyServerClose_serverFailure() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation
@@ -1155,7 +1151,8 @@ public void earlyServerClose_serverFailure_withClientCancelOnListenerClosed() th
serverTransport = serverTransportListener.transport;
final ClientStream clientStream =
- client.newStream(methodDescriptor, new Metadata(), callOptions);
+ client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase() {
@Override
@@ -1196,7 +1193,8 @@ public void clientCancel() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation
@@ -1230,7 +1228,8 @@ public void clientCancelFromWithinMessageRead() throws Exception {
final SettableFuture closedCalled = SettableFuture.create();
final ClientStream clientStream =
- client.newStream(methodDescriptor, new Metadata(), callOptions);
+ client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
final Status status = Status.CANCELLED.withDescription("nevermind");
clientStream.start(new ClientStreamListener() {
private boolean messageReceived = false;
@@ -1311,7 +1310,8 @@ public void serverCancel() throws Exception {
= serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation
@@ -1331,8 +1331,6 @@ public void serverCancel() throws Exception {
// Cause should not be transmitted between server and client
assertNull(clientStreamStatus.getCause());
- verify(clientStreamTracerFactory).newClientStreamTracer(
- any(ClientStreamTracer.StreamInfo.class), any(Metadata.class));
assertTrue(clientStreamTracer1.getOutboundHeaders());
assertNull(clientStreamTracer1.getInboundTrailers());
assertSame(clientStreamStatus, clientStreamTracer1.getStatus());
@@ -1353,7 +1351,8 @@ public void flowControlPushBack() throws Exception {
serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
serverTransport = serverTransportListener.transport;
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation =
@@ -1515,7 +1514,8 @@ public void interactionsAfterServerStreamCloseAreNoops() throws Exception {
// boilerplate
ClientStream clientStream =
- client.newStream(methodDescriptor, new Metadata(), callOptions);
+ client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation server
@@ -1547,7 +1547,8 @@ public void interactionsAfterClientStreamCancelAreNoops() throws Exception {
// boilerplate
ClientStream clientStream =
- client.newStream(methodDescriptor, new Metadata(), callOptions);
+ client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListener clientListener = mock(ClientStreamListener.class);
clientStream.start(clientListener);
StreamCreation server
@@ -1594,7 +1595,8 @@ public void transportTracer_streamStarted() throws Exception {
assertEquals(0, clientBefore.streamsStarted);
assertEquals(0, clientBefore.lastRemoteStreamCreatedTimeNanos);
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation = serverTransportListener
@@ -1624,7 +1626,8 @@ public void transportTracer_streamStarted() throws Exception {
TransportStats clientBefore = getTransportStats(client);
assertEquals(1, clientBefore.streamsStarted);
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, noopTracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
StreamCreation serverStreamCreation = serverTransportListener
@@ -1654,7 +1657,8 @@ public void transportTracer_server_streamEnded_ok() throws Exception {
server.start(serverListener);
client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
MockServerTransportListener serverTransportListener
@@ -1693,7 +1697,8 @@ public void transportTracer_server_streamEnded_nonOk() throws Exception {
server.start(serverListener);
client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
MockServerTransportListener serverTransportListener
@@ -1733,7 +1738,8 @@ public void transportTracer_client_streamEnded_nonOk() throws Exception {
server.start(serverListener);
client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
MockServerTransportListener serverTransportListener =
@@ -1768,7 +1774,8 @@ public void transportTracer_server_receive_msg() throws Exception {
server.start(serverListener);
client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
MockServerTransportListener serverTransportListener
@@ -1809,7 +1816,8 @@ public void transportTracer_server_send_msg() throws Exception {
server.start(serverListener);
client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
MockServerTransportListener serverTransportListener
@@ -1849,7 +1857,8 @@ public void socketStats() throws Exception {
server.start(serverListener);
ManagedClientTransport client = newClientTransport(server);
startTransport(client, mockClientTransportListener);
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
@@ -1896,8 +1905,8 @@ public void serverChecksInboundMetadataSize() throws Exception {
Metadata.Key.of("foo-bin", Metadata.BINARY_BYTE_MARSHALLER),
new byte[GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE]);
- ClientStream clientStream =
- client.newStream(methodDescriptor, tooLargeMetadata, callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, tooLargeMetadata, callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
@@ -1931,7 +1940,8 @@ public void clientChecksInboundMetadataSize_header() throws Exception {
new byte[GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE]);
ClientStream clientStream =
- client.newStream(methodDescriptor, new Metadata(), callOptions);
+ client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
@@ -1975,7 +1985,8 @@ public void clientChecksInboundMetadataSize_trailer() throws Exception {
new byte[GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE]);
ClientStream clientStream =
- client.newStream(methodDescriptor, new Metadata(), callOptions);
+ client.newStream(
+ methodDescriptor, new Metadata(), callOptions, tracers);
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
@@ -2011,7 +2022,9 @@ private void doPingPong(MockServerListener serverListener) throws Exception {
ManagedClientTransport client = newClientTransport(server);
ManagedClientTransport.Listener listener = mock(ManagedClientTransport.Listener.class);
startTransport(client, listener);
- ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+ ClientStream clientStream = client.newStream(
+ methodDescriptor, new Metadata(), callOptions,
+ new ClientStreamTracer[] { new ClientStreamTracer() {} });
ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
clientStream.start(clientStreamListener);
@@ -2092,6 +2105,16 @@ private static void startTransport(
verify(listener, timeout(TIMEOUT_MS)).transportReady();
}
+ private final class TestHeaderClientStreamTracer extends TestClientStreamTracer {
+ Attributes transportAttrs;
+
+ @Override
+ public void streamCreated(Attributes transportAttrs, Metadata metadata) {
+ this.transportAttrs = transportAttrs;
+ metadata.put(tracerHeaderKey, tracerKeyValue);
+ }
+ }
+
private static class MockServerListener implements ServerListener {
public final BlockingQueue listeners
= new LinkedBlockingQueue<>();
diff --git a/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java
index 7725c46726b..963a586319b 100644
--- a/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java
+++ b/core/src/test/java/io/grpc/internal/CallCredentials2ApplyingTest.java
@@ -34,6 +34,7 @@
import io.grpc.CallCredentials.RequestInfo;
import io.grpc.CallOptions;
import io.grpc.ChannelLogger;
+import io.grpc.ClientStreamTracer;
import io.grpc.IntegerMarshaller;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
@@ -48,6 +49,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnit;
@@ -103,6 +105,9 @@ public class CallCredentials2ApplyingTest {
private static final Metadata.Key CREDS_KEY =
Metadata.Key.of("test-creds", Metadata.ASCII_STRING_MARSHALLER);
private static final String CREDS_VALUE = "some credentials";
+ private static final ClientStreamTracer[] tracers = new ClientStreamTracer[] {
+ new ClientStreamTracer() {}
+ };
private final Metadata origHeaders = new Metadata();
private ForwardingConnectionClientTransport transport;
@@ -118,7 +123,9 @@ public void setUp() {
origHeaders.put(ORIG_HEADER_KEY, ORIG_HEADER_VALUE);
when(mockTransportFactory.newClientTransport(address, clientTransportOptions, channelLogger))
.thenReturn(mockTransport);
- when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
ClientTransportFactory transportFactory = new CallCredentialsApplyingTransportFactory(
mockTransportFactory, null, mockExecutor);
@@ -134,7 +141,7 @@ public void parameterPropagation_base() {
Attributes transportAttrs = Attributes.newBuilder().set(ATTR_KEY, ATTR_VALUE).build();
when(mockTransport.getAttributes()).thenReturn(transportAttrs);
- transport.newStream(method, origHeaders, callOptions);
+ transport.newStream(method, origHeaders, callOptions, tracers);
ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(
@@ -155,7 +162,7 @@ public void parameterPropagation_transportSetSecurityLevel() {
.build();
when(mockTransport.getAttributes()).thenReturn(transportAttrs);
- transport.newStream(method, origHeaders, callOptions);
+ transport.newStream(method, origHeaders, callOptions, tracers);
ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(
@@ -176,8 +183,10 @@ public void parameterPropagation_callOptionsSetAuthority() {
when(mockTransport.getAttributes()).thenReturn(transportAttrs);
Executor anotherExecutor = mock(Executor.class);
- transport.newStream(method, origHeaders,
- callOptions.withAuthority("calloptions-authority").withExecutor(anotherExecutor));
+ transport.newStream(
+ method, origHeaders,
+ callOptions.withAuthority("calloptions-authority").withExecutor(anotherExecutor),
+ tracers);
ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(
@@ -199,13 +208,15 @@ public void credentialThrows() {
any(io.grpc.CallCredentials2.MetadataApplier.class));
FailingClientStream stream =
- (FailingClientStream) transport.newStream(method, origHeaders, callOptions);
+ (FailingClientStream) transport.newStream(method, origHeaders, callOptions, tracers);
- verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+ verify(mockTransport, never()).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
assertEquals(Status.Code.UNAUTHENTICATED, stream.getError().getCode());
assertSame(ex, stream.getError().getCause());
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
@@ -226,14 +237,14 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
any(RequestInfo.class), same(mockExecutor),
any(io.grpc.CallCredentials2.MetadataApplier.class));
- ClientStream stream = transport.newStream(method, origHeaders, callOptions);
+ ClientStream stream = transport.newStream(method, origHeaders, callOptions, tracers);
- verify(mockTransport).newStream(method, origHeaders, callOptions);
+ verify(mockTransport).newStream(method, origHeaders, callOptions, tracers);
assertSame(mockStream, stream);
assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
@@ -254,12 +265,14 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
any(io.grpc.CallCredentials2.MetadataApplier.class));
FailingClientStream stream =
- (FailingClientStream) transport.newStream(method, origHeaders, callOptions);
+ (FailingClientStream) transport.newStream(method, origHeaders, callOptions, tracers);
- verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+ verify(mockTransport, never()).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
assertSame(error, stream.getError());
transport.shutdownNow(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdownNow(Status.UNAVAILABLE);
}
@@ -269,12 +282,15 @@ public void applyMetadata_delayed() {
when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
// Will call applyRequestMetadata(), which is no-op.
- DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions);
+ DelayedStream stream = (DelayedStream) transport.newStream(
+ method, origHeaders, callOptions, tracers);
ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(
any(RequestInfo.class), same(mockExecutor), applierCaptor.capture());
- verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+ verify(mockTransport, never()).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
transport.shutdown(Status.UNAVAILABLE);
verify(mockTransport, never()).shutdown(Status.UNAVAILABLE);
@@ -283,11 +299,11 @@ public void applyMetadata_delayed() {
headers.put(CREDS_KEY, CREDS_VALUE);
applierCaptor.getValue().apply(headers);
- verify(mockTransport).newStream(method, origHeaders, callOptions);
+ verify(mockTransport).newStream(method, origHeaders, callOptions, tracers);
assertSame(mockStream, stream.getRealStream());
assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
@@ -297,7 +313,8 @@ public void fail_delayed() {
when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
// Will call applyRequestMetadata(), which is no-op.
- DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions);
+ DelayedStream stream = (DelayedStream) transport.newStream(
+ method, origHeaders, callOptions, tracers);
ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(
@@ -306,11 +323,13 @@ public void fail_delayed() {
Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds");
applierCaptor.getValue().fail(error);
- verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+ verify(mockTransport, never()).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
FailingClientStream failingStream = (FailingClientStream) stream.getRealStream();
assertSame(error, failingStream.getError());
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
@@ -318,14 +337,14 @@ public void fail_delayed() {
@Test
public void noCreds() {
callOptions = callOptions.withCallCredentials(null);
- ClientStream stream = transport.newStream(method, origHeaders, callOptions);
+ ClientStream stream = transport.newStream(method, origHeaders, callOptions, tracers);
- verify(mockTransport).newStream(method, origHeaders, callOptions);
+ verify(mockTransport).newStream(method, origHeaders, callOptions, tracers);
assertSame(mockStream, stream);
assertNull(origHeaders.get(CREDS_KEY));
assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
diff --git a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java
index 61a221f73de..ef49e66bf2d 100644
--- a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java
+++ b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java
@@ -35,6 +35,7 @@
import io.grpc.CallCredentials.RequestInfo;
import io.grpc.CallOptions;
import io.grpc.ChannelLogger;
+import io.grpc.ClientStreamTracer;
import io.grpc.IntegerMarshaller;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
@@ -49,6 +50,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnit;
@@ -86,6 +88,9 @@ public class CallCredentialsApplyingTest {
@Mock
private ChannelLogger channelLogger;
+ private static final ClientStreamTracer[] tracers = new ClientStreamTracer[] {
+ new ClientStreamTracer() {}
+ };
private static final String AUTHORITY = "testauthority";
private static final String USER_AGENT = "testuseragent";
private static final Attributes.Key ATTR_KEY = Attributes.Key.create("somekey");
@@ -117,7 +122,9 @@ public void setUp() {
origHeaders.put(ORIG_HEADER_KEY, ORIG_HEADER_VALUE);
when(mockTransportFactory.newClientTransport(address, clientTransportOptions, channelLogger))
.thenReturn(mockTransport);
- when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
ClientTransportFactory transportFactory = new CallCredentialsApplyingTransportFactory(
mockTransportFactory, null, mockExecutor);
@@ -133,7 +140,7 @@ public void parameterPropagation_base() {
Attributes transportAttrs = Attributes.newBuilder().set(ATTR_KEY, ATTR_VALUE).build();
when(mockTransport.getAttributes()).thenReturn(transportAttrs);
- transport.newStream(method, origHeaders, callOptions);
+ transport.newStream(method, origHeaders, callOptions, tracers);
ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(infoCaptor.capture(), same(mockExecutor),
@@ -154,8 +161,10 @@ public void parameterPropagation_overrideByCallOptions() {
when(mockTransport.getAttributes()).thenReturn(transportAttrs);
Executor anotherExecutor = mock(Executor.class);
- transport.newStream(method, origHeaders,
- callOptions.withAuthority("calloptions-authority").withExecutor(anotherExecutor));
+ transport.newStream(
+ method, origHeaders,
+ callOptions.withAuthority("calloptions-authority").withExecutor(anotherExecutor),
+ tracers);
ArgumentCaptor infoCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(infoCaptor.capture(),
@@ -175,15 +184,17 @@ public void credentialThrows() {
any(RequestInfo.class), same(mockExecutor),
any(CallCredentials.MetadataApplier.class));
- FailingClientStream stream =
- (FailingClientStream) transport.newStream(method, origHeaders, callOptions);
+ FailingClientStream stream = (FailingClientStream) transport.newStream(
+ method, origHeaders, callOptions, tracers);
- verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+ verify(mockTransport, never()).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
assertEquals(Status.Code.UNAUTHENTICATED, stream.getError().getCode());
assertSame(ex, stream.getError().getCause());
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
@@ -193,14 +204,15 @@ public void applyMetadata_inline() {
when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
callOptions = callOptions.withCallCredentials(new FakeCallCredentials(CREDS_KEY, CREDS_VALUE));
- ClientStream stream = transport.newStream(method, origHeaders, callOptions);
+ ClientStream stream = transport.newStream(
+ method, origHeaders, callOptions, tracers);
- verify(mockTransport).newStream(method, origHeaders, callOptions);
+ verify(mockTransport).newStream(method, origHeaders, callOptions, tracers);
assertSame(mockStream, stream);
assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
@@ -220,13 +232,15 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
}).when(mockCreds).applyRequestMetadata(any(RequestInfo.class),
same(mockExecutor), any(CallCredentials.MetadataApplier.class));
- FailingClientStream stream =
- (FailingClientStream) transport.newStream(method, origHeaders, callOptions);
+ FailingClientStream stream = (FailingClientStream) transport.newStream(
+ method, origHeaders, callOptions, tracers);
- verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+ verify(mockTransport, never()).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
assertSame(error, stream.getError());
transport.shutdownNow(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdownNow(Status.UNAVAILABLE);
}
@@ -236,23 +250,26 @@ public void applyMetadata_delayed() {
when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
// Will call applyRequestMetadata(), which is no-op.
- DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions);
+ DelayedStream stream = (DelayedStream) transport.newStream(
+ method, origHeaders, callOptions, tracers);
ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(any(RequestInfo.class),
same(mockExecutor), applierCaptor.capture());
- verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+ verify(mockTransport, never()).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
transport.shutdown(Status.UNAVAILABLE);
verify(mockTransport, never()).shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
Metadata headers = new Metadata();
headers.put(CREDS_KEY, CREDS_VALUE);
applierCaptor.getValue().apply(headers);
- verify(mockTransport).newStream(method, origHeaders, callOptions);
+ verify(mockTransport).newStream(method, origHeaders, callOptions, tracers);
assertSame(mockStream, stream.getRealStream());
assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
@@ -261,20 +278,20 @@ public void applyMetadata_delayed() {
@Test
public void delayedShutdown_shutdownShutdownNowThenApply() {
- transport.newStream(method, origHeaders, callOptions);
+ transport.newStream(method, origHeaders, callOptions, tracers);
ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(any(RequestInfo.class),
same(mockExecutor), applierCaptor.capture());
transport.shutdown(Status.UNAVAILABLE);
transport.shutdownNow(Status.ABORTED);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport, never()).shutdown(any(Status.class));
verify(mockTransport, never()).shutdownNow(any(Status.class));
Metadata headers = new Metadata();
headers.put(CREDS_KEY, CREDS_VALUE);
applierCaptor.getValue().apply(headers);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
verify(mockTransport).shutdownNow(Status.ABORTED);
@@ -282,12 +299,12 @@ public void delayedShutdown_shutdownShutdownNowThenApply() {
@Test
public void delayedShutdown_shutdownThenApplyThenShutdownNow() {
- transport.newStream(method, origHeaders, callOptions);
+ transport.newStream(method, origHeaders, callOptions, tracers);
ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(any(RequestInfo.class),
same(mockExecutor), applierCaptor.capture());
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport, never()).shutdown(any(Status.class));
Metadata headers = new Metadata();
@@ -308,25 +325,25 @@ public void delayedShutdown_shutdownMulti() {
Metadata headers = new Metadata();
headers.put(CREDS_KEY, CREDS_VALUE);
- transport.newStream(method, origHeaders, callOptions);
- transport.newStream(method, origHeaders, callOptions);
- transport.newStream(method, origHeaders, callOptions);
+ transport.newStream(method, origHeaders, callOptions, tracers);
+ transport.newStream(method, origHeaders, callOptions, tracers);
+ transport.newStream(method, origHeaders, callOptions, tracers);
ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds, times(3)).applyRequestMetadata(any(RequestInfo.class),
same(mockExecutor), applierCaptor.capture());
applierCaptor.getAllValues().get(1).apply(headers);
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport, never()).shutdown(Status.UNAVAILABLE);
applierCaptor.getAllValues().get(0).apply(headers);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport, never()).shutdown(Status.UNAVAILABLE);
applierCaptor.getAllValues().get(2).apply(headers);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
@@ -336,7 +353,8 @@ public void fail_delayed() {
when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
// Will call applyRequestMetadata(), which is no-op.
- DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions);
+ DelayedStream stream = (DelayedStream) transport.newStream(
+ method, origHeaders, callOptions, tracers);
ArgumentCaptor applierCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(any(RequestInfo.class),
@@ -345,11 +363,13 @@ public void fail_delayed() {
Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds");
applierCaptor.getValue().fail(error);
- verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+ verify(mockTransport, never()).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
FailingClientStream failingStream = (FailingClientStream) stream.getRealStream();
assertSame(error, failingStream.getError());
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
@@ -357,14 +377,15 @@ public void fail_delayed() {
@Test
public void noCreds() {
callOptions = callOptions.withCallCredentials(null);
- ClientStream stream = transport.newStream(method, origHeaders, callOptions);
+ ClientStream stream = transport.newStream(
+ method, origHeaders, callOptions, tracers);
- verify(mockTransport).newStream(method, origHeaders, callOptions);
+ verify(mockTransport).newStream(method, origHeaders, callOptions, tracers);
assertSame(mockStream, stream);
assertNull(origHeaders.get(CREDS_KEY));
assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
transport.shutdown(Status.UNAVAILABLE);
- assertTrue(transport.newStream(method, origHeaders, callOptions)
+ assertTrue(transport.newStream(method, origHeaders, callOptions, tracers)
instanceof FailingClientStream);
verify(mockTransport).shutdown(Status.UNAVAILABLE);
}
@@ -373,7 +394,8 @@ public void noCreds() {
public void justCallOptionCreds() {
callOptions = callOptions.withCallCredentials(new FakeCallCredentials(CREDS_KEY, CREDS_VALUE));
- ClientStream stream = transport.newStream(method, origHeaders, callOptions);
+ ClientStream stream = transport.newStream(
+ method, origHeaders, callOptions, tracers);
assertSame(mockStream, stream);
assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
@@ -388,7 +410,8 @@ public void justChannelCreds() {
transportFactory.newClientTransport(address, clientTransportOptions, channelLogger);
callOptions = callOptions.withCallCredentials(null);
- ClientStream stream = transport.newStream(method, origHeaders, callOptions);
+ ClientStream stream = transport.newStream(
+ method, origHeaders, callOptions, tracers);
assertSame(mockStream, stream);
assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
@@ -406,7 +429,8 @@ public void callOptionAndChanelCreds() {
String creds2Value = "some more credentials";
callOptions = callOptions.withCallCredentials(new FakeCallCredentials(creds2Key, creds2Value));
- ClientStream stream = transport.newStream(method, origHeaders, callOptions);
+ ClientStream stream = transport.newStream(
+ method, origHeaders, callOptions, tracers);
assertSame(mockStream, stream);
assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
diff --git a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java
index 1808a4bd478..0e5e5f50599 100644
--- a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java
+++ b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
@@ -47,6 +48,7 @@
import io.grpc.CallOptions;
import io.grpc.ClientCall;
import io.grpc.ClientStreamTracer;
+import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.Codec;
import io.grpc.Context;
import io.grpc.Deadline;
@@ -143,6 +145,8 @@ public void setUp() {
any(Metadata.class),
any(Context.class)))
.thenReturn(stream);
+ when(streamTracerFactory.newClientStreamTracer(any(StreamInfo.class), any(Metadata.class)))
+ .thenReturn(new ClientStreamTracer() {});
doAnswer(new Answer() {
@Override
public Void answer(InvocationOnMock in) {
@@ -156,7 +160,7 @@ public Void answer(InvocationOnMock in) {
@After
public void tearDown() {
- verifyNoInteractions(streamTracerFactory);
+ verifyNoMoreInteractions(streamTracerFactory);
}
@Test
@@ -763,6 +767,7 @@ public void deadlineExceededBeforeCallStarted() {
channelCallTracer, configSelector)
.setDecompressorRegistry(decompressorRegistry);
call.start(callListener, new Metadata());
+ verify(streamTracerFactory).newClientStreamTracer(any(StreamInfo.class), any(Metadata.class));
verify(clientStreamProvider, never())
.newStream(
(MethodDescriptor, ?>) any(MethodDescriptor.class),
diff --git a/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java b/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java
index 9f48b8987d1..4cae565a19e 100644
--- a/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java
+++ b/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java
@@ -36,6 +36,7 @@
import static org.mockito.Mockito.when;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.IntegerMarshaller;
import io.grpc.LoadBalancer.PickResult;
import io.grpc.LoadBalancer.PickSubchannelArgs;
@@ -57,6 +58,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
@@ -89,6 +91,9 @@ public class DelayedClientTransportTest {
= CallOptions.Key.createWithDefault("shard-id", -1);
private static final Status SHUTDOWN_STATUS =
Status.UNAVAILABLE.withDescription("shutdown called");
+ private static final ClientStreamTracer[] tracers = new ClientStreamTracer[] {
+ new ClientStreamTracer() {}
+ };
private final MethodDescriptor method =
MethodDescriptor.newBuilder()
@@ -122,9 +127,13 @@ public void uncaughtException(Thread t, Throwable e) {
.thenReturn(PickResult.withSubchannel(mockSubchannel));
when(mockSubchannel.getInternalSubchannel()).thenReturn(mockInternalSubchannel);
when(mockInternalSubchannel.obtainActiveTransport()).thenReturn(mockRealTransport);
- when(mockRealTransport.newStream(same(method), same(headers), same(callOptions)))
+ when(mockRealTransport.newStream(
+ same(method), same(headers), same(callOptions),
+ ArgumentMatchers.any()))
.thenReturn(mockRealStream);
- when(mockRealTransport2.newStream(same(method2), same(headers2), same(callOptions2)))
+ when(mockRealTransport2.newStream(
+ same(method2), same(headers2), same(callOptions2),
+ ArgumentMatchers.any()))
.thenReturn(mockRealStream2);
delayedTransport.start(transportListener);
}
@@ -135,7 +144,8 @@ public void uncaughtException(Thread t, Throwable e) {
@Test public void streamStartThenAssignTransport() {
assertFalse(delayedTransport.hasPendingStreams());
- ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+ ClientStream stream = delayedTransport.newStream(
+ method, headers, callOptions, tracers);
stream.start(streamListener);
assertEquals(1, delayedTransport.getPendingStreamsCount());
assertTrue(delayedTransport.hasPendingStreams());
@@ -145,7 +155,9 @@ public void uncaughtException(Thread t, Throwable e) {
assertEquals(0, delayedTransport.getPendingStreamsCount());
assertFalse(delayedTransport.hasPendingStreams());
assertEquals(1, fakeExecutor.runDueTasks());
- verify(mockRealTransport).newStream(same(method), same(headers), same(callOptions));
+ verify(mockRealTransport).newStream(
+ same(method), same(headers), same(callOptions),
+ ArgumentMatchers.any());
verify(mockRealStream).start(listenerCaptor.capture());
verifyNoMoreInteractions(streamListener);
listenerCaptor.getValue().onReady();
@@ -154,7 +166,7 @@ public void uncaughtException(Thread t, Throwable e) {
}
@Test public void newStreamThenAssignTransportThenShutdown() {
- ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+ ClientStream stream = delayedTransport.newStream(method, headers, callOptions, tracers);
assertEquals(1, delayedTransport.getPendingStreamsCount());
assertTrue(stream instanceof DelayedStream);
delayedTransport.reprocess(mockPicker);
@@ -163,7 +175,9 @@ public void uncaughtException(Thread t, Throwable e) {
verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
verify(transportListener).transportTerminated();
assertEquals(0, fakeExecutor.runDueTasks());
- verify(mockRealTransport).newStream(same(method), same(headers), same(callOptions));
+ verify(mockRealTransport).newStream(
+ same(method), same(headers), same(callOptions),
+ ArgumentMatchers.any());
stream.start(streamListener);
verify(mockRealStream).start(same(streamListener));
}
@@ -181,11 +195,13 @@ public void uncaughtException(Thread t, Throwable e) {
delayedTransport.shutdown(SHUTDOWN_STATUS);
verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
verify(transportListener).transportTerminated();
- ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+ ClientStream stream = delayedTransport.newStream(
+ method, headers, callOptions, tracers);
assertEquals(0, delayedTransport.getPendingStreamsCount());
assertTrue(stream instanceof FailingClientStream);
verify(mockRealTransport, never()).newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
}
@Test public void assignTransportThenShutdownNowThenNewStream() {
@@ -193,15 +209,18 @@ public void uncaughtException(Thread t, Throwable e) {
delayedTransport.shutdownNow(Status.UNAVAILABLE);
verify(transportListener).transportShutdown(any(Status.class));
verify(transportListener).transportTerminated();
- ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+ ClientStream stream = delayedTransport.newStream(
+ method, headers, callOptions, tracers);
assertEquals(0, delayedTransport.getPendingStreamsCount());
assertTrue(stream instanceof FailingClientStream);
verify(mockRealTransport, never()).newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
}
@Test public void startThenCancelStreamWithoutSetTransport() {
- ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = delayedTransport.newStream(
+ method, new Metadata(), CallOptions.DEFAULT, tracers);
stream.start(streamListener);
assertEquals(1, delayedTransport.getPendingStreamsCount());
stream.cancel(Status.CANCELLED);
@@ -213,7 +232,8 @@ public void uncaughtException(Thread t, Throwable e) {
}
@Test public void newStreamThenShutdownTransportThenAssignTransport() {
- ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+ ClientStream stream = delayedTransport.newStream(
+ method, headers, callOptions, tracers);
stream.start(streamListener);
delayedTransport.shutdown(SHUTDOWN_STATUS);
@@ -225,7 +245,8 @@ public void uncaughtException(Thread t, Throwable e) {
// ... and will proceed if a real transport is available
delayedTransport.reprocess(mockPicker);
fakeExecutor.runDueTasks();
- verify(mockRealTransport).newStream(method, headers, callOptions);
+ verify(mockRealTransport).newStream(
+ method, headers, callOptions, tracers);
verify(mockRealStream).start(any(ClientStreamListener.class));
// Since no more streams are pending, delayed transport is now terminated
@@ -233,7 +254,8 @@ public void uncaughtException(Thread t, Throwable e) {
verify(transportListener).transportTerminated();
// Further newStream() will return a failing stream
- stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+ stream = delayedTransport.newStream(
+ method, new Metadata(), CallOptions.DEFAULT, tracers);
verify(streamListener, never()).closed(
any(Status.class), any(RpcProgress.class), any(Metadata.class));
stream.start(streamListener);
@@ -247,7 +269,8 @@ public void uncaughtException(Thread t, Throwable e) {
}
@Test public void newStreamThenShutdownTransportThenCancelStream() {
- ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = delayedTransport.newStream(
+ method, new Metadata(), CallOptions.DEFAULT, tracers);
delayedTransport.shutdown(SHUTDOWN_STATUS);
verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
verify(transportListener, times(0)).transportTerminated();
@@ -264,7 +287,8 @@ public void uncaughtException(Thread t, Throwable e) {
delayedTransport.shutdown(SHUTDOWN_STATUS);
verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
verify(transportListener).transportTerminated();
- ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = delayedTransport.newStream(
+ method, new Metadata(), CallOptions.DEFAULT, tracers);
stream.start(streamListener);
verify(streamListener).closed(
statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
@@ -272,7 +296,8 @@ public void uncaughtException(Thread t, Throwable e) {
}
@Test public void startStreamThenShutdownNow() {
- ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = delayedTransport.newStream(
+ method, new Metadata(), CallOptions.DEFAULT, tracers);
stream.start(streamListener);
delayedTransport.shutdownNow(Status.UNAVAILABLE);
verify(transportListener).transportShutdown(any(Status.class));
@@ -286,7 +311,8 @@ public void uncaughtException(Thread t, Throwable e) {
delayedTransport.shutdownNow(Status.UNAVAILABLE);
verify(transportListener).transportShutdown(any(Status.class));
verify(transportListener).transportTerminated();
- ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = delayedTransport.newStream(
+ method, new Metadata(), CallOptions.DEFAULT, tracers);
stream.start(streamListener);
verify(streamListener).closed(
statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
@@ -301,55 +327,59 @@ public void uncaughtException(Thread t, Throwable e) {
AbstractSubchannel subchannel1 = mock(AbstractSubchannel.class);
AbstractSubchannel subchannel2 = mock(AbstractSubchannel.class);
AbstractSubchannel subchannel3 = mock(AbstractSubchannel.class);
- when(mockRealTransport.newStream(any(MethodDescriptor.class), any(Metadata.class),
- any(CallOptions.class))).thenReturn(mockRealStream);
- when(mockRealTransport2.newStream(any(MethodDescriptor.class), any(Metadata.class),
- any(CallOptions.class))).thenReturn(mockRealStream2);
+ when(mockRealTransport.newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
+ .thenReturn(mockRealStream);
+ when(mockRealTransport2.newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
+ .thenReturn(mockRealStream2);
when(subchannel1.getInternalSubchannel()).thenReturn(newTransportProvider(mockRealTransport));
when(subchannel2.getInternalSubchannel()).thenReturn(newTransportProvider(mockRealTransport2));
when(subchannel3.getInternalSubchannel()).thenReturn(newTransportProvider(null));
// Fail-fast streams
DelayedStream ff1 = (DelayedStream) delayedTransport.newStream(
- method, headers, failFastCallOptions);
+ method, headers, failFastCallOptions, tracers);
ff1.start(mock(ClientStreamListener.class));
ff1.halfClose();
PickSubchannelArgsImpl ff1args = new PickSubchannelArgsImpl(method, headers,
failFastCallOptions);
verify(transportListener).transportInUse(true);
DelayedStream ff2 = (DelayedStream) delayedTransport.newStream(
- method2, headers2, failFastCallOptions);
+ method2, headers2, failFastCallOptions, tracers);
PickSubchannelArgsImpl ff2args = new PickSubchannelArgsImpl(method2, headers2,
failFastCallOptions);
DelayedStream ff3 = (DelayedStream) delayedTransport.newStream(
- method, headers, failFastCallOptions);
+ method, headers, failFastCallOptions, tracers);
PickSubchannelArgsImpl ff3args = new PickSubchannelArgsImpl(method, headers,
failFastCallOptions);
DelayedStream ff4 = (DelayedStream) delayedTransport.newStream(
- method2, headers2, failFastCallOptions);
+ method2, headers2, failFastCallOptions, tracers);
PickSubchannelArgsImpl ff4args = new PickSubchannelArgsImpl(method2, headers2,
failFastCallOptions);
// Wait-for-ready streams
FakeClock wfr3Executor = new FakeClock();
DelayedStream wfr1 = (DelayedStream) delayedTransport.newStream(
- method, headers, waitForReadyCallOptions);
+ method, headers, waitForReadyCallOptions, tracers);
PickSubchannelArgsImpl wfr1args = new PickSubchannelArgsImpl(method, headers,
waitForReadyCallOptions);
DelayedStream wfr2 = (DelayedStream) delayedTransport.newStream(
- method2, headers2, waitForReadyCallOptions);
+ method2, headers2, waitForReadyCallOptions, tracers);
PickSubchannelArgsImpl wfr2args = new PickSubchannelArgsImpl(method2, headers2,
waitForReadyCallOptions);
CallOptions wfr3callOptions = waitForReadyCallOptions.withExecutor(
wfr3Executor.getScheduledExecutorService());
DelayedStream wfr3 = (DelayedStream) delayedTransport.newStream(
- method, headers, wfr3callOptions);
+ method, headers, wfr3callOptions, tracers);
wfr3.start(mock(ClientStreamListener.class));
wfr3.halfClose();
PickSubchannelArgsImpl wfr3args = new PickSubchannelArgsImpl(method, headers,
wfr3callOptions);
DelayedStream wfr4 = (DelayedStream) delayedTransport.newStream(
- method2, headers2, waitForReadyCallOptions);
+ method2, headers2, waitForReadyCallOptions, tracers);
PickSubchannelArgsImpl wfr4args = new PickSubchannelArgsImpl(method2, headers2,
waitForReadyCallOptions);
@@ -386,8 +416,10 @@ public void uncaughtException(Thread t, Throwable e) {
// streams are now owned by a real transport (which should prevent the Channel from
// terminating).
// ff1 and wfr1 went through
- verify(mockRealTransport).newStream(method, headers, failFastCallOptions);
- verify(mockRealTransport2).newStream(method, headers, waitForReadyCallOptions);
+ verify(mockRealTransport).newStream(
+ method, headers, failFastCallOptions, tracers);
+ verify(mockRealTransport2).newStream(
+ method, headers, waitForReadyCallOptions, tracers);
assertSame(mockRealStream, ff1.getRealStream());
assertSame(mockRealStream2, wfr1.getRealStream());
verify(mockRealStream).start(any(ClientStreamListener.class));
@@ -443,7 +475,7 @@ public void uncaughtException(Thread t, Throwable e) {
// New streams will use the last picker
DelayedStream wfr5 = (DelayedStream) delayedTransport.newStream(
- method, headers, waitForReadyCallOptions);
+ method, headers, waitForReadyCallOptions, tracers);
assertNull(wfr5.getRealStream());
inOrder.verify(picker).pickSubchannel(
new PickSubchannelArgsImpl(method, headers, waitForReadyCallOptions));
@@ -474,14 +506,17 @@ public void reprocess_NoPendingStream() {
when(subchannel.getInternalSubchannel()).thenReturn(mockInternalSubchannel);
when(picker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
PickResult.withSubchannel(subchannel));
- when(mockRealTransport.newStream(any(MethodDescriptor.class), any(Metadata.class),
- any(CallOptions.class))).thenReturn(mockRealStream);
+ when(mockRealTransport.newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
+ .thenReturn(mockRealStream);
delayedTransport.reprocess(picker);
verifyNoMoreInteractions(picker);
verifyNoMoreInteractions(transportListener);
// Though picker was not originally used, it will be saved and serve future streams.
- ClientStream stream = delayedTransport.newStream(method, headers, CallOptions.DEFAULT);
+ ClientStream stream = delayedTransport.newStream(
+ method, headers, CallOptions.DEFAULT, tracers);
verify(picker).pickSubchannel(new PickSubchannelArgsImpl(method, headers, CallOptions.DEFAULT));
verify(mockInternalSubchannel).obtainActiveTransport();
assertSame(mockRealStream, stream);
@@ -519,7 +554,7 @@ public PickResult answer(InvocationOnMock invocation) throws Throwable {
@Override
public void run() {
// Will call pickSubchannel and wait on barrier
- delayedTransport.newStream(method, headers, callOptions);
+ delayedTransport.newStream(method, headers, callOptions, tracers);
}
};
sideThread.start();
@@ -552,7 +587,7 @@ public void run() {
@Override
public void run() {
// Will call pickSubchannel and wait on barrier
- delayedTransport.newStream(method, headers2, callOptions);
+ delayedTransport.newStream(method, headers2, callOptions, tracers);
}
};
sideThread2.start();
@@ -600,7 +635,8 @@ public void newStream_racesWithReprocessIdleMode() throws Exception {
// Because there is no pending stream yet, it will do nothing but save the picker.
delayedTransport.reprocess(picker);
- ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+ ClientStream stream = delayedTransport.newStream(
+ method, headers, callOptions, tracers);
stream.start(streamListener);
assertTrue(delayedTransport.hasPendingStreams());
verify(transportListener).transportInUse(true);
@@ -609,7 +645,7 @@ public void newStream_racesWithReprocessIdleMode() throws Exception {
@Test
public void pendingStream_appendTimeoutInsight_waitForReady() {
ClientStream stream = delayedTransport.newStream(
- method, headers, callOptions.withWaitForReady());
+ method, headers, callOptions.withWaitForReady(), tracers);
stream.start(streamListener);
InsightBuilder insight = new InsightBuilder();
stream.appendTimeoutInsight(insight);
diff --git a/core/src/test/java/io/grpc/internal/FailingClientStreamTest.java b/core/src/test/java/io/grpc/internal/FailingClientStreamTest.java
index dad82902395..c07812577d5 100644
--- a/core/src/test/java/io/grpc/internal/FailingClientStreamTest.java
+++ b/core/src/test/java/io/grpc/internal/FailingClientStreamTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.internal.ClientStreamListener.RpcProgress;
@@ -33,13 +34,16 @@
*/
@RunWith(JUnit4.class)
public class FailingClientStreamTest {
+ private static final ClientStreamTracer[] tracers = new ClientStreamTracer[] {
+ new ClientStreamTracer() {}
+ };
@Test
public void processedRpcProgressPopulatedToListener() {
ClientStreamListener listener = mock(ClientStreamListener.class);
Status status = Status.UNAVAILABLE;
- ClientStream stream = new FailingClientStream(status);
+ ClientStream stream = new FailingClientStream(status, RpcProgress.PROCESSED, tracers);
stream.start(listener);
verify(listener).closed(eq(status), eq(RpcProgress.PROCESSED), any(Metadata.class));
}
@@ -49,7 +53,7 @@ public void droppedRpcProgressPopulatedToListener() {
ClientStreamListener listener = mock(ClientStreamListener.class);
Status status = Status.UNAVAILABLE;
- ClientStream stream = new FailingClientStream(status, RpcProgress.DROPPED);
+ ClientStream stream = new FailingClientStream(status, RpcProgress.DROPPED, tracers);
stream.start(listener);
verify(listener).closed(eq(status), eq(RpcProgress.DROPPED), any(Metadata.class));
}
diff --git a/core/src/test/java/io/grpc/internal/FailingClientTransportTest.java b/core/src/test/java/io/grpc/internal/FailingClientTransportTest.java
index ff15ef7ff02..98749d74910 100644
--- a/core/src/test/java/io/grpc/internal/FailingClientTransportTest.java
+++ b/core/src/test/java/io/grpc/internal/FailingClientTransportTest.java
@@ -22,6 +22,7 @@
import static org.mockito.Mockito.verify;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.internal.ClientStreamListener.RpcProgress;
@@ -41,8 +42,9 @@ public void newStreamStart() {
Status error = Status.UNAVAILABLE;
RpcProgress rpcProgress = RpcProgress.DROPPED;
FailingClientTransport transport = new FailingClientTransport(error, rpcProgress);
- ClientStream stream = transport
- .newStream(TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = transport.newStream(
+ TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT,
+ new ClientStreamTracer[] { new ClientStreamTracer() {} });
ClientStreamListener listener = mock(ClientStreamListener.class);
stream.start(listener);
diff --git a/core/src/test/java/io/grpc/internal/ForwardingClientStreamTracerTest.java b/core/src/test/java/io/grpc/internal/ForwardingClientStreamTracerTest.java
new file mode 100644
index 00000000000..5eb5b49fa19
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ForwardingClientStreamTracerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.internal;
+
+import static org.mockito.Mockito.mock;
+
+import io.grpc.ClientStreamTracer;
+import io.grpc.ForwardingTestUtil;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ForwardingClientStreamTracer}. */
+@RunWith(JUnit4.class)
+public class ForwardingClientStreamTracerTest {
+ private final ClientStreamTracer mockDelegate = mock(ClientStreamTracer.class);
+
+ @Test
+ public void allMethodsForwarded() throws Exception {
+ ForwardingTestUtil.testMethodsForwarded(
+ ClientStreamTracer.class,
+ mockDelegate,
+ new ForwardingClientStreamTracerTest.TestClientStreamTracer(),
+ Collections.emptyList());
+ }
+
+ private final class TestClientStreamTracer extends ForwardingClientStreamTracer {
+ @Override
+ protected ClientStreamTracer delegate() {
+ return mockDelegate;
+ }
+ }
+}
diff --git a/core/src/test/java/io/grpc/internal/GrpcUtilTest.java b/core/src/test/java/io/grpc/internal/GrpcUtilTest.java
index 7a3808de6e3..6d2c21ddab8 100644
--- a/core/src/test/java/io/grpc/internal/GrpcUtilTest.java
+++ b/core/src/test/java/io/grpc/internal/GrpcUtilTest.java
@@ -16,6 +16,7 @@
package io.grpc.internal;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -27,13 +28,18 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import io.grpc.Attributes;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
+import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.LoadBalancer.PickResult;
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.internal.ClientStreamListener.RpcProgress;
import io.grpc.internal.GrpcUtil.Http2Error;
import io.grpc.testing.TestMethodDescriptors;
+import java.util.ArrayDeque;
+import java.util.concurrent.atomic.AtomicReference;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -44,6 +50,10 @@
@RunWith(JUnit4.class)
public class GrpcUtilTest {
+ private static final ClientStreamTracer[] tracers = new ClientStreamTracer[] {
+ new ClientStreamTracer() {}
+ };
+
@SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
@Rule public final ExpectedException thrown = ExpectedException.none();
@@ -244,8 +254,9 @@ public void getTransportFromPickResult_errorPickResult_failFast() {
assertNotNull(transport);
- ClientStream stream = transport
- .newStream(TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = transport.newStream(
+ TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT,
+ tracers);
ClientStreamListener listener = mock(ClientStreamListener.class);
stream.start(listener);
@@ -260,8 +271,9 @@ public void getTransportFromPickResult_dropPickResult_waitForReady() {
assertNotNull(transport);
- ClientStream stream = transport
- .newStream(TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = transport.newStream(
+ TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT,
+ tracers);
ClientStreamListener listener = mock(ClientStreamListener.class);
stream.start(listener);
@@ -276,11 +288,45 @@ public void getTransportFromPickResult_dropPickResult_failFast() {
assertNotNull(transport);
- ClientStream stream = transport
- .newStream(TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT);
+ ClientStream stream = transport.newStream(
+ TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT,
+ tracers);
ClientStreamListener listener = mock(ClientStreamListener.class);
stream.start(listener);
verify(listener).closed(eq(status), eq(RpcProgress.DROPPED), any(Metadata.class));
}
+
+ @Test
+ public void clientStreamTracerFactoryBackwardCompatibility() {
+ final AtomicReference transportAttrsRef = new AtomicReference<>();
+ final ClientStreamTracer mockTracer = mock(ClientStreamTracer.class);
+ final Metadata.Key key = Metadata.Key.of("fake-key", Metadata.ASCII_STRING_MARSHALLER);
+ final ArrayDeque tracers = new ArrayDeque<>();
+ ClientStreamTracer.Factory oldFactoryImpl = new ClientStreamTracer.Factory() {
+ @SuppressWarnings("deprecation")
+ @Override
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
+ transportAttrsRef.set(info.getTransportAttrs());
+ headers.put(key, "fake-value");
+ tracers.offer(mockTracer);
+ return mockTracer;
+ }
+ };
+
+ StreamInfo info =
+ StreamInfo.newBuilder().setCallOptions(CallOptions.DEFAULT.withWaitForReady()).build();
+ Metadata metadata = new Metadata();
+ Attributes transAttrs =
+ Attributes.newBuilder().set(Attributes.Key.create("foo"), "bar").build();
+ ClientStreamTracer tracer = GrpcUtil.newClientStreamTracer(oldFactoryImpl, info, metadata);
+ tracer.streamCreated(transAttrs, metadata);
+ assertThat(tracers.poll()).isSameInstanceAs(mockTracer);
+ assertThat(transportAttrsRef.get()).isEqualTo(transAttrs);
+ assertThat(metadata.get(key)).isEqualTo("fake-value");
+
+ tracer.streamClosed(Status.UNAVAILABLE);
+ // verify that newClientStreamTracer() is called no more than once
+ assertThat(tracers).isEmpty();
+ }
}
diff --git a/core/src/test/java/io/grpc/internal/InUseStateAggregatorTest.java b/core/src/test/java/io/grpc/internal/InUseStateAggregatorTest.java
new file mode 100644
index 00000000000..e1bbc063ea3
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/InUseStateAggregatorTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link InUseStateAggregator}.
+ */
+@RunWith(JUnit4.class)
+public class InUseStateAggregatorTest {
+
+ private InUseStateAggregator aggregator;
+
+ @Before
+ public void setUp() {
+ aggregator = new InUseStateAggregator() {
+ @Override
+ protected void handleInUse() {
+ }
+
+ @Override
+ protected void handleNotInUse() {
+ }
+ };
+ }
+
+ @Test
+ public void anyObjectInUse() {
+ String objectOne = "1";
+ String objectTwo = "2";
+ String objectThree = "3";
+
+ aggregator.updateObjectInUse(objectOne, true);
+ assertTrue(aggregator.anyObjectInUse(objectOne));
+
+ aggregator.updateObjectInUse(objectTwo, true);
+ aggregator.updateObjectInUse(objectThree, true);
+ assertTrue(aggregator.anyObjectInUse(objectOne, objectTwo, objectThree));
+
+ aggregator.updateObjectInUse(objectTwo, false);
+ assertTrue(aggregator.anyObjectInUse(objectOne, objectThree));
+ }
+}
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java
index 6a76f75c8b7..30e137cba22 100644
--- a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java
@@ -26,7 +26,9 @@
import static org.mockito.AdditionalAnswers.delegatesTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.atMostOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -284,6 +286,35 @@ public void delayedTransportHoldsOffIdleness() throws Exception {
verify(mockLoadBalancer).shutdown();
}
+ @Test
+ public void pendingCallExitsIdleAfterEnter() throws Exception {
+ // Create a pending call without starting it.
+ channel.newCall(method, CallOptions.DEFAULT);
+
+ channel.enterIdle();
+
+ // Just the existence of a non-started, pending call means the channel cannot stay
+ // in idle mode because the expectation is that the pending call will also need to
+ // be handled.
+ verify(mockNameResolver, times(2)).start(any(NameResolver.Listener2.class));
+ }
+
+ @Test
+ public void delayedTransportExitsIdleAfterEnter() throws Exception {
+ // Start a new call that will go to the delayed transport
+ ClientCall call = channel.newCall(method, CallOptions.DEFAULT);
+ call.start(mockCallListener, new Metadata());
+ deliverResolutionResult();
+
+ channel.enterIdle();
+
+ // Since we have a call in delayed transport, the call to enterIdle() should have resulted in
+ // the channel going to idle mode and then immediately exiting. We confirm this by verifying
+ // that the name resolver was started up twice - once when the call was first created and a
+ // second time after exiting idle mode.
+ verify(mockNameResolver, times(2)).start(any(NameResolver.Listener2.class));
+ }
+
@Test
public void realTransportsHoldsOffIdleness() throws Exception {
final EquivalentAddressGroup addressGroup = servers.get(1);
@@ -332,6 +363,50 @@ public void realTransportsHoldsOffIdleness() throws Exception {
verify(mockLoadBalancer).shutdown();
}
+ @Test
+ public void enterIdleWhileRealTransportInProgress() {
+ final EquivalentAddressGroup addressGroup = servers.get(1);
+
+ // Start a call, which goes to delayed transport
+ ClientCall call = channel.newCall(method, CallOptions.DEFAULT);
+ call.start(mockCallListener, new Metadata());
+
+ // Verify that we have exited the idle mode
+ ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null);
+ verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture());
+ deliverResolutionResult();
+ Helper helper = helperCaptor.getValue();
+
+ // Create a subchannel for the real transport to happen on.
+ Subchannel subchannel = createSubchannelSafely(helper, addressGroup, Attributes.EMPTY);
+ requestConnectionSafely(helper, subchannel);
+ MockClientTransportInfo t0 = newTransports.poll();
+ t0.listener.transportReady();
+
+ SubchannelPicker mockPicker = mock(SubchannelPicker.class);
+ when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+ .thenReturn(PickResult.withSubchannel(subchannel));
+ updateBalancingStateSafely(helper, READY, mockPicker);
+
+ // Delayed transport creates real streams in the app executor
+ executor.runDueTasks();
+
+ // Move transport to the in-use state
+ t0.listener.transportInUse(true);
+
+ // Now we enter Idle mode while real transport is happening
+ channel.enterIdle();
+
+ // Verify that the name resolver and the load balance were shut down.
+ verify(mockNameResolver).shutdown();
+ verify(mockLoadBalancer).shutdown();
+
+ // When there are no pending streams, the call to enterIdle() should stick and
+ // we remain in idle mode. We verify this by making sure that the name resolver
+ // was not started up more than once (the initial startup).
+ verify(mockNameResolver, atMostOnce()).start(isA(NameResolver.Listener2.class));
+ }
+
@Test
public void updateSubchannelAddresses_newAddressConnects() {
ClientCall call = channel.newCall(method, CallOptions.DEFAULT);
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
index e5e017d756a..668411d7ecc 100644
--- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
@@ -71,6 +71,7 @@
import io.grpc.ClientInterceptor;
import io.grpc.ClientInterceptors;
import io.grpc.ClientStreamTracer;
+import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.CompositeChannelCredentials;
import io.grpc.ConnectivityState;
import io.grpc.ConnectivityStateInfo;
@@ -151,6 +152,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
@@ -225,6 +227,8 @@ public boolean shouldAccept(Runnable command) {
private ArgumentCaptor statusCaptor;
@Captor
private ArgumentCaptor callOptionsCaptor;
+ @Captor
+ private ArgumentCaptor tracersCaptor;
@Mock
private LoadBalancer mockLoadBalancer;
@Mock
@@ -351,6 +355,7 @@ public void close() throws SecurityException {
channelBuilder = new ManagedChannelImplBuilder(TARGET,
new UnsupportedClientTransportFactoryBuilder(), new FixedPortProvider(DEFAULT_PORT));
+ channelBuilder.disableRetry();
configureBuilder(channelBuilder);
}
@@ -525,7 +530,9 @@ channelBuilder, mockTransportFactory, new FakeBackoffPolicyProvider(),
MockClientTransportInfo transportInfo = transports.poll();
ConnectionClientTransport mockTransport = transportInfo.transport;
ManagedClientTransport.Listener transportListener = transportInfo.listener;
- when(mockTransport.newStream(same(method), same(headers), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), same(headers), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
transportListener.transportReady();
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
@@ -534,7 +541,9 @@ channelBuilder, mockTransportFactory, new FakeBackoffPolicyProvider(),
executor.runDueTasks();
ArgumentCaptor callOptionsCaptor = ArgumentCaptor.forClass(null);
- verify(mockTransport).newStream(same(method), same(headers), callOptionsCaptor.capture());
+ verify(mockTransport).newStream(
+ same(method), same(headers), callOptionsCaptor.capture(),
+ ArgumentMatchers.any());
assertThat(callOptionsCaptor.getValue().isWaitForReady()).isTrue();
verify(mockStream).start(streamListenerCaptor.capture());
@@ -600,7 +609,9 @@ public ClientCall interceptCall(
MockClientTransportInfo transportInfo = transports.poll();
ConnectionClientTransport mockTransport = transportInfo.transport;
ManagedClientTransport.Listener transportListener = transportInfo.listener;
- when(mockTransport.newStream(same(method), same(headers), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), same(headers), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
transportListener.transportReady();
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
@@ -609,7 +620,9 @@ public ClientCall interceptCall(
executor.runDueTasks();
ArgumentCaptor callOptionsCaptor = ArgumentCaptor.forClass(null);
- verify(mockTransport).newStream(same(method), same(headers), callOptionsCaptor.capture());
+ verify(mockTransport).newStream(
+ same(method), same(headers), callOptionsCaptor.capture(),
+ ArgumentMatchers.any());
assertThat(callOptionsCaptor.getValue().getOption(callOptionsKey)).isEqualTo("fooValue");
verify(mockStream).start(streamListenerCaptor.capture());
@@ -800,9 +813,13 @@ private void subtestCallsAndShutdown(boolean shutdownNow, boolean shutdownNowAft
ConnectionClientTransport mockTransport = transportInfo.transport;
verify(mockTransport).start(any(ManagedClientTransport.Listener.class));
ManagedClientTransport.Listener transportListener = transportInfo.listener;
- when(mockTransport.newStream(same(method), same(headers), same(CallOptions.DEFAULT)))
+ when(mockTransport.newStream(
+ same(method), same(headers), same(CallOptions.DEFAULT),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
- when(mockTransport.newStream(same(method), same(headers2), same(CallOptions.DEFAULT)))
+ when(mockTransport.newStream(
+ same(method), same(headers2), same(CallOptions.DEFAULT),
+ ArgumentMatchers.any()))
.thenReturn(mockStream2);
transportListener.transportReady();
when(mockPicker.pickSubchannel(
@@ -820,14 +837,19 @@ private void subtestCallsAndShutdown(boolean shutdownNow, boolean shutdownNowAft
any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class));
call.start(mockCallListener, headers);
- verify(mockTransport, never())
- .newStream(same(method), same(headers), same(CallOptions.DEFAULT));
+ verify(mockTransport, never()).newStream(
+ same(method), same(headers), same(CallOptions.DEFAULT),
+ ArgumentMatchers.any());
// Second RPC, will be assigned to the real transport
ClientCall call2 = channel.newCall(method, CallOptions.DEFAULT);
call2.start(mockCallListener2, headers2);
- verify(mockTransport).newStream(same(method), same(headers2), same(CallOptions.DEFAULT));
- verify(mockTransport).newStream(same(method), same(headers2), same(CallOptions.DEFAULT));
+ verify(mockTransport).newStream(
+ same(method), same(headers2), same(CallOptions.DEFAULT),
+ ArgumentMatchers.any());
+ verify(mockTransport).newStream(
+ same(method), same(headers2), same(CallOptions.DEFAULT),
+ ArgumentMatchers.any());
verify(mockStream2).start(any(ClientStreamListener.class));
// Shutdown
@@ -872,7 +894,9 @@ private void subtestCallsAndShutdown(boolean shutdownNow, boolean shutdownNowAft
.thenReturn(PickResult.withSubchannel(subchannel));
updateBalancingStateSafely(helper, READY, picker2);
executor.runDueTasks();
- verify(mockTransport).newStream(same(method), same(headers), same(CallOptions.DEFAULT));
+ verify(mockTransport).newStream(
+ same(method), same(headers), same(CallOptions.DEFAULT),
+ ArgumentMatchers.any());
verify(mockStream).start(any(ClientStreamListener.class));
}
@@ -1021,7 +1045,9 @@ public void callOptionsExecutor() {
MockClientTransportInfo transportInfo = transports.poll();
ConnectionClientTransport mockTransport = transportInfo.transport;
ManagedClientTransport.Listener transportListener = transportInfo.listener;
- when(mockTransport.newStream(same(method), same(headers), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), same(headers), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
transportListener.transportReady();
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
@@ -1031,7 +1057,8 @@ public void callOptionsExecutor() {
// Real streams are started in the call executor if they were previously buffered.
assertEquals(1, callExecutor.runDueTasks());
- verify(mockTransport).newStream(same(method), same(headers), same(options));
+ verify(mockTransport).newStream(
+ same(method), same(headers), same(options), ArgumentMatchers.any());
verify(mockStream).start(streamListenerCaptor.capture());
// Call listener callbacks are also run in the call executor
@@ -1298,7 +1325,8 @@ public void firstResolvedServerFailedToConnect() throws Exception {
same(goodAddress), any(ClientTransportOptions.class), any(ChannelLogger.class));
MockClientTransportInfo goodTransportInfo = transports.poll();
when(goodTransportInfo.transport.newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mock(ClientStream.class));
goodTransportInfo.listener.transportReady();
@@ -1310,11 +1338,13 @@ public void firstResolvedServerFailedToConnect() throws Exception {
// Delayed transport uses the app executor to create real streams.
executor.runDueTasks();
- verify(goodTransportInfo.transport).newStream(same(method), same(headers),
- same(CallOptions.DEFAULT));
+ verify(goodTransportInfo.transport).newStream(
+ same(method), same(headers), same(CallOptions.DEFAULT),
+ ArgumentMatchers.any());
// The bad transport was never used.
- verify(badTransportInfo.transport, times(0)).newStream(any(MethodDescriptor.class),
- any(Metadata.class), any(CallOptions.class));
+ verify(badTransportInfo.transport, times(0)).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
}
@Test
@@ -1464,10 +1494,12 @@ public void allServersFailedToConnect() throws Exception {
// ... while the wait-for-ready call stays
verifyNoMoreInteractions(mockCallListener);
// No real stream was ever created
- verify(transportInfo1.transport, times(0))
- .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
- verify(transportInfo2.transport, times(0))
- .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+ verify(transportInfo1.transport, times(0)).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
+ verify(transportInfo2.transport, times(0)).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
}
@Test
@@ -1763,8 +1795,9 @@ public void oobchannels() {
assertEquals(0, balancerRpcExecutor.numPendingTasks());
transportInfo.listener.transportReady();
assertEquals(1, balancerRpcExecutor.runDueTasks());
- verify(transportInfo.transport).newStream(same(method), same(headers),
- same(CallOptions.DEFAULT));
+ verify(transportInfo.transport).newStream(
+ same(method), same(headers), same(CallOptions.DEFAULT),
+ ArgumentMatchers.any());
// The transport goes away
transportInfo.listener.transportShutdown(Status.UNAVAILABLE);
@@ -1849,6 +1882,7 @@ public void oobChannelHasNoChannelCallCredentials() {
TARGET, InsecureChannelCredentials.create(),
new FakeCallCredentials(metadataKey, channelCredValue),
new UnsupportedClientTransportFactoryBuilder(), new FixedPortProvider(DEFAULT_PORT));
+ channelBuilder.disableRetry();
configureBuilder(channelBuilder);
createChannel();
@@ -1870,7 +1904,9 @@ public void oobChannelHasNoChannelCallCredentials() {
ClientCall call = channel.newCall(method, callOptions);
call.start(mockCallListener, headers);
- verify(transportInfo.transport).newStream(same(method), same(headers), same(callOptions));
+ verify(transportInfo.transport).newStream(
+ same(method), same(headers), same(callOptions),
+ ArgumentMatchers.any());
assertThat(headers.getAll(metadataKey))
.containsExactly(channelCredValue, callCredValue).inOrder();
@@ -1887,7 +1923,9 @@ public void oobChannelHasNoChannelCallCredentials() {
transportInfo.listener.transportReady();
balancerRpcExecutor.runDueTasks();
- verify(transportInfo.transport).newStream(same(method), same(headers), same(callOptions));
+ verify(transportInfo.transport).newStream(
+ same(method), same(headers), same(callOptions),
+ ArgumentMatchers.any());
assertThat(headers.getAll(metadataKey)).containsExactly(callCredValue);
oob.shutdownNow();
@@ -1897,6 +1935,7 @@ public void oobChannelHasNoChannelCallCredentials() {
new FakeNameResolverFactory.Builder(URI.create("oobauthority")).build())
.defaultLoadBalancingPolicy(MOCK_POLICY_NAME)
.idleTimeout(ManagedChannelImplBuilder.IDLE_MODE_MAX_TIMEOUT_DAYS, TimeUnit.DAYS)
+ .disableRetry() // irrelevant to what we test, disable retry to make verification easy
.build();
oob.getState(true);
ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class);
@@ -1919,7 +1958,9 @@ public void oobChannelHasNoChannelCallCredentials() {
call.start(mockCallListener2, headers);
// CallOptions may contain StreamTracerFactory for census that is added by default.
- verify(transportInfo.transport).newStream(same(method), same(headers), any(CallOptions.class));
+ verify(transportInfo.transport).newStream(
+ same(method), same(headers), any(CallOptions.class),
+ ArgumentMatchers.any());
assertThat(headers.getAll(metadataKey)).containsExactly(callCredValue);
oob.shutdownNow();
}
@@ -1942,6 +1983,7 @@ public SwapChannelCredentialsResult answer(InvocationOnMock invocation) {
TARGET, InsecureChannelCredentials.create(),
new FakeCallCredentials(metadataKey, channelCredValue),
new UnsupportedClientTransportFactoryBuilder(), new FixedPortProvider(DEFAULT_PORT));
+ channelBuilder.disableRetry();
configureBuilder(channelBuilder);
createChannel();
@@ -1962,7 +2004,9 @@ public SwapChannelCredentialsResult answer(InvocationOnMock invocation) {
ClientCall call = channel.newCall(method, callOptions);
call.start(mockCallListener, headers);
- verify(transportInfo.transport).newStream(same(method), same(headers), same(callOptions));
+ verify(transportInfo.transport).newStream(
+ same(method), same(headers), same(callOptions),
+ ArgumentMatchers.any());
assertThat(headers.getAll(metadataKey))
.containsExactly(channelCredValue, callCredValue).inOrder();
@@ -1977,6 +2021,7 @@ public SwapChannelCredentialsResult answer(InvocationOnMock invocation) {
new FakeNameResolverFactory.Builder(URI.create("fake://oobauthority/")).build())
.defaultLoadBalancingPolicy(MOCK_POLICY_NAME)
.idleTimeout(ManagedChannelImplBuilder.IDLE_MODE_MAX_TIMEOUT_DAYS, TimeUnit.DAYS)
+ .disableRetry() // irrelevant to what we test, disable retry to make verification easy
.build();
oob.getState(true);
ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class);
@@ -1998,7 +2043,9 @@ public SwapChannelCredentialsResult answer(InvocationOnMock invocation) {
call.start(mockCallListener2, headers);
// CallOptions may contain StreamTracerFactory for census that is added by default.
- verify(transportInfo.transport).newStream(same(method), same(headers), any(CallOptions.class));
+ verify(transportInfo.transport).newStream(
+ same(method), same(headers), any(CallOptions.class),
+ ArgumentMatchers.any());
assertThat(headers.getAll(metadataKey))
.containsExactly(oobChannelCredValue, callCredValue).inOrder();
oob.shutdownNow();
@@ -2097,7 +2144,9 @@ public void subchannelChannel_normalUsage() {
ClientCall call = sChannel.newCall(method, callOptions);
call.start(mockCallListener, headers);
- verify(mockTransport).newStream(same(method), same(headers), callOptionsCaptor.capture());
+ verify(mockTransport).newStream(
+ same(method), same(headers), callOptionsCaptor.capture(),
+ ArgumentMatchers.any());
CallOptions capturedCallOption = callOptionsCaptor.getValue();
assertThat(capturedCallOption.getDeadline()).isSameInstanceAs(callOptions.getDeadline());
@@ -2125,7 +2174,8 @@ public void subchannelChannel_failWhenNotReady() {
ClientCall call = sChannel.newCall(method, CallOptions.DEFAULT);
call.start(mockCallListener, headers);
verify(mockTransport, never()).newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
verifyNoInteractions(mockCallListener);
assertEquals(1, balancerRpcExecutor.runDueTasks());
@@ -2157,7 +2207,8 @@ public void subchannelChannel_failWaitForReady() {
sChannel.newCall(method, CallOptions.DEFAULT.withWaitForReady());
call.start(mockCallListener, headers);
verify(mockTransport, never()).newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
verifyNoInteractions(mockCallListener);
assertEquals(1, balancerRpcExecutor.runDueTasks());
@@ -2332,7 +2383,8 @@ public ClientStream answer(InvocationOnMock in) throws Throwable {
return mock(ClientStream.class);
}
}).when(transport).newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
verify(creds, never()).applyRequestMetadata(
any(RequestInfo.class), any(Executor.class), any(CallCredentials.MetadataApplier.class));
@@ -2351,11 +2403,14 @@ public ClientStream answer(InvocationOnMock in) throws Throwable {
assertEquals(AUTHORITY, infoCaptor.getValue().getAuthority());
assertEquals(SecurityLevel.NONE, infoCaptor.getValue().getSecurityLevel());
verify(transport, never()).newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
// newStream() is called after apply() is called
applierCaptor.getValue().apply(new Metadata());
- verify(transport).newStream(same(method), any(Metadata.class), same(callOptions));
+ verify(transport).newStream(
+ same(method), any(Metadata.class), same(callOptions),
+ ArgumentMatchers.any());
assertEquals("testValue", testKey.get(newStreamContexts.poll()));
// The context should not live beyond the scope of newStream() and applyRequestMetadata()
assertNull(testKey.get());
@@ -2374,11 +2429,14 @@ public ClientStream answer(InvocationOnMock in) throws Throwable {
assertEquals(SecurityLevel.NONE, infoCaptor.getValue().getSecurityLevel());
// This is from the first call
verify(transport).newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
// Still, newStream() is called after apply() is called
applierCaptor.getValue().apply(new Metadata());
- verify(transport, times(2)).newStream(same(method), any(Metadata.class), same(callOptions));
+ verify(transport, times(2)).newStream(
+ same(method), any(Metadata.class), same(callOptions),
+ ArgumentMatchers.any());
assertEquals("testValue", testKey.get(newStreamContexts.poll()));
assertNull(testKey.get());
@@ -2387,8 +2445,20 @@ public ClientStream answer(InvocationOnMock in) throws Throwable {
@Test
public void pickerReturnsStreamTracer_noDelay() {
ClientStream mockStream = mock(ClientStream.class);
- ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class);
- ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class);
+ final ClientStreamTracer tracer1 = new ClientStreamTracer() {};
+ final ClientStreamTracer tracer2 = new ClientStreamTracer() {};
+ ClientStreamTracer.Factory factory1 = new ClientStreamTracer.InternalLimitedInfoFactory() {
+ @Override
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
+ return tracer1;
+ }
+ };
+ ClientStreamTracer.Factory factory2 = new ClientStreamTracer.InternalLimitedInfoFactory() {
+ @Override
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
+ return tracer2;
+ }
+ };
createChannel();
Subchannel subchannel =
createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener);
@@ -2397,7 +2467,8 @@ public void pickerReturnsStreamTracer_noDelay() {
transportInfo.listener.transportReady();
ClientTransport mockTransport = transportInfo.transport;
when(mockTransport.newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
@@ -2409,20 +2480,29 @@ public void pickerReturnsStreamTracer_noDelay() {
call.start(mockCallListener, new Metadata());
verify(mockPicker).pickSubchannel(any(PickSubchannelArgs.class));
- verify(mockTransport).newStream(same(method), any(Metadata.class), callOptionsCaptor.capture());
- assertEquals(
- Arrays.asList(factory1, factory2),
- callOptionsCaptor.getValue().getStreamTracerFactories());
- // The factories are safely not stubbed because we do not expect any usage of them.
- verifyNoInteractions(factory1);
- verifyNoInteractions(factory2);
+ verify(mockTransport).newStream(
+ same(method), any(Metadata.class), callOptionsCaptor.capture(),
+ tracersCaptor.capture());
+ assertThat(tracersCaptor.getValue()).isEqualTo(new ClientStreamTracer[] {tracer1, tracer2});
}
@Test
public void pickerReturnsStreamTracer_delayed() {
ClientStream mockStream = mock(ClientStream.class);
- ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class);
- ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class);
+ final ClientStreamTracer tracer1 = new ClientStreamTracer() {};
+ final ClientStreamTracer tracer2 = new ClientStreamTracer() {};
+ ClientStreamTracer.Factory factory1 = new ClientStreamTracer.InternalLimitedInfoFactory() {
+ @Override
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
+ return tracer1;
+ }
+ };
+ ClientStreamTracer.Factory factory2 = new ClientStreamTracer.InternalLimitedInfoFactory() {
+ @Override
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
+ return tracer2;
+ }
+ };
createChannel();
CallOptions callOptions = CallOptions.DEFAULT.withStreamTracerFactory(factory1);
@@ -2436,7 +2516,8 @@ public void pickerReturnsStreamTracer_delayed() {
transportInfo.listener.transportReady();
ClientTransport mockTransport = transportInfo.transport;
when(mockTransport.newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
PickResult.withSubchannel(subchannel, factory2));
@@ -2445,13 +2526,10 @@ public void pickerReturnsStreamTracer_delayed() {
assertEquals(1, executor.runDueTasks());
verify(mockPicker).pickSubchannel(any(PickSubchannelArgs.class));
- verify(mockTransport).newStream(same(method), any(Metadata.class), callOptionsCaptor.capture());
- assertEquals(
- Arrays.asList(factory1, factory2),
- callOptionsCaptor.getValue().getStreamTracerFactories());
- // The factories are safely not stubbed because we do not expect any usage of them.
- verifyNoInteractions(factory1);
- verifyNoInteractions(factory2);
+ verify(mockTransport).newStream(
+ same(method), any(Metadata.class), callOptionsCaptor.capture(),
+ tracersCaptor.capture());
+ assertThat(tracersCaptor.getValue()).isEqualTo(new ClientStreamTracer[] {tracer1, tracer2});
}
@Test
@@ -2818,7 +2896,9 @@ public void idleMode_resetsDelayedTransportPicker() {
MockClientTransportInfo transportInfo = transports.poll();
ConnectionClientTransport mockTransport = transportInfo.transport;
ManagedClientTransport.Listener transportListener = transportInfo.listener;
- when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
transportListener.transportReady();
@@ -2829,7 +2909,9 @@ public void idleMode_resetsDelayedTransportPicker() {
executor.runDueTasks();
// Verify the buffered call was drained
- verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class));
+ verify(mockTransport).newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
verify(mockStream).start(any(ClientStreamListener.class));
}
@@ -2888,7 +2970,9 @@ public void enterIdle_exitsIdleIfDelayedStreamPending() {
MockClientTransportInfo transportInfo = transports.poll();
ConnectionClientTransport mockTransport = transportInfo.transport;
ManagedClientTransport.Listener transportListener = transportInfo.listener;
- when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
transportListener.transportReady();
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
@@ -2898,7 +2982,9 @@ public void enterIdle_exitsIdleIfDelayedStreamPending() {
// Verify the original call was drained
executor.runDueTasks();
- verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class));
+ verify(mockTransport).newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
verify(mockStream).start(any(ClientStreamListener.class));
}
@@ -2920,7 +3006,9 @@ public void updateBalancingStateDoesUpdatePicker() {
MockClientTransportInfo transportInfo = transports.poll();
ConnectionClientTransport mockTransport = transportInfo.transport;
ManagedClientTransport.Listener transportListener = transportInfo.listener;
- when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
transportListener.transportReady();
@@ -2929,8 +3017,9 @@ public void updateBalancingStateDoesUpdatePicker() {
updateBalancingStateSafely(helper, READY, mockPicker);
executor.runDueTasks();
- verify(mockTransport, never())
- .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+ verify(mockTransport, never()).newStream(
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
verify(mockStream, never()).start(any(ClientStreamListener.class));
@@ -2939,7 +3028,9 @@ public void updateBalancingStateDoesUpdatePicker() {
updateBalancingStateSafely(helper, READY, mockPicker);
executor.runDueTasks();
- verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class));
+ verify(mockTransport).newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
verify(mockStream).start(any(ClientStreamListener.class));
}
@@ -2958,7 +3049,9 @@ public void updateBalancingState_withWrappedSubchannel() {
MockClientTransportInfo transportInfo = transports.poll();
ConnectionClientTransport mockTransport = transportInfo.transport;
ManagedClientTransport.Listener transportListener = transportInfo.listener;
- when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
transportListener.transportReady();
@@ -2973,7 +3066,9 @@ protected Subchannel delegate() {
updateBalancingStateSafely(helper, READY, mockPicker);
executor.runDueTasks();
- verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class));
+ verify(mockTransport).newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any());
verify(mockStream).start(any(ClientStreamListener.class));
}
@@ -3405,7 +3500,8 @@ private void channelsAndSubchannels_instrumented0(boolean success) throws Except
transportInfo.listener.transportReady();
ClientTransport mockTransport = transportInfo.transport;
when(mockTransport.newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
PickResult.withSubchannel(subchannel, factory));
@@ -3478,7 +3574,9 @@ private void channelsAndSubchannels_oob_instrumented0(boolean success) throws Ex
MockClientTransportInfo transportInfo = transports.poll();
ConnectionClientTransport mockTransport = transportInfo.transport;
ManagedClientTransport.Listener transportListener = transportInfo.listener;
- when(mockTransport.newStream(same(method), same(headers), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), same(headers), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream);
// subchannel stat bumped when call gets assigned to it
@@ -3650,7 +3748,9 @@ public double nextDouble() {
ConnectionClientTransport mockTransport = transportInfo.transport;
ClientStream mockStream = mock(ClientStream.class);
ClientStream mockStream2 = mock(ClientStream.class);
- when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream).thenReturn(mockStream2);
transportInfo.listener.transportReady();
updateBalancingStateSafely(helper, READY, mockPicker);
@@ -3754,7 +3854,9 @@ public void hedgingScheduledThenChannelShutdown_hedgeShouldStillHappen_newCallSh
ConnectionClientTransport mockTransport = transportInfo.transport;
ClientStream mockStream = mock(ClientStream.class);
ClientStream mockStream2 = mock(ClientStream.class);
- when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+ when(mockTransport.newStream(
+ same(method), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mockStream).thenReturn(mockStream2);
transportInfo.listener.transportReady();
updateBalancingStateSafely(helper, READY, mockPicker);
diff --git a/core/src/test/java/io/grpc/internal/MessageDeframerTest.java b/core/src/test/java/io/grpc/internal/MessageDeframerTest.java
index c1907e51703..5edf64ef85d 100644
--- a/core/src/test/java/io/grpc/internal/MessageDeframerTest.java
+++ b/core/src/test/java/io/grpc/internal/MessageDeframerTest.java
@@ -378,7 +378,7 @@ public void sizeEnforcingInputStream_readByteAboveLimit() throws IOException {
try {
thrown.expect(StatusRuntimeException.class);
- thrown.expectMessage("RESOURCE_EXHAUSTED: Compressed gRPC message exceeds");
+ thrown.expectMessage("RESOURCE_EXHAUSTED: Decompressed gRPC message exceeds");
while (stream.read() != -1) {
}
@@ -424,7 +424,7 @@ public void sizeEnforcingInputStream_readAboveLimit() throws IOException {
try {
thrown.expect(StatusRuntimeException.class);
- thrown.expectMessage("RESOURCE_EXHAUSTED: Compressed gRPC message exceeds");
+ thrown.expectMessage("RESOURCE_EXHAUSTED: Decompressed gRPC message exceeds");
stream.read(buf, 0, buf.length);
} finally {
@@ -467,7 +467,7 @@ public void sizeEnforcingInputStream_skipAboveLimit() throws IOException {
try {
thrown.expect(StatusRuntimeException.class);
- thrown.expectMessage("RESOURCE_EXHAUSTED: Compressed gRPC message exceeds");
+ thrown.expectMessage("RESOURCE_EXHAUSTED: Decompressed gRPC message exceeds");
stream.skip(4);
} finally {
diff --git a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java
index a83964b5e91..8b851573b21 100644
--- a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java
+++ b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java
@@ -163,9 +163,11 @@ void postCommit() {
}
@Override
- ClientStream newSubstream(ClientStreamTracer.Factory tracerFactory, Metadata metadata) {
+ ClientStream newSubstream(
+ Metadata metadata, ClientStreamTracer.Factory tracerFactory, int previousAttempts,
+ boolean isTransparentRetry) {
bufferSizeTracer =
- tracerFactory.newClientStreamTracer(STREAM_INFO, new Metadata());
+ tracerFactory.newClientStreamTracer(STREAM_INFO, metadata);
int actualPreviousRpcAttemptsInHeader = metadata.get(GRPC_PREVIOUS_RPC_ATTEMPTS) == null
? 0 : Integer.valueOf(metadata.get(GRPC_PREVIOUS_RPC_ATTEMPTS));
return retriableStreamRecorder.newSubstream(actualPreviousRpcAttemptsInHeader);
@@ -254,6 +256,7 @@ public Void answer(InvocationOnMock in) {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
retriableStream.sendMessage("msg1");
@@ -306,6 +309,7 @@ public Void answer(InvocationOnMock in) {
inOrder.verify(mockStream2).writeMessage(any(InputStream.class));
inOrder.verify(mockStream2).request(456);
inOrder.verify(mockStream2, times(2)).writeMessage(any(InputStream.class));
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
// send more messages
@@ -354,6 +358,7 @@ public Void answer(InvocationOnMock in) {
inOrder.verify(mockStream3).writeMessage(any(InputStream.class));
inOrder.verify(mockStream3).request(456);
inOrder.verify(mockStream3, times(7)).writeMessage(any(InputStream.class));
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
InsightBuilder insight = new InsightBuilder();
@@ -635,6 +640,7 @@ public void retry_cancelWhileBackoff() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
verify(mockStream1).start(sublistenerCaptor1.capture());
+ verify(mockStream1).isReady();
// retry
ClientStream mockStream2 = mock(ClientStream.class);
@@ -654,7 +660,7 @@ public void retry_cancelWhileBackoff() {
@Test
public void operationsWhileDraining() {
- ArgumentCaptor sublistenerCaptor1 =
+ final ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
final AtomicReference sublistenerCaptor2 =
new AtomicReference<>();
@@ -667,10 +673,16 @@ public void operationsWhileDraining() {
@Override
public void request(int numMessages) {
retriableStream.sendMessage("substream1 request " + numMessages);
+ sublistenerCaptor1.getValue().onReady();
if (numMessages > 1) {
retriableStream.request(--numMessages);
}
}
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
}));
final ClientStream mockStream2 =
@@ -686,7 +698,7 @@ public void start(ClientStreamListener listener) {
@Override
public void request(int numMessages) {
retriableStream.sendMessage("substream2 request " + numMessages);
-
+ sublistenerCaptor2.get().onReady();
if (numMessages == 3) {
sublistenerCaptor2.get().headersRead(new Metadata());
}
@@ -697,9 +709,14 @@ public void request(int numMessages) {
retriableStream.cancel(cancelStatus);
}
}
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
}));
- InOrder inOrder = inOrder(retriableStreamRecorder, mockStream1, mockStream2);
+ InOrder inOrder = inOrder(retriableStreamRecorder, mockStream1, mockStream2, masterListener);
doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
retriableStream.start(masterListener);
@@ -714,6 +731,7 @@ public void request(int numMessages) {
inOrder.verify(mockStream1).writeMessage(any(InputStream.class)); // msg "substream1 request 2"
inOrder.verify(mockStream1).request(1);
inOrder.verify(mockStream1).writeMessage(any(InputStream.class)); // msg "substream1 request 1"
+ inOrder.verify(masterListener).onReady();
// retry
doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
@@ -741,13 +759,98 @@ public void request(int numMessages) {
// msg "substream2 request 2"
inOrder.verify(mockStream2, times(2)).writeMessage(any(InputStream.class));
inOrder.verify(mockStream2).request(100);
-
- verify(mockStream2).cancel(cancelStatus);
+ inOrder.verify(mockStream2).cancel(cancelStatus);
+ inOrder.verify(masterListener, never()).onReady();
// "substream2 request 1" will never be sent
inOrder.verify(mockStream2, never()).writeMessage(any(InputStream.class));
}
+ @Test
+ public void cancelWhileDraining() {
+ ArgumentCaptor sublistenerCaptor1 =
+ ArgumentCaptor.forClass(ClientStreamListener.class);
+ ClientStream mockStream1 = mock(ClientStream.class);
+ ClientStream mockStream2 =
+ mock(
+ ClientStream.class,
+ delegatesTo(
+ new NoopClientStream() {
+ @Override
+ public void request(int numMessages) {
+ retriableStream.cancel(
+ Status.CANCELLED.withDescription("cancelled while requesting"));
+ }
+ }));
+
+ InOrder inOrder = inOrder(retriableStreamRecorder, mockStream1, mockStream2);
+ doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+ retriableStream.start(masterListener);
+ inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ retriableStream.request(3);
+ inOrder.verify(mockStream1).request(3);
+
+ // retry
+ doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+ sublistenerCaptor1.getValue().closed(
+ Status.fromCode(RETRIABLE_STATUS_CODE_1), PROCESSED, new Metadata());
+ fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+
+ inOrder.verify(mockStream2).start(any(ClientStreamListener.class));
+ inOrder.verify(mockStream2).request(3);
+ inOrder.verify(retriableStreamRecorder).postCommit();
+ ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class);
+ inOrder.verify(mockStream2).cancel(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.CANCELLED);
+ assertThat(statusCaptor.getValue().getDescription())
+ .isEqualTo("Stream thrown away because RetriableStream committed");
+ verify(masterListener).closed(
+ statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
+ assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.CANCELLED);
+ assertThat(statusCaptor.getValue().getDescription()).isEqualTo("cancelled while requesting");
+ }
+
+ @Test
+ public void cancelWhileRetryStart() {
+ ArgumentCaptor sublistenerCaptor1 =
+ ArgumentCaptor.forClass(ClientStreamListener.class);
+ ClientStream mockStream1 = mock(ClientStream.class);
+ ClientStream mockStream2 =
+ mock(
+ ClientStream.class,
+ delegatesTo(
+ new NoopClientStream() {
+ @Override
+ public void start(ClientStreamListener listener) {
+ retriableStream.cancel(
+ Status.CANCELLED.withDescription("cancelled while retry start"));
+ }
+ }));
+
+ InOrder inOrder = inOrder(retriableStreamRecorder, mockStream1, mockStream2);
+ doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+ retriableStream.start(masterListener);
+ inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+
+ // retry
+ doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+ sublistenerCaptor1.getValue().closed(
+ Status.fromCode(RETRIABLE_STATUS_CODE_1), PROCESSED, new Metadata());
+ fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+
+ inOrder.verify(mockStream2).start(any(ClientStreamListener.class));
+ inOrder.verify(retriableStreamRecorder).postCommit();
+ ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class);
+ inOrder.verify(mockStream2).cancel(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.CANCELLED);
+ assertThat(statusCaptor.getValue().getDescription())
+ .isEqualTo("Stream thrown away because RetriableStream committed");
+ verify(masterListener).closed(
+ statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
+ assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.CANCELLED);
+ assertThat(statusCaptor.getValue().getDescription()).isEqualTo("cancelled while retry start");
+ }
+
@Test
public void operationsAfterImmediateCommit() {
ArgumentCaptor sublistenerCaptor1 =
@@ -915,6 +1018,47 @@ public void start(ClientStreamListener listener) {
verify(mockStream3).request(1);
}
+ @Test
+ public void commitAndCancelWhileDraining() {
+ ClientStream mockStream1 = mock(ClientStream.class);
+ ClientStream mockStream2 =
+ mock(
+ ClientStream.class,
+ delegatesTo(
+ new NoopClientStream() {
+ @Override
+ public void start(ClientStreamListener listener) {
+ // commit while draining
+ listener.headersRead(new Metadata());
+ // cancel while draining
+ retriableStream.cancel(
+ Status.CANCELLED.withDescription("cancelled while drained"));
+ }
+ }));
+
+ when(retriableStreamRecorder.newSubstream(anyInt()))
+ .thenReturn(mockStream1, mockStream2);
+
+ retriableStream.start(masterListener);
+
+ ArgumentCaptor sublistenerCaptor1 =
+ ArgumentCaptor.forClass(ClientStreamListener.class);
+ verify(mockStream1).start(sublistenerCaptor1.capture());
+
+ ClientStreamListener listener1 = sublistenerCaptor1.getValue();
+
+ // retry
+ listener1.closed(
+ Status.fromCode(RETRIABLE_STATUS_CODE_1), PROCESSED, new Metadata());
+ fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+
+ verify(mockStream2).start(any(ClientStreamListener.class));
+ verify(retriableStreamRecorder).postCommit();
+ ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class);
+ verify(mockStream2).cancel(statusCaptor.capture());
+ assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.CANCELLED);
+ assertThat(statusCaptor.getValue().getDescription()).isEqualTo("cancelled while drained");
+ }
@Test
public void perRpcBufferLimitExceeded() {
@@ -945,6 +1089,7 @@ public void perRpcBufferLimitExceededDuringBackoff() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
verify(mockStream1).start(sublistenerCaptor1.capture());
+ verify(mockStream1).isReady();
bufferSizeTracer.outboundWireSize(PER_RPC_BUFFER_LIMIT - 1);
@@ -961,6 +1106,7 @@ public void perRpcBufferLimitExceededDuringBackoff() {
fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
verify(mockStream2).start(any(ClientStreamListener.class));
+ verify(mockStream2).isReady();
// bufferLimitExceeded
bufferSizeTracer.outboundWireSize(PER_RPC_BUFFER_LIMIT - 1);
@@ -1024,6 +1170,7 @@ public void expBackoff_maxBackoff_maxRetryAttempts() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
@@ -1039,6 +1186,7 @@ public void expBackoff_maxBackoff_maxRetryAttempts() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
// retry2
@@ -1055,6 +1203,7 @@ public void expBackoff_maxBackoff_maxRetryAttempts() {
ArgumentCaptor sublistenerCaptor3 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
// retry3
@@ -1072,6 +1221,7 @@ public void expBackoff_maxBackoff_maxRetryAttempts() {
ArgumentCaptor sublistenerCaptor4 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream4).start(sublistenerCaptor4.capture());
+ inOrder.verify(mockStream4).isReady();
inOrder.verifyNoMoreInteractions();
// retry4
@@ -1086,6 +1236,7 @@ public void expBackoff_maxBackoff_maxRetryAttempts() {
ArgumentCaptor sublistenerCaptor5 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream5).start(sublistenerCaptor5.capture());
+ inOrder.verify(mockStream5).isReady();
inOrder.verifyNoMoreInteractions();
// retry5
@@ -1100,6 +1251,7 @@ public void expBackoff_maxBackoff_maxRetryAttempts() {
ArgumentCaptor sublistenerCaptor6 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream6).start(sublistenerCaptor6.capture());
+ inOrder.verify(mockStream6).isReady();
inOrder.verifyNoMoreInteractions();
// can not retry any more
@@ -1130,6 +1282,7 @@ public void pushback() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
@@ -1148,6 +1301,7 @@ public void pushback() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
// retry2
@@ -1165,6 +1319,7 @@ public void pushback() {
ArgumentCaptor sublistenerCaptor3 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
// retry3
@@ -1179,6 +1334,7 @@ public void pushback() {
ArgumentCaptor sublistenerCaptor4 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream4).start(sublistenerCaptor4.capture());
+ inOrder.verify(mockStream4).isReady();
inOrder.verifyNoMoreInteractions();
// retry4
@@ -1195,6 +1351,7 @@ public void pushback() {
ArgumentCaptor sublistenerCaptor5 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream5).start(sublistenerCaptor5.capture());
+ inOrder.verify(mockStream5).isReady();
inOrder.verifyNoMoreInteractions();
// retry5
@@ -1212,6 +1369,7 @@ public void pushback() {
ArgumentCaptor sublistenerCaptor6 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream6).start(sublistenerCaptor6.capture());
+ inOrder.verify(mockStream6).isReady();
inOrder.verifyNoMoreInteractions();
// can not retry any more even pushback is positive
@@ -1469,6 +1627,7 @@ public void transparentRetry() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
// transparent retry
@@ -1480,6 +1639,7 @@ public void transparentRetry() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
verify(retriableStreamRecorder, never()).postCommit();
assertEquals(0, fakeClock.numPendingTasks());
@@ -1495,6 +1655,7 @@ public void transparentRetry() {
ArgumentCaptor sublistenerCaptor3 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
verify(retriableStreamRecorder, never()).postCommit();
assertEquals(0, fakeClock.numPendingTasks());
@@ -1517,6 +1678,7 @@ public void normalRetry_thenNoTransparentRetry_butNormalRetry() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
// normal retry
@@ -1530,6 +1692,7 @@ public void normalRetry_thenNoTransparentRetry_butNormalRetry() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
verify(retriableStreamRecorder, never()).postCommit();
assertEquals(0, fakeClock.numPendingTasks());
@@ -1546,6 +1709,7 @@ public void normalRetry_thenNoTransparentRetry_butNormalRetry() {
ArgumentCaptor sublistenerCaptor3 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
verify(retriableStreamRecorder, never()).postCommit();
}
@@ -1567,6 +1731,7 @@ public void normalRetry_thenNoTransparentRetry_andNoMoreRetry() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
// normal retry
@@ -1580,6 +1745,7 @@ public void normalRetry_thenNoTransparentRetry_andNoMoreRetry() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
verify(retriableStreamRecorder, never()).postCommit();
assertEquals(0, fakeClock.numPendingTasks());
@@ -1610,6 +1776,7 @@ method, new Metadata(), channelBufferUsed, PER_RPC_BUFFER_LIMIT, CHANNEL_BUFFER_
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
// transparent retry
@@ -1622,6 +1789,7 @@ method, new Metadata(), channelBufferUsed, PER_RPC_BUFFER_LIMIT, CHANNEL_BUFFER_
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(retriableStreamRecorder).postCommit();
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
assertEquals(0, fakeClock.numPendingTasks());
}
@@ -1640,6 +1808,7 @@ public void droppedShouldNeverRetry() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
verify(mockStream1).start(sublistenerCaptor1.capture());
+ verify(mockStream1).isReady();
// drop and verify no retry
Status status = Status.fromCode(RETRIABLE_STATUS_CODE_1);
@@ -1711,6 +1880,7 @@ public Void answer(InvocationOnMock in) {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
hedgingStream.sendMessage("msg1");
@@ -1752,6 +1922,8 @@ public Void answer(InvocationOnMock in) {
inOrder.verify(mockStream2, times(2)).flush();
inOrder.verify(mockStream2).writeMessage(any(InputStream.class));
inOrder.verify(mockStream2).request(456);
+ inOrder.verify(mockStream1).isReady();
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
// send more messages
@@ -1789,6 +1961,9 @@ public Void answer(InvocationOnMock in) {
inOrder.verify(mockStream3).writeMessage(any(InputStream.class));
inOrder.verify(mockStream3).request(456);
inOrder.verify(mockStream3, times(2)).writeMessage(any(InputStream.class));
+ inOrder.verify(mockStream1).isReady();
+ inOrder.verify(mockStream2).isReady();
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
// send one more message
@@ -1831,6 +2006,9 @@ public Void answer(InvocationOnMock in) {
inOrder.verify(mockStream4).writeMessage(any(InputStream.class));
inOrder.verify(mockStream4).request(456);
inOrder.verify(mockStream4, times(4)).writeMessage(any(InputStream.class));
+ inOrder.verify(mockStream1).isReady();
+ inOrder.verify(mockStream2).isReady();
+ inOrder.verify(mockStream4).isReady();
inOrder.verifyNoMoreInteractions();
InsightBuilder insight = new InsightBuilder();
@@ -1881,6 +2059,7 @@ public void hedging_maxAttempts() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -1888,6 +2067,7 @@ public void hedging_maxAttempts() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -1895,6 +2075,7 @@ public void hedging_maxAttempts() {
ArgumentCaptor sublistenerCaptor3 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -1902,6 +2083,7 @@ public void hedging_maxAttempts() {
ArgumentCaptor sublistenerCaptor4 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream4).start(sublistenerCaptor4.capture());
+ inOrder.verify(mockStream4).isReady();
inOrder.verifyNoMoreInteractions();
// a random one of the hedges fails
@@ -1913,6 +2095,7 @@ public void hedging_maxAttempts() {
ArgumentCaptor sublistenerCaptor5 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream5).start(sublistenerCaptor5.capture());
+ inOrder.verify(mockStream5).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -1920,6 +2103,7 @@ public void hedging_maxAttempts() {
ArgumentCaptor sublistenerCaptor6 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream6).start(sublistenerCaptor6.capture());
+ inOrder.verify(mockStream6).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -1964,6 +2148,7 @@ public void hedging_receiveHeaders() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -1971,6 +2156,7 @@ public void hedging_receiveHeaders() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -1978,6 +2164,7 @@ public void hedging_receiveHeaders() {
ArgumentCaptor sublistenerCaptor3 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
// a random one of the hedges receives headers
@@ -2015,6 +2202,7 @@ public void hedging_pushback_negative() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -2022,6 +2210,7 @@ public void hedging_pushback_negative() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -2029,6 +2218,7 @@ public void hedging_pushback_negative() {
ArgumentCaptor sublistenerCaptor3 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
// a random one of the hedges receives a negative pushback
@@ -2060,6 +2250,7 @@ public void hedging_pushback_positive() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -2067,6 +2258,7 @@ public void hedging_pushback_positive() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
@@ -2084,6 +2276,7 @@ public void hedging_pushback_positive() {
ArgumentCaptor sublistenerCaptor3 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+ inOrder.verify(mockStream3).isReady();
inOrder.verifyNoMoreInteractions();
// hedge2 receives a pushback for HEDGING_DELAY_IN_SECONDS - 1 second
@@ -2097,6 +2290,7 @@ public void hedging_pushback_positive() {
ArgumentCaptor sublistenerCaptor4 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream4).start(sublistenerCaptor4.capture());
+ inOrder.verify(mockStream4).isReady();
inOrder.verifyNoMoreInteractions();
// commit
@@ -2126,6 +2320,7 @@ public void hedging_cancelled() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+ inOrder.verify(mockStream1).isReady();
inOrder.verifyNoMoreInteractions();
fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS);
@@ -2133,6 +2328,8 @@ public void hedging_cancelled() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+ inOrder.verify(mockStream1).isReady();
+ inOrder.verify(mockStream2).isReady();
inOrder.verifyNoMoreInteractions();
Status status = Status.CANCELLED.withDescription("cancelled");
@@ -2147,6 +2344,8 @@ public void hedging_cancelled() {
assertEquals(CANCELLED_BECAUSE_COMMITTED, statusCaptor.getValue().getDescription());
inOrder.verify(retriableStreamRecorder).postCommit();
+ inOrder.verify(masterListener).closed(
+ any(Status.class), any(RpcProgress.class), any(Metadata.class));
inOrder.verifyNoMoreInteractions();
}
@@ -2161,6 +2360,7 @@ public void hedging_perRpcBufferLimitExceeded() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
verify(mockStream1).start(sublistenerCaptor1.capture());
+ verify(mockStream1).isReady();
ClientStreamTracer bufferSizeTracer1 = bufferSizeTracer;
bufferSizeTracer1.outboundWireSize(PER_RPC_BUFFER_LIMIT - 1);
@@ -2169,6 +2369,8 @@ public void hedging_perRpcBufferLimitExceeded() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
verify(mockStream2).start(sublistenerCaptor2.capture());
+ verify(mockStream1, times(2)).isReady();
+ verify(mockStream2).isReady();
ClientStreamTracer bufferSizeTracer2 = bufferSizeTracer;
bufferSizeTracer2.outboundWireSize(PER_RPC_BUFFER_LIMIT - 1);
@@ -2185,6 +2387,7 @@ public void hedging_perRpcBufferLimitExceeded() {
verify(retriableStreamRecorder).postCommit();
verifyNoMoreInteractions(mockStream1);
+ verify(mockStream2).isReady();
verifyNoMoreInteractions(mockStream2);
}
@@ -2199,6 +2402,7 @@ public void hedging_channelBufferLimitExceeded() {
ArgumentCaptor sublistenerCaptor1 =
ArgumentCaptor.forClass(ClientStreamListener.class);
verify(mockStream1).start(sublistenerCaptor1.capture());
+ verify(mockStream1).isReady();
ClientStreamTracer bufferSizeTracer1 = bufferSizeTracer;
bufferSizeTracer1.outboundWireSize(100);
@@ -2207,6 +2411,8 @@ public void hedging_channelBufferLimitExceeded() {
ArgumentCaptor sublistenerCaptor2 =
ArgumentCaptor.forClass(ClientStreamListener.class);
verify(mockStream2).start(sublistenerCaptor2.capture());
+ verify(mockStream1, times(2)).isReady();
+ verify(mockStream2).isReady();
ClientStreamTracer bufferSizeTracer2 = bufferSizeTracer;
bufferSizeTracer2.outboundWireSize(100);
diff --git a/core/src/test/java/io/grpc/internal/ServerCallImplTest.java b/core/src/test/java/io/grpc/internal/ServerCallImplTest.java
index 5130bc05aa7..ea49b94e8aa 100644
--- a/core/src/test/java/io/grpc/internal/ServerCallImplTest.java
+++ b/core/src/test/java/io/grpc/internal/ServerCallImplTest.java
@@ -18,6 +18,7 @@
import static com.google.common.base.Charsets.UTF_8;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -378,6 +379,9 @@ public void streamListener_closedOk() {
verify(callListener).onComplete();
assertTrue(context.isCancelled());
assertNull(context.cancellationCause());
+ // The call considers cancellation to be an exceptional situation so it should
+ // not be cancelled with an OK status.
+ assertFalse(call.isCancelled());
}
@Test
diff --git a/core/src/test/java/io/grpc/internal/ServerImplTest.java b/core/src/test/java/io/grpc/internal/ServerImplTest.java
index 2a9dbd5a1fe..0f5c510f97c 100644
--- a/core/src/test/java/io/grpc/internal/ServerImplTest.java
+++ b/core/src/test/java/io/grpc/internal/ServerImplTest.java
@@ -1196,7 +1196,9 @@ public void testStreamClose_clientOkTriggersDelayedCancellation() throws Excepti
context, contextCancelled, null);
// For close status OK:
- // isCancelled is expected to be true after all pending work is done
+ // The context isCancelled is expected to be true after all pending work is done,
+ // but for the call it should be false as it gets set cancelled only if the call
+ // fails with a non-OK status.
assertFalse(callReference.get().isCancelled());
assertFalse(context.get().isCancelled());
streamListener.closed(Status.OK);
@@ -1204,7 +1206,7 @@ public void testStreamClose_clientOkTriggersDelayedCancellation() throws Excepti
assertFalse(context.get().isCancelled());
assertEquals(1, executor.runDueTasks());
- assertTrue(callReference.get().isCancelled());
+ assertFalse(callReference.get().isCancelled());
assertTrue(context.get().isCancelled());
assertTrue(contextCancelled.get());
}
diff --git a/core/src/test/java/io/grpc/internal/TestUtils.java b/core/src/test/java/io/grpc/internal/TestUtils.java
index d5b4ce4949e..974f36e595c 100644
--- a/core/src/test/java/io/grpc/internal/TestUtils.java
+++ b/core/src/test/java/io/grpc/internal/TestUtils.java
@@ -23,6 +23,7 @@
import io.grpc.CallOptions;
import io.grpc.ChannelLogger;
+import io.grpc.ClientStreamTracer;
import io.grpc.InternalLogId;
import io.grpc.LoadBalancer.PickResult;
import io.grpc.LoadBalancer.PickSubchannelArgs;
@@ -35,6 +36,7 @@
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.annotation.Nullable;
+import org.mockito.ArgumentMatchers;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -118,7 +120,8 @@ public ConnectionClientTransport answer(InvocationOnMock invocation) throws Thro
when(mockTransport.getLogId())
.thenReturn(InternalLogId.allocate("mocktransport", /*details=*/ null));
when(mockTransport.newStream(
- any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+ any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class),
+ ArgumentMatchers.any()))
.thenReturn(mock(ClientStream.class));
// Save the listener
doAnswer(new Answer() {
diff --git a/core/src/test/java/io/grpc/util/ForwardingClientStreamTracerTest.java b/core/src/test/java/io/grpc/util/ForwardingClientStreamTracerTest.java
index fcb19b69eb8..dbd7e99b29a 100644
--- a/core/src/test/java/io/grpc/util/ForwardingClientStreamTracerTest.java
+++ b/core/src/test/java/io/grpc/util/ForwardingClientStreamTracerTest.java
@@ -40,6 +40,7 @@ public void allMethodsForwarded() throws Exception {
Collections.emptyList());
}
+ @SuppressWarnings("deprecation")
private final class TestClientStreamTracer extends ForwardingClientStreamTracer {
@Override
protected ClientStreamTracer delegate() {
diff --git a/cronet/README.md b/cronet/README.md
index bd5329e5192..8b220bd606d 100644
--- a/cronet/README.md
+++ b/cronet/README.md
@@ -26,7 +26,7 @@ In your app module's `build.gradle` file, include a dependency on both `grpc-cro
Google Play Services Client Library for Cronet
```
-implementation 'io.grpc:grpc-cronet:1.39.0'
+implementation 'io.grpc:grpc-cronet:1.41.0'
implementation 'com.google.android.gms:play-services-cronet:16.0.0'
```
diff --git a/cronet/build.gradle b/cronet/build.gradle
index 2d73cc4194f..7a6d58a7e4b 100644
--- a/cronet/build.gradle
+++ b/cronet/build.gradle
@@ -28,6 +28,10 @@ android {
consumerProguardFiles 'proguard-rules.pro'
}
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
testOptions { unitTests { includeAndroidResources = true } }
lintOptions { disable 'InvalidPackage' }
}
diff --git a/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java b/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java
index dc4fc45ae4e..d41ec372d4c 100644
--- a/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java
+++ b/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java
@@ -21,6 +21,7 @@
import com.google.common.util.concurrent.SettableFuture;
import io.grpc.Attributes;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.InternalChannelz.SocketStats;
import io.grpc.InternalLogId;
import io.grpc.Metadata;
@@ -118,7 +119,7 @@ public ListenableFuture getStats() {
@Override
public CronetClientStream newStream(final MethodDescriptor, ?> method, final Metadata headers,
- final CallOptions callOptions) {
+ final CallOptions callOptions, ClientStreamTracer[] tracers) {
Preconditions.checkNotNull(method, "method");
Preconditions.checkNotNull(headers, "headers");
@@ -126,7 +127,7 @@ public CronetClientStream newStream(final MethodDescriptor, ?> method, final M
final String url = "https://" + authority + defaultPath;
final StatsTraceContext statsTraceCtx =
- StatsTraceContext.newClientContext(callOptions, attrs, headers);
+ StatsTraceContext.newClientContext(tracers, attrs, headers);
class StartCallback implements Runnable {
final CronetClientStream clientStream = new CronetClientStream(
url, userAgent, executor, headers, CronetClientTransport.this, this, lock, maxMessageSize,
diff --git a/cronet/src/test/java/io/grpc/cronet/CronetChannelBuilderTest.java b/cronet/src/test/java/io/grpc/cronet/CronetChannelBuilderTest.java
index 39fe03991e4..c27963c6d56 100644
--- a/cronet/src/test/java/io/grpc/cronet/CronetChannelBuilderTest.java
+++ b/cronet/src/test/java/io/grpc/cronet/CronetChannelBuilderTest.java
@@ -25,6 +25,7 @@
import android.os.Build;
import io.grpc.CallOptions;
import io.grpc.ChannelLogger;
+import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.cronet.CronetChannelBuilder.CronetTransportFactory;
@@ -50,6 +51,8 @@ public final class CronetChannelBuilderTest {
@Mock private ExperimentalCronetEngine mockEngine;
@Mock private ChannelLogger channelLogger;
+ private final ClientStreamTracer[] tracers =
+ new ClientStreamTracer[]{ new ClientStreamTracer() {} };
private MethodDescriptor, ?> method = TestMethodDescriptors.voidMethod();
@Before
@@ -69,7 +72,8 @@ public void alwaysUsePutTrue_cronetStreamIsIdempotent() throws Exception {
new InetSocketAddress("localhost", 443),
new ClientTransportOptions(),
channelLogger);
- CronetClientStream stream = transport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+ CronetClientStream stream = transport.newStream(
+ method, new Metadata(), CallOptions.DEFAULT, tracers);
assertTrue(stream.idempotent);
}
@@ -85,7 +89,8 @@ public void alwaysUsePut_defaultsToFalse() throws Exception {
new InetSocketAddress("localhost", 443),
new ClientTransportOptions(),
channelLogger);
- CronetClientStream stream = transport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+ CronetClientStream stream = transport.newStream(
+ method, new Metadata(), CallOptions.DEFAULT, tracers);
assertFalse(stream.idempotent);
}
diff --git a/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java b/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java
index 50017cb43f8..9503481e747 100644
--- a/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java
+++ b/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java
@@ -27,6 +27,7 @@
import android.os.Build;
import io.grpc.Attributes;
import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.SecurityLevel;
@@ -60,6 +61,8 @@ public final class CronetClientTransportTest {
private static final Attributes EAG_ATTRS =
Attributes.newBuilder().set(EAG_ATTR_KEY, "value").build();
+ private final ClientStreamTracer[] tracers =
+ new ClientStreamTracer[]{ new ClientStreamTracer() {} };
private CronetClientTransport transport;
@Mock private StreamBuilderFactory streamFactory;
@Mock private Executor executor;
@@ -101,9 +104,9 @@ public void transportAttributes() {
@Test
public void shutdownTransport() throws Exception {
CronetClientStream stream1 =
- transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT);
+ transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT, tracers);
CronetClientStream stream2 =
- transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT);
+ transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT, tracers);
// Create a transport and start two streams on it.
ArgumentCaptor callbackCaptor =
@@ -137,7 +140,7 @@ public void shutdownTransport() throws Exception {
@Test
public void startStreamAfterShutdown() throws Exception {
CronetClientStream stream =
- transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT);
+ transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT, tracers);
transport.shutdown();
BaseClientStreamListener listener = new BaseClientStreamListener();
stream.start(listener);
diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md
index 93447639197..60e3bb35a85 100644
--- a/documentation/android-channel-builder.md
+++ b/documentation/android-channel-builder.md
@@ -36,8 +36,8 @@ In your `build.gradle` file, include a dependency on both `grpc-android` and
`grpc-okhttp`:
```
-implementation 'io.grpc:grpc-android:1.39.0'
-implementation 'io.grpc:grpc-okhttp:1.39.0'
+implementation 'io.grpc:grpc-android:1.41.0'
+implementation 'io.grpc:grpc-okhttp:1.41.0'
```
You also need permission to access the device's network state in your
diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle
index 2d6cb8e0097..9b5ef9d448e 100644
--- a/examples/android/clientcache/app/build.gradle
+++ b/examples/android/clientcache/app/build.gradle
@@ -34,7 +34,7 @@ android {
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.17.2' }
plugins {
- grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.41.0' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
@@ -54,12 +54,12 @@ dependencies {
implementation 'com.android.support:appcompat-v7:27.0.2'
// You need to build grpc-java to obtain these libraries below.
- implementation 'io.grpc:grpc-okhttp:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-protobuf-lite:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-stub:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-okhttp:1.41.0' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-protobuf-lite:1.41.0' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-stub:1.41.0' // CURRENT_GRPC_VERSION
implementation 'org.apache.tomcat:annotations-api:6.0.53'
testImplementation 'junit:junit:4.12'
testImplementation 'com.google.truth:truth:1.0.1'
- testImplementation 'io.grpc:grpc-testing:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ testImplementation 'io.grpc:grpc-testing:1.41.0' // CURRENT_GRPC_VERSION
}
diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle
index 57490bc84ff..8a3ed89300d 100644
--- a/examples/android/helloworld/app/build.gradle
+++ b/examples/android/helloworld/app/build.gradle
@@ -32,7 +32,7 @@ android {
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.17.2' }
plugins {
- grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.41.0' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
@@ -52,8 +52,8 @@ dependencies {
implementation 'com.android.support:appcompat-v7:27.0.2'
// You need to build grpc-java to obtain these libraries below.
- implementation 'io.grpc:grpc-okhttp:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-protobuf-lite:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-stub:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-okhttp:1.41.0' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-protobuf-lite:1.41.0' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-stub:1.41.0' // CURRENT_GRPC_VERSION
implementation 'org.apache.tomcat:annotations-api:6.0.53'
}
diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle
index ed7ae228853..4b2d3989e2c 100644
--- a/examples/android/routeguide/app/build.gradle
+++ b/examples/android/routeguide/app/build.gradle
@@ -32,7 +32,7 @@ android {
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.17.2' }
plugins {
- grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.41.0' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
@@ -52,8 +52,8 @@ dependencies {
implementation 'com.android.support:appcompat-v7:27.0.2'
// You need to build grpc-java to obtain these libraries below.
- implementation 'io.grpc:grpc-okhttp:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-protobuf-lite:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-stub:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-okhttp:1.41.0' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-protobuf-lite:1.41.0' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-stub:1.41.0' // CURRENT_GRPC_VERSION
implementation 'org.apache.tomcat:annotations-api:6.0.53'
}
diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle
index b117eccc12a..e18273cbadc 100644
--- a/examples/android/strictmode/app/build.gradle
+++ b/examples/android/strictmode/app/build.gradle
@@ -33,7 +33,7 @@ android {
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.17.2' }
plugins {
- grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.41.0' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
@@ -53,8 +53,8 @@ dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
// You need to build grpc-java to obtain these libraries below.
- implementation 'io.grpc:grpc-okhttp:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-protobuf-lite:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-stub:1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-okhttp:1.41.0' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-protobuf-lite:1.41.0' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-stub:1.41.0' // CURRENT_GRPC_VERSION
implementation 'org.apache.tomcat:annotations-api:6.0.53'
}
diff --git a/examples/build.gradle b/examples/build.gradle
index 9b2226e38c6..b21a15f8443 100644
--- a/examples/build.gradle
+++ b/examples/build.gradle
@@ -22,7 +22,7 @@ targetCompatibility = 1.7
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.41.0' // CURRENT_GRPC_VERSION
def protobufVersion = '3.17.2'
def protocVersion = protobufVersion
diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle
index e3f38649708..e08d8fd1ce5 100644
--- a/examples/example-alts/build.gradle
+++ b/examples/example-alts/build.gradle
@@ -23,7 +23,7 @@ targetCompatibility = 1.7
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.41.0' // CURRENT_GRPC_VERSION
def protocVersion = '3.17.2'
dependencies {
diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle
index 708365e8973..0e7d7ece1f0 100644
--- a/examples/example-gauth/build.gradle
+++ b/examples/example-gauth/build.gradle
@@ -23,7 +23,7 @@ targetCompatibility = 1.7
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.41.0' // CURRENT_GRPC_VERSION
def protobufVersion = '3.17.2'
def protocVersion = protobufVersion
diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml
index cf901838401..91849437181 100644
--- a/examples/example-gauth/pom.xml
+++ b/examples/example-gauth/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.40.0-SNAPSHOT
+ 1.41.0
example-gauth
https://github.com/grpc/grpc-java
UTF-8
- 1.40.0-SNAPSHOT
+ 1.41.0
3.17.2
1.7
diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle
index 9ff9210ab7d..23779a52a2b 100644
--- a/examples/example-hostname/build.gradle
+++ b/examples/example-hostname/build.gradle
@@ -21,7 +21,7 @@ targetCompatibility = 1.7
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.41.0' // CURRENT_GRPC_VERSION
def protobufVersion = '3.17.2'
dependencies {
diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml
index 17e954bf67e..1df16a9acbe 100644
--- a/examples/example-hostname/pom.xml
+++ b/examples/example-hostname/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.40.0-SNAPSHOT
+ 1.41.0
example-hostname
https://github.com/grpc/grpc-java
UTF-8
- 1.40.0-SNAPSHOT
+ 1.41.0
3.17.2
1.7
diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle
index c3593a2dd08..762d8e08f3e 100644
--- a/examples/example-jwt-auth/build.gradle
+++ b/examples/example-jwt-auth/build.gradle
@@ -22,7 +22,7 @@ targetCompatibility = 1.7
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.41.0' // CURRENT_GRPC_VERSION
def protobufVersion = '3.17.2'
def protocVersion = protobufVersion
diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml
index 6e76f72b1e2..bbe496f0c90 100644
--- a/examples/example-jwt-auth/pom.xml
+++ b/examples/example-jwt-auth/pom.xml
@@ -7,13 +7,13 @@
jar
- 1.40.0-SNAPSHOT
+ 1.41.0
example-jwt-auth
https://github.com/grpc/grpc-java
UTF-8
- 1.40.0-SNAPSHOT
+ 1.41.0
3.17.2
3.17.2
diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle
index a272fc30e6a..f60b54c146a 100644
--- a/examples/example-tls/build.gradle
+++ b/examples/example-tls/build.gradle
@@ -23,7 +23,7 @@ targetCompatibility = 1.7
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.41.0' // CURRENT_GRPC_VERSION
def protocVersion = '3.17.2'
dependencies {
diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml
index ec49708c2b0..67d6ff1a507 100644
--- a/examples/example-tls/pom.xml
+++ b/examples/example-tls/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.40.0-SNAPSHOT
+ 1.41.0
example-tls
https://github.com/grpc/grpc-java
UTF-8
- 1.40.0-SNAPSHOT
+ 1.41.0
3.17.2
2.0.34.Final
diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle
index dfa860ec0d0..47151427c8a 100644
--- a/examples/example-xds/build.gradle
+++ b/examples/example-xds/build.gradle
@@ -22,7 +22,7 @@ targetCompatibility = 1.7
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.40.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.41.0' // CURRENT_GRPC_VERSION
def nettyTcNativeVersion = '2.0.31.Final'
def protocVersion = '3.17.2'
diff --git a/examples/pom.xml b/examples/pom.xml
index 3ba96b6c197..a73da5d3bdf 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.40.0-SNAPSHOT
+ 1.41.0
examples
https://github.com/grpc/grpc-java
UTF-8
- 1.40.0-SNAPSHOT
+ 1.41.0
3.17.2
3.17.2
diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java
index d27c485dc13..75f2481254d 100644
--- a/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java
+++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java
@@ -37,7 +37,7 @@
* span of an LB stream with the remote load-balancer.
*/
@ThreadSafe
-final class GrpclbClientLoadRecorder extends ClientStreamTracer.Factory {
+final class GrpclbClientLoadRecorder extends ClientStreamTracer.InternalLimitedInfoFactory {
private static final AtomicLongFieldUpdater callsStartedUpdater =
AtomicLongFieldUpdater.newUpdater(GrpclbClientLoadRecorder.class, "callsStarted");
diff --git a/grpclb/src/main/java/io/grpc/grpclb/TokenAttachingTracerFactory.java b/grpclb/src/main/java/io/grpc/grpclb/TokenAttachingTracerFactory.java
index 03b9bdf7f1b..03e1447bb2c 100644
--- a/grpclb/src/main/java/io/grpc/grpclb/TokenAttachingTracerFactory.java
+++ b/grpclb/src/main/java/io/grpc/grpclb/TokenAttachingTracerFactory.java
@@ -22,6 +22,7 @@
import io.grpc.Attributes;
import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
+import io.grpc.internal.ForwardingClientStreamTracer;
import io.grpc.internal.GrpcAttributes;
import javax.annotation.Nullable;
@@ -29,7 +30,7 @@
* Wraps a {@link ClientStreamTracer.Factory}, retrieves tokens from transport attributes and
* attaches them to headers. This is only used in the PICK_FIRST mode.
*/
-final class TokenAttachingTracerFactory extends ClientStreamTracer.Factory {
+final class TokenAttachingTracerFactory extends ClientStreamTracer.InternalLimitedInfoFactory {
private static final ClientStreamTracer NOOP_TRACER = new ClientStreamTracer() {};
@Nullable
@@ -42,19 +43,30 @@ final class TokenAttachingTracerFactory extends ClientStreamTracer.Factory {
@Override
public ClientStreamTracer newClientStreamTracer(
ClientStreamTracer.StreamInfo info, Metadata headers) {
- Attributes transportAttrs = checkNotNull(info.getTransportAttrs(), "transportAttrs");
- Attributes eagAttrs =
- checkNotNull(transportAttrs.get(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS), "eagAttrs");
- String token = eagAttrs.get(GrpclbConstants.TOKEN_ATTRIBUTE_KEY);
- headers.discardAll(GrpclbConstants.TOKEN_METADATA_KEY);
- if (token != null) {
- headers.put(GrpclbConstants.TOKEN_METADATA_KEY, token);
- }
- if (delegate != null) {
- return delegate.newClientStreamTracer(info, headers);
- } else {
+ if (delegate == null) {
return NOOP_TRACER;
}
+ final ClientStreamTracer clientStreamTracer = delegate.newClientStreamTracer(info, headers);
+ class TokenPropagationTracer extends ForwardingClientStreamTracer {
+ @Override
+ protected ClientStreamTracer delegate() {
+ return clientStreamTracer;
+ }
+
+ @Override
+ public void streamCreated(Attributes transportAttrs, Metadata headers) {
+ Attributes eagAttrs =
+ checkNotNull(transportAttrs.get(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS), "eagAttrs");
+ String token = eagAttrs.get(GrpclbConstants.TOKEN_ATTRIBUTE_KEY);
+ headers.discardAll(GrpclbConstants.TOKEN_METADATA_KEY);
+ if (token != null) {
+ headers.put(GrpclbConstants.TOKEN_METADATA_KEY, token);
+ }
+ delegate().streamCreated(transportAttrs, headers);
+ }
+ }
+
+ return new TokenPropagationTracer();
}
@Override
diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
index 39f736dbcf4..a68962ad7d9 100644
--- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
+++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
@@ -481,6 +481,7 @@ public void loadReporting() {
ClientStreamTracer tracer1 =
pick1.getStreamTracerFactory().newClientStreamTracer(STREAM_INFO, new Metadata());
+ tracer1.streamCreated(Attributes.EMPTY, new Metadata());
PickResult pick2 = picker.pickSubchannel(args);
assertNull(pick2.getSubchannel());
@@ -504,6 +505,7 @@ public void loadReporting() {
assertSame(getLoadRecorder(), pick3.getStreamTracerFactory());
ClientStreamTracer tracer3 =
pick3.getStreamTracerFactory().newClientStreamTracer(STREAM_INFO, new Metadata());
+ tracer3.streamCreated(Attributes.EMPTY, new Metadata());
// pick3 has sent out headers
tracer3.outboundHeaders();
@@ -541,6 +543,7 @@ public void loadReporting() {
assertSame(getLoadRecorder(), pick5.getStreamTracerFactory());
ClientStreamTracer tracer5 =
pick5.getStreamTracerFactory().newClientStreamTracer(STREAM_INFO, new Metadata());
+ tracer5.streamCreated(Attributes.EMPTY, new Metadata());
// pick3 ended without receiving response headers
tracer3.streamClosed(Status.DEADLINE_EXCEEDED);
diff --git a/grpclb/src/test/java/io/grpc/grpclb/TokenAttachingTracerFactoryTest.java b/grpclb/src/test/java/io/grpc/grpclb/TokenAttachingTracerFactoryTest.java
index 34b0a8ea1aa..29ded18d913 100644
--- a/grpclb/src/test/java/io/grpc/grpclb/TokenAttachingTracerFactoryTest.java
+++ b/grpclb/src/test/java/io/grpc/grpclb/TokenAttachingTracerFactoryTest.java
@@ -33,12 +33,23 @@
/** Unit tests for {@link TokenAttachingTracerFactory}. */
@RunWith(JUnit4.class)
public class TokenAttachingTracerFactoryTest {
- private static final ClientStreamTracer fakeTracer = new ClientStreamTracer() {};
+ private static final class FakeClientStreamTracer extends ClientStreamTracer {
+ Attributes transportAttrs;
+ Metadata headers;
+
+ @Override
+ public void streamCreated(Attributes transportAttrs, Metadata headers) {
+ this.transportAttrs = transportAttrs;
+ this.headers = headers;
+ }
+ }
+
+ private static final FakeClientStreamTracer fakeTracer = new FakeClientStreamTracer();
private final ClientStreamTracer.Factory delegate = mock(
ClientStreamTracer.Factory.class,
delegatesTo(
- new ClientStreamTracer.Factory() {
+ new ClientStreamTracer.InternalLimitedInfoFactory() {
@Override
public ClientStreamTracer newClientStreamTracer(
ClientStreamTracer.StreamInfo info, Metadata headers) {
@@ -51,28 +62,25 @@ public void hasToken() {
TokenAttachingTracerFactory factory = new TokenAttachingTracerFactory(delegate);
Attributes eagAttrs = Attributes.newBuilder()
.set(GrpclbConstants.TOKEN_ATTRIBUTE_KEY, "token0001").build();
- ClientStreamTracer.StreamInfo info = ClientStreamTracer.StreamInfo.newBuilder()
- .setTransportAttrs(
- Attributes.newBuilder().set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs).build())
- .build();
+ ClientStreamTracer.StreamInfo info = ClientStreamTracer.StreamInfo.newBuilder().build();
Metadata headers = new Metadata();
// Preexisting token should be replaced
headers.put(GrpclbConstants.TOKEN_METADATA_KEY, "preexisting-token");
ClientStreamTracer tracer = factory.newClientStreamTracer(info, headers);
verify(delegate).newClientStreamTracer(same(info), same(headers));
- assertThat(tracer).isSameInstanceAs(fakeTracer);
+ Attributes transportAttrs =
+ Attributes.newBuilder().set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs).build();
+ tracer.streamCreated(transportAttrs, headers);
+ assertThat(fakeTracer.transportAttrs).isSameInstanceAs(transportAttrs);
+ assertThat(fakeTracer.headers).isSameInstanceAs(headers);
assertThat(headers.getAll(GrpclbConstants.TOKEN_METADATA_KEY)).containsExactly("token0001");
}
@Test
public void noToken() {
TokenAttachingTracerFactory factory = new TokenAttachingTracerFactory(delegate);
- ClientStreamTracer.StreamInfo info = ClientStreamTracer.StreamInfo.newBuilder()
- .setTransportAttrs(
- Attributes.newBuilder()
- .set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, Attributes.EMPTY).build())
- .build();
+ ClientStreamTracer.StreamInfo info = ClientStreamTracer.StreamInfo.newBuilder().build();
Metadata headers = new Metadata();
// Preexisting token should be removed
@@ -80,22 +88,25 @@ public void noToken() {
ClientStreamTracer tracer = factory.newClientStreamTracer(info, headers);
verify(delegate).newClientStreamTracer(same(info), same(headers));
- assertThat(tracer).isSameInstanceAs(fakeTracer);
+ Attributes transportAttrs =
+ Attributes.newBuilder().set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, Attributes.EMPTY).build();
+ tracer.streamCreated(transportAttrs, headers);
+ assertThat(fakeTracer.transportAttrs).isSameInstanceAs(transportAttrs);
+ assertThat(fakeTracer.headers).isSameInstanceAs(headers);
assertThat(headers.get(GrpclbConstants.TOKEN_METADATA_KEY)).isNull();
}
@Test
public void nullDelegate() {
TokenAttachingTracerFactory factory = new TokenAttachingTracerFactory(null);
- ClientStreamTracer.StreamInfo info = ClientStreamTracer.StreamInfo.newBuilder()
- .setTransportAttrs(
- Attributes.newBuilder()
- .set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, Attributes.EMPTY).build())
- .build();
+ ClientStreamTracer.StreamInfo info = ClientStreamTracer.StreamInfo.newBuilder().build();
Metadata headers = new Metadata();
ClientStreamTracer tracer = factory.newClientStreamTracer(info, headers);
+ tracer.streamCreated(
+ Attributes.newBuilder().set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, Attributes.EMPTY).build(),
+ headers);
assertThat(tracer).isNotNull();
assertThat(headers.get(GrpclbConstants.TOKEN_METADATA_KEY)).isNull();
}
diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle
index 79aa5356ecd..944c0daab81 100644
--- a/interop-testing/build.gradle
+++ b/interop-testing/build.gradle
@@ -27,6 +27,7 @@ dependencies {
project(':grpc-stub'),
project(':grpc-testing'),
project(path: ':grpc-xds', configuration: 'shadow'),
+ libraries.hdrhistogram,
libraries.junit,
libraries.truth,
libraries.opencensus_contrib_grpc_metrics,
@@ -43,6 +44,8 @@ dependencies {
libraries.netty_tcnative,
project(':grpc-grpclb')
testImplementation project(':grpc-context').sourceSets.test.output,
+ project(':grpc-api').sourceSets.test.output,
+ project(':grpc-core').sourceSets.test.output,
libraries.mockito
alpnagent libraries.jetty_alpn_agent
}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java
index 758f99d5353..33d263e95de 100644
--- a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java
@@ -92,6 +92,9 @@
import io.grpc.testing.integration.Messages.StreamingOutputCallRequest;
import io.grpc.testing.integration.Messages.StreamingOutputCallResponse;
import io.opencensus.contrib.grpc.metrics.RpcMeasureConstants;
+import io.opencensus.stats.Measure;
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.stats.Measure.MeasureLong;
import io.opencensus.tags.TagKey;
import io.opencensus.tags.TagValue;
import io.opencensus.trace.Span;
@@ -124,6 +127,7 @@
import javax.annotation.Nullable;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
+import org.HdrHistogram.Histogram;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
@@ -152,6 +156,15 @@ public abstract class AbstractInteropTest {
* SETTINGS/WINDOW_UPDATE exchange.
*/
public static final int TEST_FLOW_CONTROL_WINDOW = 65 * 1024;
+ private static final MeasureLong RETRIES_PER_CALL =
+ Measure.MeasureLong.create(
+ "grpc.io/client/retries_per_call", "Number of retries per call", "1");
+ private static final MeasureLong TRANSPARENT_RETRIES_PER_CALL =
+ Measure.MeasureLong.create(
+ "grpc.io/client/transparent_retries_per_call", "Transparent retries per call", "1");
+ private static final MeasureDouble RETRY_DELAY_PER_CALL =
+ Measure.MeasureDouble.create(
+ "grpc.io/client/retry_delay_per_call", "Retry delay per call", "ms");
private static final FakeTagger tagger = new FakeTagger();
private static final FakeTagContextBinarySerializer tagContextBinarySerializer =
@@ -289,7 +302,7 @@ final SocketAddress getListenAddress() {
new LinkedBlockingQueue<>();
private final ClientStreamTracer.Factory clientStreamTracerFactory =
- new ClientStreamTracer.Factory() {
+ new ClientStreamTracer.InternalLimitedInfoFactory() {
@Override
public ClientStreamTracer newClientStreamTracer(
ClientStreamTracer.StreamInfo info, Metadata headers) {
@@ -375,7 +388,8 @@ protected final ClientInterceptor createCensusStatsClientInterceptor() {
.getClientInterceptor(
tagger, tagContextBinarySerializer, clientStatsRecorder,
GrpcUtil.STOPWATCH_SUPPLIER,
- true, true, true, false /* real-time metrics */);
+ true, true, true,
+ /* recordRealTimeMetrics= */ false);
}
protected final ServerStreamTracer.Factory createCustomCensusTracerFactory() {
@@ -1042,19 +1056,18 @@ public void veryLargeResponse() throws Exception {
@Test
public void exchangeMetadataUnaryCall() throws Exception {
- TestServiceGrpc.TestServiceBlockingStub stub = blockingStub;
-
// Capture the metadata exchange
Metadata fixedHeaders = new Metadata();
// Send a context proto (as it's in the default extension registry)
Messages.SimpleContext contextValue =
Messages.SimpleContext.newBuilder().setValue("dog").build();
fixedHeaders.put(Util.METADATA_KEY, contextValue);
- stub = MetadataUtils.attachHeaders(stub, fixedHeaders);
// .. and expect it to be echoed back in trailers
AtomicReference trailersCapture = new AtomicReference<>();
AtomicReference headersCapture = new AtomicReference<>();
- stub = MetadataUtils.captureMetadata(stub, headersCapture, trailersCapture);
+ TestServiceGrpc.TestServiceBlockingStub stub = blockingStub.withInterceptors(
+ MetadataUtils.newAttachHeadersInterceptor(fixedHeaders),
+ MetadataUtils.newCaptureMetadataInterceptor(headersCapture, trailersCapture));
assertNotNull(stub.emptyCall(EMPTY));
@@ -1065,19 +1078,18 @@ public void exchangeMetadataUnaryCall() throws Exception {
@Test
public void exchangeMetadataStreamingCall() throws Exception {
- TestServiceGrpc.TestServiceStub stub = asyncStub;
-
// Capture the metadata exchange
Metadata fixedHeaders = new Metadata();
// Send a context proto (as it's in the default extension registry)
Messages.SimpleContext contextValue =
Messages.SimpleContext.newBuilder().setValue("dog").build();
fixedHeaders.put(Util.METADATA_KEY, contextValue);
- stub = MetadataUtils.attachHeaders(stub, fixedHeaders);
// .. and expect it to be echoed back in trailers
AtomicReference trailersCapture = new AtomicReference<>();
AtomicReference headersCapture = new AtomicReference<>();
- stub = MetadataUtils.captureMetadata(stub, headersCapture, trailersCapture);
+ TestServiceGrpc.TestServiceStub stub = asyncStub.withInterceptors(
+ MetadataUtils.newAttachHeadersInterceptor(fixedHeaders),
+ MetadataUtils.newCaptureMetadataInterceptor(headersCapture, trailersCapture));
List responseSizes = Arrays.asList(50, 100, 150, 200);
Messages.StreamingOutputCallRequest.Builder streamingOutputBuilder =
@@ -1179,6 +1191,7 @@ public void deadlineExceeded() throws Exception {
public void deadlineExceededServerStreaming() throws Exception {
// warm up the channel and JVM
blockingStub.emptyCall(Empty.getDefaultInstance());
+ assertStatsTrace("grpc.testing.TestService/EmptyCall", Status.Code.OK);
ResponseParameters.Builder responseParameters = ResponseParameters.newBuilder()
.setSize(1)
.setIntervalUs(10000);
@@ -1195,7 +1208,6 @@ public void deadlineExceededServerStreaming() throws Exception {
recorder.awaitCompletion();
assertEquals(Status.DEADLINE_EXCEEDED.getCode(),
Status.fromThrowable(recorder.getError()).getCode());
- assertStatsTrace("grpc.testing.TestService/EmptyCall", Status.Code.OK);
if (metricsExpected()) {
// Stream may not have been created when deadline is exceeded, thus we don't check tracer
// stats.
@@ -1235,10 +1247,18 @@ public void deadlineInPast() throws Exception {
checkEndTags(
clientEndRecord, "grpc.testing.TestService/EmptyCall",
Status.DEADLINE_EXCEEDED.getCode(), true);
+ assertZeroRetryRecorded();
}
// warm up the channel
blockingStub.emptyCall(Empty.getDefaultInstance());
+ if (metricsExpected()) {
+ // clientStartRecord
+ clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+ // clientEndRecord
+ clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+ assertZeroRetryRecorded();
+ }
try {
blockingStub
.withDeadlineAfter(-10, TimeUnit.SECONDS)
@@ -1249,7 +1269,6 @@ public void deadlineInPast() throws Exception {
assertThat(ex.getStatus().getDescription())
.startsWith("ClientCall started after deadline exceeded");
}
- assertStatsTrace("grpc.testing.TestService/EmptyCall", Status.Code.OK);
if (metricsExpected()) {
MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
checkStartTags(clientStartRecord, "grpc.testing.TestService/EmptyCall", true);
@@ -1257,6 +1276,7 @@ public void deadlineInPast() throws Exception {
checkEndTags(
clientEndRecord, "grpc.testing.TestService/EmptyCall",
Status.DEADLINE_EXCEEDED.getCode(), true);
+ assertZeroRetryRecorded();
}
}
@@ -1484,11 +1504,11 @@ public void customMetadata() throws Exception {
Metadata metadata = new Metadata();
metadata.put(Util.ECHO_INITIAL_METADATA_KEY, "test_initial_metadata_value");
metadata.put(Util.ECHO_TRAILING_METADATA_KEY, trailingBytes);
- TestServiceGrpc.TestServiceBlockingStub blockingStub = this.blockingStub;
- blockingStub = MetadataUtils.attachHeaders(blockingStub, metadata);
AtomicReference headersCapture = new AtomicReference<>();
AtomicReference trailersCapture = new AtomicReference<>();
- blockingStub = MetadataUtils.captureMetadata(blockingStub, headersCapture, trailersCapture);
+ TestServiceGrpc.TestServiceBlockingStub blockingStub = this.blockingStub.withInterceptors(
+ MetadataUtils.newAttachHeadersInterceptor(metadata),
+ MetadataUtils.newCaptureMetadataInterceptor(headersCapture, trailersCapture));
SimpleResponse response = blockingStub.unaryCall(request);
assertResponse(goldenResponse, response);
@@ -1503,11 +1523,11 @@ public void customMetadata() throws Exception {
metadata = new Metadata();
metadata.put(Util.ECHO_INITIAL_METADATA_KEY, "test_initial_metadata_value");
metadata.put(Util.ECHO_TRAILING_METADATA_KEY, trailingBytes);
- TestServiceGrpc.TestServiceStub stub = asyncStub;
- stub = MetadataUtils.attachHeaders(stub, metadata);
headersCapture = new AtomicReference<>();
trailersCapture = new AtomicReference<>();
- stub = MetadataUtils.captureMetadata(stub, headersCapture, trailersCapture);
+ TestServiceGrpc.TestServiceStub stub = asyncStub.withInterceptors(
+ MetadataUtils.newAttachHeadersInterceptor(metadata),
+ MetadataUtils.newCaptureMetadataInterceptor(headersCapture, trailersCapture));
StreamRecorder