From c11f45d6748b07dbe20fea363731920fa96d2101 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:21:15 -0700 Subject: [PATCH 01/22] Pre-factor out the guts of the BinderClientTransport handshake --- .../grpc/binder/internal/BinderTransport.java | 123 ++++++++++++------ 1 file changed, 86 insertions(+), 37 deletions(-) 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 d87cfb74044..7fefde7720b 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -22,6 +22,7 @@ import static io.grpc.binder.ApiConstants.PRE_AUTH_SERVER_OVERRIDE; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; import android.os.Binder; @@ -32,6 +33,7 @@ import android.os.RemoteException; import android.os.TransactionTooLargeException; import androidx.annotation.BinderThread; +import androidx.annotation.MainThread; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Ticker; import com.google.common.base.Verify; @@ -559,6 +561,27 @@ final void handleAcknowledgedBytes(long numBytes) { } } + /** + * An abstraction of the client handshake, used to transition off a problematic legacy approach. + */ + interface ClientHandshake { + /** + * Notifies the implementation that the binding has succeeded and we are now connected to the + * server 'endpointBinder'. + */ + @GuardedBy("this") + @MainThread + void onBound(IBinder endpointBinder); + + /** + * Notifies the implementation that we've received a valid SETUP_TRANSPORT transaction from a + * server that can be reached at 'serverBinder'. + */ + @GuardedBy("this") + @BinderThread + void handleSetupTransport(IBinder serverBinder); + } + /** Concrete client-side transport implementation. */ @ThreadSafe @Internal @@ -576,6 +599,7 @@ public static final class BinderClientTransport extends BinderTransport private final long readyTimeoutMillis; private final PingTracker pingTracker; private final boolean preAuthorizeServer; + private final ClientHandshake handshakeImpl; @Nullable private ManagedClientTransport.Listener clientTransportListener; @@ -618,6 +642,7 @@ public BinderClientTransport( Boolean preAuthServerOverride = options.getEagAttributes().get(PRE_AUTH_SERVER_OVERRIDE); this.preAuthorizeServer = preAuthServerOverride != null ? preAuthServerOverride : factory.preAuthorizeServers; + this.handshakeImpl = new LegacyClientHandshake(); numInUseStreams = new AtomicInteger(); pingTracker = new PingTracker(Ticker.systemTicker(), (id) -> sendPing(id)); @@ -641,9 +666,8 @@ void releaseExecutors() { } @Override - public synchronized void onBound(IBinder binder) { - sendSetupTransaction( - binderDecorator.decorate(OneWayBinderProxy.wrap(binder, offloadExecutor))); + public synchronized void onBound(IBinder endpointBinder) { + handshakeImpl.onBound(endpointBinder); } @Override @@ -826,8 +850,6 @@ void notifyTerminated() { @Override @GuardedBy("this") protected void handleSetupTransport(Parcel parcel) { - int remoteUid = Binder.getCallingUid(); - attributes = setSecurityAttrs(attributes, remoteUid); if (inState(TransportState.SETUP)) { int version = parcel.readInt(); IBinder binder = parcel.readStrongBinder(); @@ -838,21 +860,7 @@ protected void handleSetupTransport(Parcel parcel) { shutdownInternal( Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true); } else { - authResultFuture = checkServerAuthorizationAsync(remoteUid); - Futures.addCallback( - authResultFuture, - new FutureCallback() { - @Override - public void onSuccess(Status result) { - handleAuthResult(binder, result); - } - - @Override - public void onFailure(Throwable t) { - handleAuthResult(t); - } - }, - offloadExecutor); + handshakeImpl.handleSetupTransport(binder); } } } @@ -863,29 +871,70 @@ private ListenableFuture checkServerAuthorizationAsync(int remoteUid) { : Futures.submit(() -> securityPolicy.checkAuthorization(remoteUid), offloadExecutor); } - private synchronized void handleAuthResult(IBinder binder, Status authorization) { - if (inState(TransportState.SETUP)) { - if (!authorization.isOk()) { - shutdownInternal(authorization, true); - } else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) { - shutdownInternal( - Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true); - } else { - // Check state again, since a failure inside setOutgoingBinder (or a callback it - // triggers), could have shut us down. - if (!isShutdown()) { - setState(TransportState.READY); - attributes = clientTransportListener.filterTransport(attributes); - clientTransportListener.transportReady(); - if (readyTimeoutFuture != null) { - readyTimeoutFuture.cancel(false); - readyTimeoutFuture = null; + class LegacyClientHandshake implements ClientHandshake { + @Override + @MainThread + @GuardedBy("BinderClientTransport.this") + public void onBound(IBinder binder) { + sendSetupTransaction( + binderDecorator.decorate(OneWayBinderProxy.wrap(binder, offloadExecutor))); + } + + @Override + @BinderThread + @GuardedBy("BinderClientTransport.this") + public void handleSetupTransport(IBinder binder) { + int remoteUid = Binder.getCallingUid(); + attributes = setSecurityAttrs(attributes, remoteUid); + authResultFuture = checkServerAuthorizationAsync(remoteUid); + Futures.addCallback( + authResultFuture, + new FutureCallback() { + @Override + public void onSuccess(Status result) { + synchronized (BinderClientTransport.this) { + handleAuthResult(binder, result); + } + } + + @Override + public void onFailure(Throwable t) { + BinderClientTransport.this.handleAuthResult(t); + } + }, + offloadExecutor); + } + + @GuardedBy("BinderClientTransport.this") + private void handleAuthResult(IBinder binder, Status authorization) { + if (inState(TransportState.SETUP)) { + if (!authorization.isOk()) { + shutdownInternal(authorization, true); + } else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) { + shutdownInternal( + Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true); + } else { + // Check state again, since a failure inside setOutgoingBinder (or a callback it + // triggers), could have shut us down. + if (!isShutdown()) { + reportReady(); } } } } } + @GuardedBy("this") + private void reportReady() { + setState(TransportState.READY); + attributes = clientTransportListener.filterTransport(attributes); + clientTransportListener.transportReady(); + if (readyTimeoutFuture != null) { + readyTimeoutFuture.cancel(false); + readyTimeoutFuture = null; + } + } + private synchronized void handleAuthResult(Throwable t) { shutdownInternal( Status.INTERNAL.withDescription("Could not evaluate SecurityPolicy").withCause(t), true); From 8486ac1c2e4a7ff4c57f3601688bdb17e6ed7c2d Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:23:34 -0700 Subject: [PATCH 02/22] don't rename binder --- .../main/java/io/grpc/binder/internal/BinderTransport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7fefde7720b..ba02e597714 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -666,8 +666,8 @@ void releaseExecutors() { } @Override - public synchronized void onBound(IBinder endpointBinder) { - handshakeImpl.onBound(endpointBinder); + public synchronized void onBound(IBinder binder) { + handshakeImpl.onBound(binder); } @Override From efd855f8183df7ccb1f88d266c05c64e7ff59e55 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:31:34 -0700 Subject: [PATCH 03/22] ComponentName --- .../src/main/java/io/grpc/binder/internal/BinderTransport.java | 1 - 1 file changed, 1 deletion(-) 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 ba02e597714..f1b1509d9e5 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -22,7 +22,6 @@ import static io.grpc.binder.ApiConstants.PRE_AUTH_SERVER_OVERRIDE; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; import android.os.Binder; From 80eee5816434b5fb6eef8373b5890ae6998ca3ba Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:21:15 -0700 Subject: [PATCH 04/22] Pre-factor out the guts of the BinderClientTransport handshake # Conflicts: # binder/src/main/java/io/grpc/binder/internal/BinderTransport.java --- .../main/java/io/grpc/binder/internal/BinderTransport.java | 4 ++++ 1 file changed, 4 insertions(+) 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 0fe131a0728..39779f7bd7c 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -20,12 +20,16 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.util.concurrent.Futures.immediateFuture; +import android.content.Context; +import android.content.pm.ServiceInfo; +import android.os.Binder; import android.os.DeadObjectException; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.os.TransactionTooLargeException; import androidx.annotation.BinderThread; +import androidx.annotation.MainThread; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Verify; import com.google.common.util.concurrent.ListenableFuture; From 643144c17912e9bab54a1e02fe1929b7d47647b8 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:21:15 -0700 Subject: [PATCH 05/22] Pre-factor out the guts of the BinderClientTransport handshake # Conflicts: # binder/src/main/java/io/grpc/binder/internal/BinderTransport.java --- .../src/main/java/io/grpc/binder/internal/BinderTransport.java | 1 + 1 file changed, 1 insertion(+) 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 39779f7bd7c..0c9259b91db 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.util.concurrent.Futures.immediateFuture; +import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; import android.os.Binder; From aedc3bc8e3b7fe132f55f6147c1225056ac4cc4c Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:31:34 -0700 Subject: [PATCH 06/22] ComponentName --- .../src/main/java/io/grpc/binder/internal/BinderTransport.java | 1 - 1 file changed, 1 deletion(-) 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 0c9259b91db..39779f7bd7c 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -20,7 +20,6 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.util.concurrent.Futures.immediateFuture; -import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; import android.os.Binder; From d9509b5bb3b9525669346124c7560f1fda5a3638 Mon Sep 17 00:00:00 2001 From: zrlw Date: Sat, 30 Aug 2025 05:01:11 +0800 Subject: [PATCH 07/22] Upgrade guava version to 33.4.8 (#12219) Guava seems to call a deprecated sun.misc.Unsafe::objectFieldOffset method which might be removed in a future JDK release. There are still a few things to do for -android versions, which are tracked in https://github.com/google/guava/issues/7742, see details at google/guava#7811 Fixes #12215 --- MODULE.bazel | 6 +++--- core/build.gradle | 2 +- cronet/build.gradle | 1 + examples/android/clientcache/settings.gradle | 16 ++++++++++++++++ examples/android/helloworld/settings.gradle | 16 ++++++++++++++++ examples/android/routeguide/settings.gradle | 16 ++++++++++++++++ examples/android/strictmode/settings.gradle | 16 ++++++++++++++++ examples/example-alts/settings.gradle | 14 ++++++++++++++ examples/example-debug/settings.gradle | 16 ++++++++++++++++ examples/example-dualstack/settings.gradle | 14 ++++++++++++++ examples/example-gauth/settings.gradle | 14 ++++++++++++++ .../settings.gradle | 16 ++++++++++++++++ .../example-gcp-observability/settings.gradle | 16 ++++++++++++++++ examples/example-hostname/settings.gradle | 16 ++++++++++++++++ examples/example-jwt-auth/settings.gradle | 14 ++++++++++++++ examples/example-oauth/settings.gradle | 14 ++++++++++++++ examples/example-opentelemetry/settings.gradle | 16 ++++++++++++++++ examples/example-orca/settings.gradle | 16 ++++++++++++++++ examples/example-reflection/settings.gradle | 16 ++++++++++++++++ examples/example-servlet/settings.gradle | 14 ++++++++++++++ examples/example-tls/settings.gradle | 14 ++++++++++++++ examples/example-xds/settings.gradle | 16 ++++++++++++++++ examples/settings.gradle | 14 ++++++++++++++ gradle/libs.versions.toml | 14 +++++--------- repositories.bzl | 6 +++--- s2a/build.gradle | 1 + settings.gradle | 13 +++++++++++++ 27 files changed, 331 insertions(+), 16 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 7d49c4e4b49..4465f46e6c5 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -15,9 +15,9 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.auto.value:auto-value:1.11.0", "com.google.code.findbugs:jsr305:3.0.2", "com.google.code.gson:gson:2.11.0", - "com.google.errorprone:error_prone_annotations:2.30.0", + "com.google.errorprone:error_prone_annotations:2.36.0", "com.google.guava:failureaccess:1.0.1", - "com.google.guava:guava:33.3.1-android", + "com.google.guava:guava:33.4.8-android", "com.google.re2j:re2j:1.8", "com.google.s2a.proto.v2:s2a-proto:0.1.2", "com.google.truth:truth:1.4.2", @@ -41,7 +41,7 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "io.opencensus:opencensus-contrib-grpc-metrics:0.31.0", "io.perfmark:perfmark-api:0.27.0", "junit:junit:4.13.2", - "org.checkerframework:checker-qual:3.12.0", + "org.checkerframework:checker-qual:3.49.5", "org.codehaus.mojo:animal-sniffer-annotations:1.24", ] # GRPC_DEPS_END diff --git a/core/build.gradle b/core/build.gradle index 2fac9ddba04..b320f326b41 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'com.google.guava:guava:30.0-android' + classpath 'com.google.guava:guava:33.4.8-android' } } diff --git a/cronet/build.gradle b/cronet/build.gradle index 0715b4129bf..d6d773a97e4 100644 --- a/cronet/build.gradle +++ b/cronet/build.gradle @@ -46,6 +46,7 @@ dependencies { libraries.cronet.api implementation project(':grpc-core') implementation libraries.guava + implementation 'org.checkerframework:checker-qual:3.49.5' testImplementation project(':grpc-testing') testImplementation libraries.cronet.embedded diff --git a/examples/android/clientcache/settings.gradle b/examples/android/clientcache/settings.gradle index e7b4def49cb..6208d70e838 100644 --- a/examples/android/clientcache/settings.gradle +++ b/examples/android/clientcache/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + include ':app' diff --git a/examples/android/helloworld/settings.gradle b/examples/android/helloworld/settings.gradle index e7b4def49cb..6208d70e838 100644 --- a/examples/android/helloworld/settings.gradle +++ b/examples/android/helloworld/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + include ':app' diff --git a/examples/android/routeguide/settings.gradle b/examples/android/routeguide/settings.gradle index e7b4def49cb..6208d70e838 100644 --- a/examples/android/routeguide/settings.gradle +++ b/examples/android/routeguide/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + include ':app' diff --git a/examples/android/strictmode/settings.gradle b/examples/android/strictmode/settings.gradle index e7b4def49cb..6208d70e838 100644 --- a/examples/android/strictmode/settings.gradle +++ b/examples/android/strictmode/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + include ':app' diff --git a/examples/example-alts/settings.gradle b/examples/example-alts/settings.gradle index c665ff96674..6bd0f0cdc2d 100644 --- a/examples/example-alts/settings.gradle +++ b/examples/example-alts/settings.gradle @@ -1,4 +1,18 @@ pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } + repositories { gradlePluginPortal() } diff --git a/examples/example-debug/settings.gradle b/examples/example-debug/settings.gradle index 3700c983b6c..48c08629ca9 100644 --- a/examples/example-debug/settings.gradle +++ b/examples/example-debug/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + rootProject.name = 'example-debug' diff --git a/examples/example-dualstack/settings.gradle b/examples/example-dualstack/settings.gradle index 49a762696f7..160d5134334 100644 --- a/examples/example-dualstack/settings.gradle +++ b/examples/example-dualstack/settings.gradle @@ -1,4 +1,18 @@ pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } + repositories { gradlePluginPortal() } diff --git a/examples/example-gauth/settings.gradle b/examples/example-gauth/settings.gradle index c665ff96674..6bd0f0cdc2d 100644 --- a/examples/example-gauth/settings.gradle +++ b/examples/example-gauth/settings.gradle @@ -1,4 +1,18 @@ pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } + repositories { gradlePluginPortal() } diff --git a/examples/example-gcp-csm-observability/settings.gradle b/examples/example-gcp-csm-observability/settings.gradle index 6b7615117d6..44e6f340ede 100644 --- a/examples/example-gcp-csm-observability/settings.gradle +++ b/examples/example-gcp-csm-observability/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + rootProject.name = 'example-gcp-csm-observability' diff --git a/examples/example-gcp-observability/settings.gradle b/examples/example-gcp-observability/settings.gradle index 1e4ba3812eb..39efc20a459 100644 --- a/examples/example-gcp-observability/settings.gradle +++ b/examples/example-gcp-observability/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + rootProject.name = 'example-gcp-observability' diff --git a/examples/example-hostname/settings.gradle b/examples/example-hostname/settings.gradle index aa159eb0946..5bd641b3fc1 100644 --- a/examples/example-hostname/settings.gradle +++ b/examples/example-hostname/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + rootProject.name = 'hostname' diff --git a/examples/example-jwt-auth/settings.gradle b/examples/example-jwt-auth/settings.gradle index c665ff96674..6bd0f0cdc2d 100644 --- a/examples/example-jwt-auth/settings.gradle +++ b/examples/example-jwt-auth/settings.gradle @@ -1,4 +1,18 @@ pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } + repositories { gradlePluginPortal() } diff --git a/examples/example-oauth/settings.gradle b/examples/example-oauth/settings.gradle index c665ff96674..6bd0f0cdc2d 100644 --- a/examples/example-oauth/settings.gradle +++ b/examples/example-oauth/settings.gradle @@ -1,4 +1,18 @@ pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } + repositories { gradlePluginPortal() } diff --git a/examples/example-opentelemetry/settings.gradle b/examples/example-opentelemetry/settings.gradle index ff7ea3fc2be..26e3bea044b 100644 --- a/examples/example-opentelemetry/settings.gradle +++ b/examples/example-opentelemetry/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + rootProject.name = 'example-opentelemetry' diff --git a/examples/example-orca/settings.gradle b/examples/example-orca/settings.gradle index 3c62dc663ce..12536c0ca8d 100644 --- a/examples/example-orca/settings.gradle +++ b/examples/example-orca/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + rootProject.name = 'example-orca' diff --git a/examples/example-reflection/settings.gradle b/examples/example-reflection/settings.gradle index dccb973085e..28e44b77905 100644 --- a/examples/example-reflection/settings.gradle +++ b/examples/example-reflection/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + rootProject.name = 'example-reflection' diff --git a/examples/example-servlet/settings.gradle b/examples/example-servlet/settings.gradle index c665ff96674..6bd0f0cdc2d 100644 --- a/examples/example-servlet/settings.gradle +++ b/examples/example-servlet/settings.gradle @@ -1,4 +1,18 @@ pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } + repositories { gradlePluginPortal() } diff --git a/examples/example-tls/settings.gradle b/examples/example-tls/settings.gradle index c665ff96674..6bd0f0cdc2d 100644 --- a/examples/example-tls/settings.gradle +++ b/examples/example-tls/settings.gradle @@ -1,4 +1,18 @@ pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } + repositories { gradlePluginPortal() } diff --git a/examples/example-xds/settings.gradle b/examples/example-xds/settings.gradle index 878f1f23ae3..4197fa6760d 100644 --- a/examples/example-xds/settings.gradle +++ b/examples/example-xds/settings.gradle @@ -1 +1,17 @@ +pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } +} + rootProject.name = 'example-xds' diff --git a/examples/settings.gradle b/examples/settings.gradle index dadd24b1c0f..4d39e8b45ba 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -1,4 +1,18 @@ pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } + repositories { gradlePluginPortal() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a374ba5aa73..e66f253fc21 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,11 +31,7 @@ commons-math3 = "org.apache.commons:commons-math3:3.6.1" conscrypt = "org.conscrypt:conscrypt-openjdk-uber:2.5.2" cronet-api = "org.chromium.net:cronet-api:119.6045.31" cronet-embedded = "org.chromium.net:cronet-embedded:119.6045.31" -# error-prone 2.31.0+ blocked on https://github.com/grpc/grpc-java/issues/10152 -# It breaks Bazel (ArrayIndexOutOfBoundsException in turbine) and Dexing ("D8: -# java.lang.NullPointerException"). We can trivially upgrade the Bazel CI to -# 6.3.0+ (https://github.com/bazelbuild/bazel/issues/18743). -errorprone-annotations = "com.google.errorprone:error_prone_annotations:2.30.0" +errorprone-annotations = "com.google.errorprone:error_prone_annotations:2.36.0" # error-prone 2.32.0+ require Java 17+ errorprone-core = "com.google.errorprone:error_prone_core:2.31.0" google-api-protos = "com.google.api.grpc:proto-google-common-protos:2.59.2" @@ -47,13 +43,13 @@ google-auth-oauth2Http = "com.google.auth:google-auth-library-oauth2-http:1.24.1 google-cloud-logging = "com.google.cloud:google-cloud-logging:3.23.1" # 2.12.1 requires error_prone_annotations:2.36.0 but we are stuck with 2.30.0 gson = "com.google.code.gson:gson:2.11.0" -# 33.4.0 requires com.google.errorprone:error_prone_annotations:2.36.0 but we are stuck with 2.30.0 (see above) -guava = "com.google.guava:guava:33.3.1-android" +# 33.4.8 requires com.google.errorprone:error_prone_annotations:2.36.0 +guava = "com.google.guava:guava:33.4.8-android" guava-betaChecker = "com.google.guava:guava-beta-checker:1.0" -guava-testlib = "com.google.guava:guava-testlib:33.3.1-android" +guava-testlib = "com.google.guava:guava-testlib:33.4.8-android" # JRE version is needed for projects where its a transitive dependency, f.e. gcp-observability. # May be different from the -android version. -guava-jre = "com.google.guava:guava:33.3.1-jre" +guava-jre = "com.google.guava:guava:33.4.8-jre" hdrhistogram = "org.hdrhistogram:HdrHistogram:2.2.2" # 6.0.0+ use java.lang.Deprecated forRemoval and since from Java 9 jakarta-servlet-api = "jakarta.servlet:jakarta.servlet-api:5.0.0" diff --git a/repositories.bzl b/repositories.bzl index 47609ae7671..4b9d0327b66 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -19,9 +19,9 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.auto.value:auto-value:1.11.0", "com.google.code.findbugs:jsr305:3.0.2", "com.google.code.gson:gson:2.11.0", - "com.google.errorprone:error_prone_annotations:2.30.0", + "com.google.errorprone:error_prone_annotations:2.36.0", "com.google.guava:failureaccess:1.0.1", - "com.google.guava:guava:33.3.1-android", + "com.google.guava:guava:33.4.8-android", "com.google.re2j:re2j:1.8", "com.google.s2a.proto.v2:s2a-proto:0.1.2", "com.google.truth:truth:1.4.2", @@ -45,7 +45,7 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "io.opencensus:opencensus-contrib-grpc-metrics:0.31.0", "io.perfmark:perfmark-api:0.27.0", "junit:junit:4.13.2", - "org.checkerframework:checker-qual:3.12.0", + "org.checkerframework:checker-qual:3.49.5", "org.codehaus.mojo:animal-sniffer-annotations:1.24", ] # GRPC_DEPS_END diff --git a/s2a/build.gradle b/s2a/build.gradle index 1e48e2bb297..012411c19ba 100644 --- a/s2a/build.gradle +++ b/s2a/build.gradle @@ -12,6 +12,7 @@ description = "gRPC: S2A" dependencies { implementation libraries.s2a.proto + implementation 'org.checkerframework:checker-qual:3.49.5' api project(':grpc-api') implementation project(':grpc-stub'), diff --git a/settings.gradle b/settings.gradle index 22a49f0c3be..f4df1105090 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,17 @@ pluginManagement { + // https://issuetracker.google.com/issues/342522142#comment8 + // use D8/R8 8.0.44 or 8.1.44 with AGP 7.4 if needed. + buildscript { + repositories { + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + classpath("com.android.tools:r8:8.1.44") + } + } plugins { // https://developer.android.com/build/releases/gradle-plugin // 8+ has many changes: https://github.com/grpc/grpc-java/issues/10152 From c208ab3cfdeb5799db4e1ba39bba42a704240d47 Mon Sep 17 00:00:00 2001 From: jdcormie Date: Sat, 30 Aug 2025 00:03:33 +0100 Subject: [PATCH 08/22] factor out the decorate() and wrap() calls common to all handshakes --- .../internal/BinderClientTransport.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java b/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java index d77e16ed5ce..d4f384a1fe4 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java @@ -150,7 +150,9 @@ void releaseExecutors() { @Override public synchronized void onBound(IBinder binder) { - handshake.onBound(binder); + OneWayBinderProxy binderProxy = OneWayBinderProxy.wrap(binder, offloadExecutor); + binderProxy = binderDecorator.decorate(binderProxy); + handshake.onBound(binderProxy); } @Override @@ -342,7 +344,8 @@ protected void handleSetupTransport(Parcel parcel) { shutdownInternal( Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true); } else { - handshake.handleSetupTransport(binder); + OneWayBinderProxy binderProxy = OneWayBinderProxy.wrap(binder, offloadExecutor); + handshake.handleSetupTransport(binderProxy); } } } @@ -357,15 +360,14 @@ class LegacyClientHandshake implements ClientHandshake { @Override @MainThread @GuardedBy("BinderClientTransport.this") - public void onBound(IBinder binder) { - sendSetupTransaction( - binderDecorator.decorate(OneWayBinderProxy.wrap(binder, offloadExecutor))); + public void onBound(OneWayBinderProxy binder) { + sendSetupTransaction(binder); } @Override @BinderThread @GuardedBy("BinderClientTransport.this") - public void handleSetupTransport(IBinder binder) { + public void handleSetupTransport(OneWayBinderProxy binder) { int remoteUid = Binder.getCallingUid(); attributes = setSecurityAttrs(attributes, remoteUid); authResultFuture = checkServerAuthorizationAsync(remoteUid); @@ -388,11 +390,11 @@ public void onFailure(Throwable t) { } @GuardedBy("BinderClientTransport.this") - private void handleAuthResult(IBinder binder, Status authorization) { + private void handleAuthResult(OneWayBinderProxy binder, Status authorization) { if (inState(TransportState.SETUP)) { if (!authorization.isOk()) { shutdownInternal(authorization, true); - } else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) { + } else if (!setOutgoingBinder(binder)) { shutdownInternal( Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true); } else { @@ -438,7 +440,7 @@ interface ClientHandshake { */ @GuardedBy("this") @MainThread - void onBound(IBinder endpointBinder); + void onBound(OneWayBinderProxy endpointBinder); /** * Notifies the implementation that we've received a valid SETUP_TRANSPORT transaction from a @@ -446,7 +448,7 @@ interface ClientHandshake { */ @GuardedBy("this") @BinderThread - void handleSetupTransport(IBinder serverBinder); + void handleSetupTransport(OneWayBinderProxy serverBinder); } private static ClientStream newFailingClientStream( From 9746bb4bc3dd4c71c4992c35b1e2b93db70f6fcd Mon Sep 17 00:00:00 2001 From: MV Shiva Date: Mon, 1 Sep 2025 13:10:09 +0530 Subject: [PATCH 09/22] allow java21 in jre matrix (#12281) --- .github/workflows/testing.yml | 2 +- .../main/java/io/grpc/StatusException.java | 2 + .../java/io/grpc/StatusRuntimeException.java | 2 + .../grpc/internal/AbstractClientStream.java | 3 +- .../grpc/internal/AbstractServerStream.java | 3 +- .../java/io/grpc/internal/AbstractStream.java | 5 +- .../main/java/io/grpc/internal/JsonUtil.java | 6 +- .../AnonymousInProcessSocketAddress.java | 7 +++ .../netty/GrpcHttp2ConnectionHandler.java | 1 + .../java/io/grpc/servlet/GrpcServlet.java | 1 + .../io/grpc/util/MultiChildLoadBalancer.java | 2 + .../grpc/xds/GrpcXdsClientImplTestBase.java | 63 +++++++++++-------- 12 files changed, 63 insertions(+), 34 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0f099cbcac7..4fe75b0be78 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - jre: [8, 11, 17] + jre: [8, 11, 17, 21] fail-fast: false # Should swap to true if we grow a large matrix steps: diff --git a/api/src/main/java/io/grpc/StatusException.java b/api/src/main/java/io/grpc/StatusException.java index f9416bf72e3..2a235c3aaaf 100644 --- a/api/src/main/java/io/grpc/StatusException.java +++ b/api/src/main/java/io/grpc/StatusException.java @@ -25,7 +25,9 @@ */ public class StatusException extends Exception { private static final long serialVersionUID = -660954903976144640L; + @SuppressWarnings("serial") // https://github.com/grpc/grpc-java/issues/1913 private final Status status; + @SuppressWarnings("serial") private final Metadata trailers; /** diff --git a/api/src/main/java/io/grpc/StatusRuntimeException.java b/api/src/main/java/io/grpc/StatusRuntimeException.java index dd22d6b2486..ebcc2f0d671 100644 --- a/api/src/main/java/io/grpc/StatusRuntimeException.java +++ b/api/src/main/java/io/grpc/StatusRuntimeException.java @@ -26,7 +26,9 @@ public class StatusRuntimeException extends RuntimeException { private static final long serialVersionUID = 1950934672280720624L; + @SuppressWarnings("serial") // https://github.com/grpc/grpc-java/issues/1913 private final Status status; + @SuppressWarnings("serial") private final Metadata trailers; /** diff --git a/core/src/main/java/io/grpc/internal/AbstractClientStream.java b/core/src/main/java/io/grpc/internal/AbstractClientStream.java index 9718f8c5171..14fd5888147 100644 --- a/core/src/main/java/io/grpc/internal/AbstractClientStream.java +++ b/core/src/main/java/io/grpc/internal/AbstractClientStream.java @@ -101,6 +101,7 @@ void writeFrame( */ private volatile boolean cancelled; + @SuppressWarnings("this-escape") protected AbstractClientStream( WritableBufferAllocator bufferAllocator, StatsTraceContext statsTraceCtx, @@ -113,7 +114,7 @@ protected AbstractClientStream( this.shouldBeCountedForInUse = GrpcUtil.shouldBeCountedForInUse(callOptions); this.useGet = useGet; if (!useGet) { - framer = new MessageFramer(this, bufferAllocator, statsTraceCtx); + this.framer = new MessageFramer(this, bufferAllocator, statsTraceCtx); this.headers = headers; } else { framer = new GetFramer(headers, statsTraceCtx); diff --git a/core/src/main/java/io/grpc/internal/AbstractServerStream.java b/core/src/main/java/io/grpc/internal/AbstractServerStream.java index a535330f4b1..c468cba978a 100644 --- a/core/src/main/java/io/grpc/internal/AbstractServerStream.java +++ b/core/src/main/java/io/grpc/internal/AbstractServerStream.java @@ -75,10 +75,11 @@ protected interface Sink { private boolean outboundClosed; private boolean headersSent; + @SuppressWarnings("this-escape") protected AbstractServerStream( WritableBufferAllocator bufferAllocator, StatsTraceContext statsTraceCtx) { this.statsTraceCtx = Preconditions.checkNotNull(statsTraceCtx, "statsTraceCtx"); - framer = new MessageFramer(this, bufferAllocator, statsTraceCtx); + this.framer = new MessageFramer(this, bufferAllocator, statsTraceCtx); } @Override diff --git a/core/src/main/java/io/grpc/internal/AbstractStream.java b/core/src/main/java/io/grpc/internal/AbstractStream.java index 46cdab7ef28..9f5fb035dab 100644 --- a/core/src/main/java/io/grpc/internal/AbstractStream.java +++ b/core/src/main/java/io/grpc/internal/AbstractStream.java @@ -163,20 +163,21 @@ public abstract static class TransportState @GuardedBy("onReadyLock") private int onReadyThreshold; + @SuppressWarnings("this-escape") protected TransportState( int maxMessageSize, StatsTraceContext statsTraceCtx, TransportTracer transportTracer) { this.statsTraceCtx = checkNotNull(statsTraceCtx, "statsTraceCtx"); this.transportTracer = checkNotNull(transportTracer, "transportTracer"); - rawDeframer = new MessageDeframer( + this.rawDeframer = new MessageDeframer( this, Codec.Identity.NONE, maxMessageSize, statsTraceCtx, transportTracer); // TODO(#7168): use MigratingThreadDeframer when enabling retry doesn't break. - deframer = rawDeframer; + deframer = this.rawDeframer; onReadyThreshold = DEFAULT_ONREADY_THRESHOLD; } diff --git a/core/src/main/java/io/grpc/internal/JsonUtil.java b/core/src/main/java/io/grpc/internal/JsonUtil.java index 44cb22abda5..a0d5eef8660 100644 --- a/core/src/main/java/io/grpc/internal/JsonUtil.java +++ b/core/src/main/java/io/grpc/internal/JsonUtil.java @@ -356,7 +356,7 @@ private static int parseNanos(String value) throws ParseException { return result; } - private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1); + private static final int NANOS_PER_SECOND = 1_000_000_000; /** * Copy of {@link com.google.protobuf.util.Durations#normalizedDuration}. @@ -368,11 +368,11 @@ private static long normalizedDuration(long seconds, int nanos) { nanos %= NANOS_PER_SECOND; } if (seconds > 0 && nanos < 0) { - nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) + nanos += NANOS_PER_SECOND; // no overflow— nanos is negative (and we're adding) seconds--; // no overflow since seconds is positive (and we're decrementing) } if (seconds < 0 && nanos > 0) { - nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting) + nanos -= NANOS_PER_SECOND; // no overflow— nanos is positive (and we're subtracting) seconds++; // no overflow since seconds is negative (and we're incrementing) } if (!durationIsValid(seconds, nanos)) { diff --git a/inprocess/src/main/java/io/grpc/inprocess/AnonymousInProcessSocketAddress.java b/inprocess/src/main/java/io/grpc/inprocess/AnonymousInProcessSocketAddress.java index 089a9f12b02..c458857d70b 100644 --- a/inprocess/src/main/java/io/grpc/inprocess/AnonymousInProcessSocketAddress.java +++ b/inprocess/src/main/java/io/grpc/inprocess/AnonymousInProcessSocketAddress.java @@ -21,6 +21,8 @@ import com.google.errorprone.annotations.concurrent.GuardedBy; import io.grpc.ExperimentalApi; import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectOutputStream; import java.net.SocketAddress; import javax.annotation.Nullable; @@ -34,8 +36,13 @@ public final class AnonymousInProcessSocketAddress extends SocketAddress { @Nullable @GuardedBy("this") + @SuppressWarnings("serial") private InProcessServer server; + private void writeObject(ObjectOutputStream out) throws IOException { + throw new NotSerializableException("AnonymousInProcessSocketAddress is not serializable"); + } + /** Creates a new AnonymousInProcessSocketAddress. */ public AnonymousInProcessSocketAddress() { } diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java index 3b8c595a12e..a463cf01d95 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java @@ -68,6 +68,7 @@ public abstract class GrpcHttp2ConnectionHandler extends Http2ConnectionHandler usingPre4_1_111_Netty = identifiedOldVersion; } + @SuppressWarnings("this-escape") protected GrpcHttp2ConnectionHandler( ChannelPromise channelUnused, Http2ConnectionDecoder decoder, diff --git a/servlet/src/main/java/io/grpc/servlet/GrpcServlet.java b/servlet/src/main/java/io/grpc/servlet/GrpcServlet.java index f68ed083506..8c1eb858ad1 100644 --- a/servlet/src/main/java/io/grpc/servlet/GrpcServlet.java +++ b/servlet/src/main/java/io/grpc/servlet/GrpcServlet.java @@ -37,6 +37,7 @@ public class GrpcServlet extends HttpServlet { private static final long serialVersionUID = 1L; + @SuppressWarnings("serial") private final ServletAdapter servletAdapter; GrpcServlet(ServletAdapter servletAdapter) { diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 2a93ef964f7..acc186e3be6 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -268,6 +268,8 @@ public class ChildLbState { private ConnectivityState currentState; private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult()); + @SuppressWarnings("this-escape") + // TODO(okshiva): Fix 'this-escape' from the constructor before making the API public. public ChildLbState(Object key, LoadBalancer.Factory policyFactory) { this.key = key; this.lb = policyFactory.newLoadBalancer(createChildHelper()); diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java index 19266b0d289..9ff19c6d1b0 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java @@ -220,7 +220,7 @@ public boolean shouldAccept(Runnable command) { protected final Queue loadReportCalls = new ArrayDeque<>(); protected final AtomicBoolean adsEnded = new AtomicBoolean(true); protected final AtomicBoolean lrsEnded = new AtomicBoolean(true); - private final MessageFactory mf = createMessageFactory(); + protected MessageFactory mf; private static final long TIME_INCREMENT = TimeUnit.SECONDS.toNanos(1); /** Fake time provider increments time TIME_INCREMENT each call. */ @@ -234,37 +234,22 @@ public long currentTimeNanos() { private static final int VHOST_SIZE = 2; // LDS test resources. - private final Any testListenerVhosts = Any.pack(mf.buildListenerWithApiListener(LDS_RESOURCE, - mf.buildRouteConfiguration("do not care", mf.buildOpaqueVirtualHosts(VHOST_SIZE)))); - private final Any testListenerRds = - Any.pack(mf.buildListenerWithApiListenerForRds(LDS_RESOURCE, RDS_RESOURCE)); + private Any testListenerVhosts; + private Any testListenerRds; // RDS test resources. - private final Any testRouteConfig = - Any.pack(mf.buildRouteConfiguration(RDS_RESOURCE, mf.buildOpaqueVirtualHosts(VHOST_SIZE))); + private Any testRouteConfig; // CDS test resources. - private final Any testClusterRoundRobin = - Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, - null, false, null, "envoy.transport_sockets.tls", null, null - )); + private Any testClusterRoundRobin; // EDS test resources. - private final Message lbEndpointHealthy = - mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", - mf.buildLbEndpoint("192.168.0.1", 8080, "healthy", 2, "endpoint-host-name"), 1, 0); + private Message lbEndpointHealthy; // Locality with 0 endpoints - private final Message lbEndpointEmpty = - mf.buildLocalityLbEndpoints("region3", "zone3", "subzone3", - ImmutableList.of(), 2, 1); + private Message lbEndpointEmpty; // Locality with 0-weight endpoint - private final Message lbEndpointZeroWeight = - mf.buildLocalityLbEndpoints("region4", "zone4", "subzone4", - mf.buildLbEndpoint("192.168.142.5", 80, "unknown", 5, "endpoint-host-name"), 0, 2); - private final Any testClusterLoadAssignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, - ImmutableList.of(lbEndpointHealthy, lbEndpointEmpty, lbEndpointZeroWeight), - ImmutableList.of(mf.buildDropOverload("lb", 200), mf.buildDropOverload("throttle", 1000)))); - + private Message lbEndpointZeroWeight; + private Any testClusterLoadAssignment; @Captor private ArgumentCaptor ldsUpdateCaptor; @Captor @@ -304,8 +289,8 @@ public long currentTimeNanos() { private boolean originalEnableLeastRequest; private Server xdsServer; private final String serverName = InProcessServerBuilder.generateName(); - private final BindableService adsService = createAdsService(); - private final BindableService lrsService = createLrsService(); + private BindableService adsService; + private BindableService lrsService; private XdsTransportFactory xdsTransportFactory = new XdsTransportFactory() { @Override @@ -333,6 +318,32 @@ public XdsTransport create(ServerInfo serverInfo) { @Before public void setUp() throws IOException { + mf = createMessageFactory(); + testListenerVhosts = Any.pack(mf.buildListenerWithApiListener(LDS_RESOURCE, + mf.buildRouteConfiguration("do not care", mf.buildOpaqueVirtualHosts(VHOST_SIZE)))); + testListenerRds = + Any.pack(mf.buildListenerWithApiListenerForRds(LDS_RESOURCE, RDS_RESOURCE)); + testRouteConfig = + Any.pack(mf.buildRouteConfiguration(RDS_RESOURCE, mf.buildOpaqueVirtualHosts(VHOST_SIZE))); + testClusterRoundRobin = + Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, + null, false, null, "envoy.transport_sockets.tls", null, null + )); + lbEndpointHealthy = + mf.buildLocalityLbEndpoints("region1", "zone1", "subzone1", + mf.buildLbEndpoint("192.168.0.1", 8080, "healthy", 2, "endpoint-host-name"), 1, 0); + lbEndpointEmpty = + mf.buildLocalityLbEndpoints("region3", "zone3", "subzone3", + ImmutableList.of(), 2, 1); + lbEndpointZeroWeight = + mf.buildLocalityLbEndpoints("region4", "zone4", "subzone4", + mf.buildLbEndpoint("192.168.142.5", 80, "unknown", 5, "endpoint-host-name"), 0, 2); + testClusterLoadAssignment = Any.pack(mf.buildClusterLoadAssignment(EDS_RESOURCE, + ImmutableList.of(lbEndpointHealthy, lbEndpointEmpty, lbEndpointZeroWeight), + ImmutableList.of(mf.buildDropOverload("lb", 200), mf.buildDropOverload("throttle", 1000)))); + adsService = createAdsService(); + lrsService = createLrsService(); + when(backoffPolicyProvider.get()).thenReturn(backoffPolicy1, backoffPolicy2); when(backoffPolicy1.nextBackoffNanos()).thenReturn(10L, 100L); when(backoffPolicy2.nextBackoffNanos()).thenReturn(20L, 200L); From b3390227ae0c4e1c01b89d601f8233692f33324f Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 2 Sep 2025 15:12:17 -0700 Subject: [PATCH 10/22] Remove org.apache.tomcat:annotations-api dep f8700a1 stopped using the dependency in our generated code and removed the dependency the Bazel build. 4f6948f removed mention of the dependency in our README. This deletes it from our Gradle build and the examples. --- alts/build.gradle | 1 - android-interop-testing/build.gradle | 2 -- authz/build.gradle | 1 - benchmarks/build.gradle | 1 - compiler/build.gradle | 6 ++---- examples/android/clientcache/app/build.gradle | 1 - examples/android/helloworld/app/build.gradle | 1 - examples/android/routeguide/app/build.gradle | 1 - examples/android/strictmode/app/build.gradle | 1 - examples/build.gradle | 1 - examples/example-alts/build.gradle | 1 - examples/example-debug/build.gradle | 1 - examples/example-debug/pom.xml | 6 ------ examples/example-dualstack/build.gradle | 1 - examples/example-dualstack/pom.xml | 6 ------ examples/example-gauth/build.gradle | 1 - examples/example-gauth/pom.xml | 6 ------ examples/example-gcp-csm-observability/build.gradle | 1 - examples/example-gcp-observability/build.gradle | 1 - examples/example-hostname/build.gradle | 1 - examples/example-hostname/pom.xml | 6 ------ examples/example-jwt-auth/build.gradle | 2 -- examples/example-jwt-auth/pom.xml | 6 ------ examples/example-oauth/build.gradle | 2 -- examples/example-oauth/pom.xml | 6 ------ examples/example-opentelemetry/build.gradle | 1 - examples/example-orca/build.gradle | 2 -- examples/example-reflection/build.gradle | 2 -- examples/example-servlet/build.gradle | 3 +-- examples/example-tls/build.gradle | 1 - examples/example-tls/pom.xml | 6 ------ examples/example-xds/build.gradle | 1 - examples/pom.xml | 6 ------ gradle/libs.versions.toml | 3 --- grpclb/build.gradle | 1 - interop-testing/build.gradle | 1 - istio-interop-testing/build.gradle | 2 -- rls/build.gradle | 1 - s2a/build.gradle | 1 - services/build.gradle | 2 -- servlet/build.gradle | 3 +-- servlet/jakarta/build.gradle | 3 +-- testing-proto/build.gradle | 2 -- xds/build.gradle | 1 - 44 files changed, 5 insertions(+), 100 deletions(-) diff --git a/alts/build.gradle b/alts/build.gradle index 3e472d9cea6..fe2e27784fc 100644 --- a/alts/build.gradle +++ b/alts/build.gradle @@ -22,7 +22,6 @@ dependencies { libraries.guava.jre, // JRE required by protobuf-java-util from grpclb libraries.google.auth.oauth2Http def nettyDependency = implementation project(':grpc-netty') - compileOnly libraries.javax.annotation shadow configurations.implementation.getDependencies().minus(nettyDependency) shadow project(path: ':grpc-netty-shaded', configuration: 'shadow') diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle index 72b6ac0a302..17551465f05 100644 --- a/android-interop-testing/build.gradle +++ b/android-interop-testing/build.gradle @@ -83,8 +83,6 @@ dependencies { exclude group: 'com.google.guava' } - compileOnly libraries.javax.annotation - androidTestImplementation 'androidx.test.ext:junit:1.1.3', 'androidx.test:runner:1.4.0' } diff --git a/authz/build.gradle b/authz/build.gradle index b72088bfbaa..4b02b01aa29 100644 --- a/authz/build.gradle +++ b/authz/build.gradle @@ -15,7 +15,6 @@ dependencies { libraries.guava.jre // JRE required by transitive protobuf-java-util annotationProcessor libraries.auto.value - compileOnly libraries.javax.annotation testImplementation project(':grpc-testing'), project(':grpc-testing-proto'), diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index bf043106050..88b26397e78 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -38,7 +38,6 @@ dependencies { classifier = "linux-x86_64" } } - compileOnly libraries.javax.annotation testImplementation libraries.junit, libraries.mockito.core diff --git a/compiler/build.gradle b/compiler/build.gradle index 6d832ecd56b..dbecb889a43 100644 --- a/compiler/build.gradle +++ b/compiler/build.gradle @@ -147,11 +147,9 @@ sourceSets { dependencies { testImplementation project(':grpc-protobuf'), - project(':grpc-stub'), - libraries.javax.annotation + project(':grpc-stub') testLiteImplementation project(':grpc-protobuf-lite'), - project(':grpc-stub'), - libraries.javax.annotation + project(':grpc-stub') } tasks.named("compileTestJava").configure { diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index a9716ea1f62..8048eba73ab 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -57,7 +57,6 @@ dependencies { implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.13.2' testImplementation 'com.google.truth:truth:1.1.5' diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index 2fd2d2fa950..69519c8e4ab 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -55,5 +55,4 @@ dependencies { implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // 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 371e277bdc6..a09de35e994 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -55,5 +55,4 @@ dependencies { implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // 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 02327041d34..c0447a42af1 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -56,5 +56,4 @@ dependencies { implementation 'io.grpc:grpc-okhttp:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'io.grpc:grpc-protobuf-lite:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'io.grpc:grpc-stub:1.76.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index 507b87df4db..ddd8e7b3a65 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -29,7 +29,6 @@ dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-services:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" // examples/advanced need this for JsonFormat implementation "com.google.protobuf:protobuf-java-util:${protobufVersion}" diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 33ea02a5875..9f86adb0aeb 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -27,7 +27,6 @@ def protocVersion = '3.25.8' dependencies { // grpc-alts transitively depends on grpc-netty-shaded, grpc-protobuf, and grpc-stub implementation "io.grpc:grpc-alts:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" } protobuf { diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle index ed01bbb2636..3dc873e179b 100644 --- a/examples/example-debug/build.gradle +++ b/examples/example-debug/build.gradle @@ -30,7 +30,6 @@ dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-services:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" testImplementation 'junit:junit:4.13.2' diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml index 90ce766d8a9..c4fce2d793b 100644 --- a/examples/example-debug/pom.xml +++ b/examples/example-debug/pom.xml @@ -44,12 +44,6 @@ io.grpc grpc-stub - - org.apache.tomcat - annotations-api - 6.0.53 - provided - io.grpc grpc-netty-shaded diff --git a/examples/example-dualstack/build.gradle b/examples/example-dualstack/build.gradle index fa9db23f987..5d8c30b2127 100644 --- a/examples/example-dualstack/build.gradle +++ b/examples/example-dualstack/build.gradle @@ -31,7 +31,6 @@ dependencies { implementation "io.grpc:grpc-netty:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-services:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" } protobuf { diff --git a/examples/example-dualstack/pom.xml b/examples/example-dualstack/pom.xml index b70a3eca3d8..b65beb84103 100644 --- a/examples/example-dualstack/pom.xml +++ b/examples/example-dualstack/pom.xml @@ -48,12 +48,6 @@ io.grpc grpc-netty - - org.apache.tomcat - annotations-api - 6.0.53 - provided - io.grpc grpc-netty-shaded diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 52d945196fa..befc2f7c0c7 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -30,7 +30,6 @@ dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-auth:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" implementation "com.google.auth:google-auth-library-oauth2-http:1.23.0" implementation "com.google.api.grpc:grpc-google-cloud-pubsub-v1:0.1.24" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 68a98c526a5..98bbebd114a 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -49,12 +49,6 @@ io.grpc grpc-auth - - org.apache.tomcat - annotations-api - 6.0.53 - provided - io.grpc grpc-testing diff --git a/examples/example-gcp-csm-observability/build.gradle b/examples/example-gcp-csm-observability/build.gradle index 816ef9e6742..abb2dd8a220 100644 --- a/examples/example-gcp-csm-observability/build.gradle +++ b/examples/example-gcp-csm-observability/build.gradle @@ -35,7 +35,6 @@ dependencies { implementation "io.opentelemetry:opentelemetry-sdk:${openTelemetryVersion}" implementation "io.opentelemetry:opentelemetry-sdk-metrics:${openTelemetryVersion}" implementation "io.opentelemetry:opentelemetry-exporter-prometheus:${openTelemetryPrometheusVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-xds:${grpcVersion}" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" } diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle index 432dceb6730..08e00294452 100644 --- a/examples/example-gcp-observability/build.gradle +++ b/examples/example-gcp-observability/build.gradle @@ -29,7 +29,6 @@ dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-gcp-observability:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" } diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index 7345d873e4f..94a85e8f8ab 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -28,7 +28,6 @@ dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-services:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" testImplementation 'junit:junit:4.13.2' diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 00209657e1d..e93b36e39d1 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -44,12 +44,6 @@ io.grpc grpc-stub - - org.apache.tomcat - annotations-api - 6.0.53 - provided - io.grpc grpc-netty-shaded diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index b14040ad58f..28df68e4fc7 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -31,8 +31,6 @@ dependencies { implementation "io.jsonwebtoken:jjwt:0.9.1" implementation "javax.xml.bind:jaxb-api:2.3.1" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" - runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" testImplementation "io.grpc:grpc-testing:${grpcVersion}" diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index 8dce2a71032..4834cfa09ef 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -57,12 +57,6 @@ jaxb-api 2.3.1 - - org.apache.tomcat - annotations-api - 6.0.53 - provided - io.grpc grpc-testing diff --git a/examples/example-oauth/build.gradle b/examples/example-oauth/build.gradle index 521d8b082ce..05a450dcc8d 100644 --- a/examples/example-oauth/build.gradle +++ b/examples/example-oauth/build.gradle @@ -31,8 +31,6 @@ dependencies { implementation "io.grpc:grpc-auth:${grpcVersion}" implementation "com.google.auth:google-auth-library-oauth2-http:1.23.0" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" - runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" testImplementation "io.grpc:grpc-testing:${grpcVersion}" diff --git a/examples/example-oauth/pom.xml b/examples/example-oauth/pom.xml index 2907d062053..a2f61e38467 100644 --- a/examples/example-oauth/pom.xml +++ b/examples/example-oauth/pom.xml @@ -62,12 +62,6 @@ google-auth-library-oauth2-http 1.23.0 - - org.apache.tomcat - annotations-api - 6.0.53 - provided - io.grpc grpc-testing diff --git a/examples/example-opentelemetry/build.gradle b/examples/example-opentelemetry/build.gradle index 482f5766bee..edcca46480e 100644 --- a/examples/example-opentelemetry/build.gradle +++ b/examples/example-opentelemetry/build.gradle @@ -34,7 +34,6 @@ dependencies { implementation "io.opentelemetry:opentelemetry-sdk-metrics:${openTelemetryVersion}" implementation "io.opentelemetry:opentelemetry-exporter-logging:${openTelemetryVersion}" implementation "io.opentelemetry:opentelemetry-exporter-prometheus:${openTelemetryPrometheusVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" } diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index 2716adf7de1..f21c89ee0a4 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -24,8 +24,6 @@ dependencies { implementation "io.grpc:grpc-services:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-xds:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" - } protobuf { diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle index 2b48cb30b5f..a5bda680ef4 100644 --- a/examples/example-reflection/build.gradle +++ b/examples/example-reflection/build.gradle @@ -24,8 +24,6 @@ dependencies { implementation "io.grpc:grpc-services:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-netty-shaded:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" - } protobuf { diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle index bbdb65349ca..d0f475f1833 100644 --- a/examples/example-servlet/build.gradle +++ b/examples/example-servlet/build.gradle @@ -23,8 +23,7 @@ dependencies { "io.grpc:grpc-servlet:${grpcVersion}", "io.grpc:grpc-stub:${grpcVersion}" - compileOnly "javax.servlet:javax.servlet-api:4.0.1", - "org.apache.tomcat:annotations-api:6.0.53" + compileOnly "javax.servlet:javax.servlet-api:4.0.1" } protobuf { diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index aeb769a479b..0b3914febf3 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -27,7 +27,6 @@ def protocVersion = '3.25.8' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" } diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 575400c3608..3932f4c32f4 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -40,12 +40,6 @@ io.grpc grpc-stub - - org.apache.tomcat - annotations-api - 6.0.53 - provided - io.grpc grpc-netty-shaded diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 6c78db3513c..3914aa3fea0 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -29,7 +29,6 @@ dependencies { implementation "io.grpc:grpc-services:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" implementation "io.grpc:grpc-xds:${grpcVersion}" - compileOnly "org.apache.tomcat:annotations-api:6.0.53" runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" } diff --git a/examples/pom.xml b/examples/pom.xml index f81346c913c..4da1eb14f90 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -60,12 +60,6 @@ j2objc-annotations 3.0.0 - - org.apache.tomcat - annotations-api - 6.0.53 - provided - io.grpc grpc-testing diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e66f253fc21..cb5dce02843 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,9 +53,6 @@ guava-jre = "com.google.guava:guava:33.4.8-jre" hdrhistogram = "org.hdrhistogram:HdrHistogram:2.2.2" # 6.0.0+ use java.lang.Deprecated forRemoval and since from Java 9 jakarta-servlet-api = "jakarta.servlet:jakarta.servlet-api:5.0.0" -# Using javax.annotation is fine as it is part of the JDK, we don't want to depend on J2EE -# where it is relocated to as org.apache.tomcat:tomcat-annotations-api. See issue #9179. -javax-annotation = "org.apache.tomcat:annotations-api:6.0.53" javax-servlet-api = "javax.servlet:javax.servlet-api:4.0.1" # 12.0.0+ require Java 17+ jetty-client = "org.eclipse.jetty:jetty-client:11.0.24" diff --git a/grpclb/build.gradle b/grpclb/build.gradle index 3f67181372b..f543e0d71fc 100644 --- a/grpclb/build.gradle +++ b/grpclb/build.gradle @@ -23,7 +23,6 @@ dependencies { libraries.protobuf.java, libraries.protobuf.java.util runtimeOnly libraries.errorprone.annotations - compileOnly libraries.javax.annotation testImplementation libraries.truth, project(':grpc-inprocess'), testFixtures(project(':grpc-core')) diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle index 97e7c69533a..5160759460c 100644 --- a/interop-testing/build.gradle +++ b/interop-testing/build.gradle @@ -31,7 +31,6 @@ dependencies { project(':grpc-stub'), project(':grpc-protobuf'), libraries.junit - compileOnly libraries.javax.annotation // TODO(sergiitk): replace with com.google.cloud:google-cloud-logging // Used instead of google-cloud-logging because it's failing // due to a circular dependency on grpc. diff --git a/istio-interop-testing/build.gradle b/istio-interop-testing/build.gradle index 4550f0ea202..083d8fcb9bf 100644 --- a/istio-interop-testing/build.gradle +++ b/istio-interop-testing/build.gradle @@ -18,8 +18,6 @@ dependencies { project(':grpc-testing'), project(':grpc-xds') - compileOnly libraries.javax.annotation - runtimeOnly libraries.netty.tcnative, libraries.netty.tcnative.classes testImplementation testFixtures(project(':grpc-api')), diff --git a/rls/build.gradle b/rls/build.gradle index 1193ab3d4bc..10b1d5fc371 100644 --- a/rls/build.gradle +++ b/rls/build.gradle @@ -22,7 +22,6 @@ dependencies { libraries.auto.value.annotations, libraries.guava annotationProcessor libraries.auto.value - compileOnly libraries.javax.annotation testImplementation libraries.truth, project(':grpc-grpclb'), project(':grpc-inprocess'), diff --git a/s2a/build.gradle b/s2a/build.gradle index 012411c19ba..c46993ec9c8 100644 --- a/s2a/build.gradle +++ b/s2a/build.gradle @@ -21,7 +21,6 @@ dependencies { libraries.protobuf.java, libraries.guava.jre // JRE required by protobuf-java-util from grpclb def nettyDependency = implementation project(':grpc-netty') - compileOnly libraries.javax.annotation shadow configurations.implementation.getDependencies().minus(nettyDependency) shadow project(path: ':grpc-netty-shaded', configuration: 'shadow') diff --git a/services/build.gradle b/services/build.gradle index 758f2a5c899..c30e1ba53bd 100644 --- a/services/build.gradle +++ b/services/build.gradle @@ -32,13 +32,11 @@ dependencies { runtimeOnly libraries.errorprone.annotations, libraries.gson // to fix checkUpperBoundDeps error here - compileOnly libraries.javax.annotation testImplementation project(':grpc-testing'), project(':grpc-inprocess'), libraries.netty.transport.epoll, // for DomainSocketAddress testFixtures(project(':grpc-core')), testFixtures(project(':grpc-api')) - testCompileOnly libraries.javax.annotation signature (libraries.signature.java) { artifact { extension = "signature" diff --git a/servlet/build.gradle b/servlet/build.gradle index 7f9cd04a57c..1367a72ab44 100644 --- a/servlet/build.gradle +++ b/servlet/build.gradle @@ -34,8 +34,7 @@ tasks.named("jar").configure { dependencies { api project(':grpc-api') - compileOnly libraries.javax.servlet.api, - libraries.javax.annotation // java 9, 10 needs it + compileOnly libraries.javax.servlet.api implementation project(':grpc-core'), libraries.guava diff --git a/servlet/jakarta/build.gradle b/servlet/jakarta/build.gradle index 456b2b75e3e..bcd904ccaee 100644 --- a/servlet/jakarta/build.gradle +++ b/servlet/jakarta/build.gradle @@ -85,8 +85,7 @@ tasks.named("jar").configure { dependencies { api project(':grpc-api') - compileOnly libraries.jakarta.servlet.api, - libraries.javax.annotation + compileOnly libraries.jakarta.servlet.api implementation project(':grpc-util'), project(':grpc-core'), diff --git a/testing-proto/build.gradle b/testing-proto/build.gradle index a34392b26d2..ee602bc5135 100644 --- a/testing-proto/build.gradle +++ b/testing-proto/build.gradle @@ -17,9 +17,7 @@ tasks.named("jar").configure { dependencies { api project(':grpc-protobuf'), project(':grpc-stub') - compileOnly libraries.javax.annotation testImplementation libraries.truth - testRuntimeOnly libraries.javax.annotation signature (libraries.signature.java) { artifact { extension = "signature" diff --git a/xds/build.gradle b/xds/build.gradle index 72dea373097..8394fe12f6b 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -41,7 +41,6 @@ configurations { } dependencies { - thirdpartyCompileOnly libraries.javax.annotation thirdpartyImplementation project(':grpc-protobuf'), project(':grpc-stub') compileOnly sourceSets.thirdparty.output From 01f2862b90631c8114174b0735b24bbb11cbb060 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 28 Aug 2025 16:26:23 -0700 Subject: [PATCH 11/22] xds: Pretty-print Resource in logs Noticed at b/431017968#comment31 --- xds/src/main/java/io/grpc/xds/MessagePrinter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xds/src/main/java/io/grpc/xds/MessagePrinter.java b/xds/src/main/java/io/grpc/xds/MessagePrinter.java index db15e961204..d6fdaa81dd7 100644 --- a/xds/src/main/java/io/grpc/xds/MessagePrinter.java +++ b/xds/src/main/java/io/grpc/xds/MessagePrinter.java @@ -37,6 +37,7 @@ import io.envoyproxy.envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext; +import io.envoyproxy.envoy.service.discovery.v3.Resource; import io.grpc.xds.client.MessagePrettyPrinter; /** @@ -55,6 +56,7 @@ private static class LazyHolder { private static JsonFormat.Printer newPrinter() { TypeRegistry.Builder registry = TypeRegistry.newBuilder() + .add(Resource.getDescriptor()) .add(Listener.getDescriptor()) .add(HttpConnectionManager.getDescriptor()) .add(HTTPFault.getDescriptor()) From 5a543726fd47e42f7ac959eb55d77c5bf173238a Mon Sep 17 00:00:00 2001 From: Sangamesh Date: Thu, 4 Sep 2025 11:25:04 +0530 Subject: [PATCH 12/22] android-interop-testing : Fix lint warnings in android-interop and binder modules (#12272) Fixes #6868 --- .../src/main/AndroidManifest.xml | 7 ++++--- .../android/integrationtest/TesterActivity.java | 2 +- .../src/main/res/layout/activity_tester.xml | 1 + .../src/main/res/values/strings.xml | 1 + binder/src/main/AndroidManifest.xml | 13 +++++++++++-- lint.xml | 5 +++++ 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/android-interop-testing/src/main/AndroidManifest.xml b/android-interop-testing/src/main/AndroidManifest.xml index 35f3ee33a2b..da7ccef5b1d 100644 --- a/android-interop-testing/src/main/AndroidManifest.xml +++ b/android-interop-testing/src/main/AndroidManifest.xml @@ -5,7 +5,9 @@ - + + android:exported="true"> diff --git a/android-interop-testing/src/main/java/io/grpc/android/integrationtest/TesterActivity.java b/android-interop-testing/src/main/java/io/grpc/android/integrationtest/TesterActivity.java index fb5b35c42d5..17c7e24cbfa 100644 --- a/android-interop-testing/src/main/java/io/grpc/android/integrationtest/TesterActivity.java +++ b/android-interop-testing/src/main/java/io/grpc/android/integrationtest/TesterActivity.java @@ -121,7 +121,7 @@ private void startTest(String testCase) { ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow( hostEdit.getWindowToken(), 0); enableButtons(false); - resultText.setText("Testing..."); + resultText.setText(R.string.testing_message); String host = hostEdit.getText().toString(); String portStr = portEdit.getText().toString(); diff --git a/android-interop-testing/src/main/res/layout/activity_tester.xml b/android-interop-testing/src/main/res/layout/activity_tester.xml index e25bd1bb6f6..042da6437c0 100644 --- a/android-interop-testing/src/main/res/layout/activity_tester.xml +++ b/android-interop-testing/src/main/res/layout/activity_tester.xml @@ -16,6 +16,7 @@ android:layout_weight="2" android:layout_width="0dp" android:layout_height="wrap_content" + android:inputType="text" android:hint="Enter Host" /> gRPC Integration Test + Testing… diff --git a/binder/src/main/AndroidManifest.xml b/binder/src/main/AndroidManifest.xml index a30cbbdd6fa..239c3b39b38 100644 --- a/binder/src/main/AndroidManifest.xml +++ b/binder/src/main/AndroidManifest.xml @@ -1,2 +1,11 @@ - - + + + + + + + + + + + \ No newline at end of file diff --git a/lint.xml b/lint.xml index 93e2f603108..5b35a8d151b 100644 --- a/lint.xml +++ b/lint.xml @@ -5,4 +5,9 @@ Remove after AGP upgrade. --> + + + From d4e1b69e781d4132f971c97a1f0893d54cf761b7 Mon Sep 17 00:00:00 2001 From: Abhishek Agrawal <81427947+AgraVator@users.noreply.github.com> Date: Mon, 8 Sep 2025 21:55:40 +0000 Subject: [PATCH 13/22] otel: subchannel metrics A94 (#12202) Implements [A94](https://github.com/grpc/proposal/pull/485/files) except for the exact reason for disconnect_error --- .../java/io/grpc/EquivalentAddressGroup.java | 5 + .../LongUpDownCounterMetricInstrument.java | 32 +++ .../io/grpc/MetricInstrumentRegistry.java | 41 ++++ api/src/main/java/io/grpc/MetricRecorder.java | 25 ++- api/src/main/java/io/grpc/MetricSink.java | 18 +- .../io/grpc/internal/InternalSubchannel.java | 55 ++++- .../io/grpc/internal/ManagedChannelImpl.java | 9 +- .../io/grpc/internal/MetricRecorderImpl.java | 29 ++- .../internal/PickFirstLeafLoadBalancer.java | 2 +- .../io/grpc/internal/SubchannelMetrics.java | 189 ++++++++++++++++++ .../grpc/internal/InternalSubchannelTest.java | 154 +++++++++++++- .../grpc/internal/MetricRecorderImplTest.java | 33 ++- .../OpenTelemetryMetricSink.java | 23 +++ .../internal/OpenTelemetryConstants.java | 6 + .../OpenTelemetryMetricSinkTest.java | 68 ++++++- .../io/grpc/xds/ClusterImplLoadBalancer.java | 2 +- .../grpc/xds/ClusterResolverLoadBalancer.java | 4 +- .../io/grpc/xds/WrrLocalityLoadBalancer.java | 2 +- .../main/java/io/grpc/xds/XdsAttributes.java | 7 - .../grpc/xds/ClusterImplLoadBalancerTest.java | 2 +- .../grpc/xds/WrrLocalityLoadBalancerTest.java | 2 +- 21 files changed, 678 insertions(+), 30 deletions(-) create mode 100644 api/src/main/java/io/grpc/LongUpDownCounterMetricInstrument.java create mode 100644 core/src/main/java/io/grpc/internal/SubchannelMetrics.java diff --git a/api/src/main/java/io/grpc/EquivalentAddressGroup.java b/api/src/main/java/io/grpc/EquivalentAddressGroup.java index 4b3db006684..bf8a864902c 100644 --- a/api/src/main/java/io/grpc/EquivalentAddressGroup.java +++ b/api/src/main/java/io/grpc/EquivalentAddressGroup.java @@ -50,6 +50,11 @@ public final class EquivalentAddressGroup { @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6138") public static final Attributes.Key ATTR_AUTHORITY_OVERRIDE = Attributes.Key.create("io.grpc.EquivalentAddressGroup.ATTR_AUTHORITY_OVERRIDE"); + /** + * The name of the locality that this EquivalentAddressGroup is in. + */ + public static final Attributes.Key ATTR_LOCALITY_NAME = + Attributes.Key.create("io.grpc.EquivalentAddressGroup.LOCALITY"); private final List addrs; private final Attributes attrs; diff --git a/api/src/main/java/io/grpc/LongUpDownCounterMetricInstrument.java b/api/src/main/java/io/grpc/LongUpDownCounterMetricInstrument.java new file mode 100644 index 00000000000..07e099cde5d --- /dev/null +++ b/api/src/main/java/io/grpc/LongUpDownCounterMetricInstrument.java @@ -0,0 +1,32 @@ +/* + * Copyright 2025 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; + +import java.util.List; + +/** + * Represents a long-valued up down counter metric instrument. + */ +@Internal +public final class LongUpDownCounterMetricInstrument extends PartialMetricInstrument { + public LongUpDownCounterMetricInstrument(int index, String name, String description, String unit, + List requiredLabelKeys, + List optionalLabelKeys, + boolean enableByDefault) { + super(index, name, description, unit, requiredLabelKeys, optionalLabelKeys, enableByDefault); + } +} \ No newline at end of file diff --git a/api/src/main/java/io/grpc/MetricInstrumentRegistry.java b/api/src/main/java/io/grpc/MetricInstrumentRegistry.java index 1b33ed17a71..ce0f8f1b5cb 100644 --- a/api/src/main/java/io/grpc/MetricInstrumentRegistry.java +++ b/api/src/main/java/io/grpc/MetricInstrumentRegistry.java @@ -144,6 +144,47 @@ public LongCounterMetricInstrument registerLongCounter(String name, } } + /** + * Registers a new Long Up Down Counter metric instrument. + * + * @param name the name of the metric + * @param description a description of the metric + * @param unit the unit of measurement for the metric + * @param requiredLabelKeys a list of required label keys + * @param optionalLabelKeys a list of optional label keys + * @param enableByDefault whether the metric should be enabled by default + * @return the newly created LongUpDownCounterMetricInstrument + * @throws IllegalStateException if a metric with the same name already exists + */ + public LongUpDownCounterMetricInstrument registerLongUpDownCounter(String name, + String description, + String unit, + List requiredLabelKeys, + List optionalLabelKeys, + boolean enableByDefault) { + checkArgument(!Strings.isNullOrEmpty(name), "missing metric name"); + checkNotNull(description, "description"); + checkNotNull(unit, "unit"); + checkNotNull(requiredLabelKeys, "requiredLabelKeys"); + checkNotNull(optionalLabelKeys, "optionalLabelKeys"); + synchronized (lock) { + if (registeredMetricNames.contains(name)) { + throw new IllegalStateException("Metric with name " + name + " already exists"); + } + int index = nextAvailableMetricIndex; + if (index + 1 == metricInstruments.length) { + resizeMetricInstruments(); + } + LongUpDownCounterMetricInstrument instrument = new LongUpDownCounterMetricInstrument( + index, name, description, unit, requiredLabelKeys, optionalLabelKeys, + enableByDefault); + metricInstruments[index] = instrument; + registeredMetricNames.add(name); + nextAvailableMetricIndex += 1; + return instrument; + } + } + /** * Registers a new Double Histogram metric instrument. * diff --git a/api/src/main/java/io/grpc/MetricRecorder.java b/api/src/main/java/io/grpc/MetricRecorder.java index d418dcbf590..897c28011cd 100644 --- a/api/src/main/java/io/grpc/MetricRecorder.java +++ b/api/src/main/java/io/grpc/MetricRecorder.java @@ -50,7 +50,7 @@ default void addDoubleCounter(DoubleCounterMetricInstrument metricInstrument, do * Adds a value for a long valued counter metric instrument. * * @param metricInstrument The counter metric instrument to add the value against. - * @param value The value to add. + * @param value The value to add. MUST be non-negative. * @param requiredLabelValues A list of required label values for the metric. * @param optionalLabelValues A list of additional, optional label values for the metric. */ @@ -66,6 +66,29 @@ default void addLongCounter(LongCounterMetricInstrument metricInstrument, long v metricInstrument.getOptionalLabelKeys().size()); } + /** + * Adds a value for a long valued up down counter metric instrument. + * + * @param metricInstrument The counter metric instrument to add the value against. + * @param value The value to add. May be positive, negative or zero. + * @param requiredLabelValues A list of required label values for the metric. + * @param optionalLabelValues A list of additional, optional label values for the metric. + */ + default void addLongUpDownCounter(LongUpDownCounterMetricInstrument metricInstrument, + long value, + List requiredLabelValues, + List optionalLabelValues) { + checkArgument(requiredLabelValues != null + && requiredLabelValues.size() == metricInstrument.getRequiredLabelKeys().size(), + "Incorrect number of required labels provided. Expected: %s", + metricInstrument.getRequiredLabelKeys().size()); + checkArgument(optionalLabelValues != null + && optionalLabelValues.size() == metricInstrument.getOptionalLabelKeys().size(), + "Incorrect number of optional labels provided. Expected: %s", + metricInstrument.getOptionalLabelKeys().size()); + } + + /** * Records a value for a double-precision histogram metric instrument. * diff --git a/api/src/main/java/io/grpc/MetricSink.java b/api/src/main/java/io/grpc/MetricSink.java index 0f56b1acb73..ce5d3822520 100644 --- a/api/src/main/java/io/grpc/MetricSink.java +++ b/api/src/main/java/io/grpc/MetricSink.java @@ -65,12 +65,26 @@ default void addDoubleCounter(DoubleCounterMetricInstrument metricInstrument, do * Adds a value for a long valued counter metric associated with specified metric instrument. * * @param metricInstrument The counter metric instrument identifies metric measure to add. - * @param value The value to record. + * @param value The value to record. MUST be non-negative. * @param requiredLabelValues A list of required label values for the metric. * @param optionalLabelValues A list of additional, optional label values for the metric. */ default void addLongCounter(LongCounterMetricInstrument metricInstrument, long value, - List requiredLabelValues, List optionalLabelValues) { + List requiredLabelValues, List optionalLabelValues) { + } + + /** + * Adds a value for a long valued up down counter metric associated with specified metric + * instrument. + * + * @param metricInstrument The counter metric instrument identifies metric measure to add. + * @param value The value to record. May be positive, negative or zero. + * @param requiredLabelValues A list of required label values for the metric. + * @param optionalLabelValues A list of additional, optional label values for the metric. + */ + default void addLongUpDownCounter(LongUpDownCounterMetricInstrument metricInstrument, long value, + List requiredLabelValues, + List optionalLabelValues) { } /** diff --git a/core/src/main/java/io/grpc/internal/InternalSubchannel.java b/core/src/main/java/io/grpc/internal/InternalSubchannel.java index a27e46eaf60..649843c5c03 100644 --- a/core/src/main/java/io/grpc/internal/InternalSubchannel.java +++ b/core/src/main/java/io/grpc/internal/InternalSubchannel.java @@ -48,6 +48,9 @@ import io.grpc.LoadBalancer; import io.grpc.Metadata; import io.grpc.MethodDescriptor; +import io.grpc.MetricRecorder; +import io.grpc.NameResolver; +import io.grpc.SecurityLevel; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; @@ -160,6 +163,8 @@ protected void handleNotInUse() { private Status shutdownReason; private volatile Attributes connectedAddressAttributes; + private final SubchannelMetrics subchannelMetrics; + private final String target; InternalSubchannel(LoadBalancer.CreateSubchannelArgs args, String authority, String userAgent, BackoffPolicy.Provider backoffPolicyProvider, @@ -168,7 +173,9 @@ protected void handleNotInUse() { Supplier stopwatchSupplier, SynchronizationContext syncContext, Callback callback, InternalChannelz channelz, CallTracer callsTracer, ChannelTracer channelTracer, InternalLogId logId, - ChannelLogger channelLogger, List transportFilters) { + ChannelLogger channelLogger, List transportFilters, + String target, + MetricRecorder metricRecorder) { List addressGroups = args.getAddresses(); Preconditions.checkNotNull(addressGroups, "addressGroups"); Preconditions.checkArgument(!addressGroups.isEmpty(), "addressGroups is empty"); @@ -192,6 +199,8 @@ protected void handleNotInUse() { this.channelLogger = Preconditions.checkNotNull(channelLogger, "channelLogger"); this.transportFilters = transportFilters; this.reconnectDisabled = args.getOption(LoadBalancer.DISABLE_SUBCHANNEL_RECONNECT_KEY); + this.target = target; + this.subchannelMetrics = new SubchannelMetrics(metricRecorder); } ChannelLogger getChannelLogger() { @@ -593,6 +602,13 @@ public void run() { pendingTransport = null; connectedAddressAttributes = addressIndex.getCurrentEagAttributes(); gotoNonErrorState(READY); + subchannelMetrics.recordConnectionAttemptSucceeded(/* target= */ target, + /* backendService= */ getAttributeOrDefault( + addressIndex.getCurrentEagAttributes(), NameResolver.ATTR_BACKEND_SERVICE), + /* locality= */ getAttributeOrDefault(addressIndex.getCurrentEagAttributes(), + EquivalentAddressGroup.ATTR_LOCALITY_NAME), + /* securityLevel= */ extractSecurityLevel(addressIndex.getCurrentEagAttributes() + .get(GrpcAttributes.ATTR_SECURITY_LEVEL))); } } }); @@ -618,11 +634,25 @@ public void run() { activeTransport = null; addressIndex.reset(); gotoNonErrorState(IDLE); + subchannelMetrics.recordDisconnection(/* target= */ target, + /* backendService= */ getAttributeOrDefault(addressIndex.getCurrentEagAttributes(), + NameResolver.ATTR_BACKEND_SERVICE), + /* locality= */ getAttributeOrDefault(addressIndex.getCurrentEagAttributes(), + EquivalentAddressGroup.ATTR_LOCALITY_NAME), + /* disconnectError= */ SubchannelMetrics.DisconnectError.UNKNOWN + .getErrorString(null), + /* securityLevel= */ extractSecurityLevel(addressIndex.getCurrentEagAttributes() + .get(GrpcAttributes.ATTR_SECURITY_LEVEL))); } else if (pendingTransport == transport) { + subchannelMetrics.recordConnectionAttemptFailed(/* target= */ target, + /* backendService= */getAttributeOrDefault(addressIndex.getCurrentEagAttributes(), + NameResolver.ATTR_BACKEND_SERVICE), + /* locality= */ getAttributeOrDefault(addressIndex.getCurrentEagAttributes(), + EquivalentAddressGroup.ATTR_LOCALITY_NAME)); Preconditions.checkState(state.getState() == CONNECTING, "Expected state is CONNECTING, actual state is %s", state.getState()); addressIndex.increment(); - // Continue reconnect if there are still addresses to try. + // Continue to reconnect if there are still addresses to try. if (!addressIndex.isValid()) { pendingTransport = null; addressIndex.reset(); @@ -658,6 +688,27 @@ public void run() { } }); } + + private String extractSecurityLevel(SecurityLevel securityLevel) { + if (securityLevel == null) { + return "none"; + } + switch (securityLevel) { + case NONE: + return "none"; + case INTEGRITY: + return "integrity_only"; + case PRIVACY_AND_INTEGRITY: + return "privacy_and_integrity"; + default: + throw new IllegalArgumentException("Unknown SecurityLevel: " + securityLevel); + } + } + + private String getAttributeOrDefault(Attributes attributes, Attributes.Key key) { + String value = attributes.get(key); + return value == null ? "" : value; + } } // All methods are called in syncContext diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 16b8adbd347..78c5181502f 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -415,7 +415,7 @@ void exitIdleMode() { LbHelperImpl lbHelper = new LbHelperImpl(); lbHelper.lb = loadBalancerFactory.newLoadBalancer(lbHelper); // Delay setting lbHelper until fully initialized, since loadBalancerFactory is user code and - // may throw. We don't want to confuse our state, even if we will enter panic mode. + // may throw. We don't want to confuse our state, even if we enter panic mode. this.lbHelper = lbHelper; channelStateManager.gotoState(CONNECTING); @@ -1464,7 +1464,9 @@ void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) { subchannelTracer, subchannelLogId, subchannelLogger, - transportFilters); + transportFilters, + target, + lbHelper.getMetricRecorder()); oobChannelTracer.reportEvent(new ChannelTrace.Event.Builder() .setDescription("Child Subchannel created") .setSeverity(ChannelTrace.Event.Severity.CT_INFO) @@ -1895,7 +1897,8 @@ void onNotInUse(InternalSubchannel is) { subchannelTracer, subchannelLogId, subchannelLogger, - transportFilters); + transportFilters, target, + lbHelper.getMetricRecorder()); channelTracer.reportEvent(new ChannelTrace.Event.Builder() .setDescription("Child Subchannel started") diff --git a/core/src/main/java/io/grpc/internal/MetricRecorderImpl.java b/core/src/main/java/io/grpc/internal/MetricRecorderImpl.java index 452b1c5df07..ded9d5ce589 100644 --- a/core/src/main/java/io/grpc/internal/MetricRecorderImpl.java +++ b/core/src/main/java/io/grpc/internal/MetricRecorderImpl.java @@ -26,6 +26,7 @@ import io.grpc.LongCounterMetricInstrument; import io.grpc.LongGaugeMetricInstrument; import io.grpc.LongHistogramMetricInstrument; +import io.grpc.LongUpDownCounterMetricInstrument; import io.grpc.MetricInstrument; import io.grpc.MetricInstrumentRegistry; import io.grpc.MetricRecorder; @@ -82,7 +83,7 @@ public void addDoubleCounter(DoubleCounterMetricInstrument metricInstrument, dou * Records a long counter value. * * @param metricInstrument the {@link LongCounterMetricInstrument} to record. - * @param value the value to record. + * @param value the value to record. Must be non-negative. * @param requiredLabelValues the required label values for the metric. * @param optionalLabelValues the optional label values for the metric. */ @@ -103,6 +104,32 @@ public void addLongCounter(LongCounterMetricInstrument metricInstrument, long va } } + /** + * Adds a long up down counter value. + * + * @param metricInstrument the {@link io.grpc.LongUpDownCounterMetricInstrument} to record. + * @param value the value to record. May be positive, negative or zero. + * @param requiredLabelValues the required label values for the metric. + * @param optionalLabelValues the optional label values for the metric. + */ + @Override + public void addLongUpDownCounter(LongUpDownCounterMetricInstrument metricInstrument, long value, + List requiredLabelValues, + List optionalLabelValues) { + MetricRecorder.super.addLongUpDownCounter(metricInstrument, value, requiredLabelValues, + optionalLabelValues); + for (MetricSink sink : metricSinks) { + int measuresSize = sink.getMeasuresSize(); + if (measuresSize <= metricInstrument.getIndex()) { + // Measures may need updating in two cases: + // 1. When the sink is initially created with an empty list of measures. + // 2. When new metric instruments are registered, requiring the sink to accommodate them. + sink.updateMeasures(registry.getMetricInstruments()); + } + sink.addLongUpDownCounter(metricInstrument, value, requiredLabelValues, optionalLabelValues); + } + } + /** * Records a double histogram value. * diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index bbc144ea775..ebe329ca591 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -92,7 +92,7 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { return Status.FAILED_PRECONDITION.withDescription("Already shut down"); } - // Cache whether or not this is a petiole policy, which is based off of an address attribute + // Check whether this is a petiole policy, which is based off of an address attribute Boolean isPetiolePolicy = resolvedAddresses.getAttributes().get(IS_PETIOLE_POLICY); this.notAPetiolePolicy = isPetiolePolicy == null || !isPetiolePolicy; diff --git a/core/src/main/java/io/grpc/internal/SubchannelMetrics.java b/core/src/main/java/io/grpc/internal/SubchannelMetrics.java new file mode 100644 index 00000000000..8921f13ebe6 --- /dev/null +++ b/core/src/main/java/io/grpc/internal/SubchannelMetrics.java @@ -0,0 +1,189 @@ +/* + * Copyright 2025 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.collect.ImmutableList; +import com.google.common.collect.Lists; +import io.grpc.LongCounterMetricInstrument; +import io.grpc.LongUpDownCounterMetricInstrument; +import io.grpc.MetricInstrumentRegistry; +import io.grpc.MetricRecorder; +import javax.annotation.Nullable; + +final class SubchannelMetrics { + + private static final LongCounterMetricInstrument disconnections; + private static final LongCounterMetricInstrument connectionAttemptsSucceeded; + private static final LongCounterMetricInstrument connectionAttemptsFailed; + private static final LongUpDownCounterMetricInstrument openConnections; + private final MetricRecorder metricRecorder; + + public SubchannelMetrics(MetricRecorder metricRecorder) { + this.metricRecorder = metricRecorder; + } + + static { + MetricInstrumentRegistry metricInstrumentRegistry + = MetricInstrumentRegistry.getDefaultRegistry(); + disconnections = metricInstrumentRegistry.registerLongCounter( + "grpc.subchannel.disconnections", + "EXPERIMENTAL. Number of times the selected subchannel becomes disconnected", + "{disconnection}", + Lists.newArrayList("grpc.target"), + Lists.newArrayList("grpc.lb.backend_service", "grpc.lb.locality", "grpc.disconnect_error"), + false + ); + + connectionAttemptsSucceeded = metricInstrumentRegistry.registerLongCounter( + "grpc.subchannel.connection_attempts_succeeded", + "EXPERIMENTAL. Number of successful connection attempts", + "{attempt}", + Lists.newArrayList("grpc.target"), + Lists.newArrayList("grpc.lb.backend_service", "grpc.lb.locality"), + false + ); + + connectionAttemptsFailed = metricInstrumentRegistry.registerLongCounter( + "grpc.subchannel.connection_attempts_failed", + "EXPERIMENTAL. Number of failed connection attempts", + "{attempt}", + Lists.newArrayList("grpc.target"), + Lists.newArrayList("grpc.lb.backend_service", "grpc.lb.locality"), + false + ); + + openConnections = metricInstrumentRegistry.registerLongUpDownCounter( + "grpc.subchannel.open_connections", + "EXPERIMENTAL. Number of open connections.", + "{connection}", + Lists.newArrayList("grpc.target"), + Lists.newArrayList("grpc.security_level", "grpc.lb.backend_service", "grpc.lb.locality"), + false + ); + } + + public void recordConnectionAttemptSucceeded(String target, String backendService, + String locality, String securityLevel) { + metricRecorder + .addLongCounter(connectionAttemptsSucceeded, 1, + ImmutableList.of(target), + ImmutableList.of(backendService, locality)); + metricRecorder + .addLongUpDownCounter(openConnections, 1, + ImmutableList.of(target), + ImmutableList.of(securityLevel, backendService, locality)); + } + + public void recordConnectionAttemptFailed(String target, String backendService, String locality) { + metricRecorder + .addLongCounter(connectionAttemptsFailed, 1, + ImmutableList.of(target), + ImmutableList.of(backendService, locality)); + } + + public void recordDisconnection(String target, String backendService, String locality, + String disconnectError, String securityLevel) { + metricRecorder + .addLongCounter(disconnections, 1, + ImmutableList.of(target), + ImmutableList.of(backendService, locality, disconnectError)); + metricRecorder + .addLongUpDownCounter(openConnections, -1, + ImmutableList.of(target), + ImmutableList.of(securityLevel, backendService, locality)); + } + + /** + * Represents the reason for a subchannel failure. + */ + public enum DisconnectError { + + /** + * Represents an HTTP/2 GOAWAY frame. The specific error code + * (e.g., "NO_ERROR", "PROTOCOL_ERROR") should be handled separately + * as it is a dynamic part of the error. + * See RFC 9113 for error codes: https://www.rfc-editor.org/rfc/rfc9113.html#name-error-codes + */ + GOAWAY("goaway"), + + /** + * The subchannel was shut down for various reasons like parent channel shutdown, + * idleness, or load balancing policy changes. + */ + SUBCHANNEL_SHUTDOWN("subchannel shutdown"), + + /** + * Connection was reset (e.g., ECONNRESET, WSAECONNERESET). + */ + CONNECTION_RESET("connection reset"), + + /** + * Connection timed out (e.g., ETIMEDOUT, WSAETIMEDOUT), including closures + * from gRPC keepalives. + */ + CONNECTION_TIMED_OUT("connection timed out"), + + /** + * Connection was aborted (e.g., ECONNABORTED, WSAECONNABORTED). + */ + CONNECTION_ABORTED("connection aborted"), + + /** + * Any socket error not covered by other specific disconnect errors. + */ + SOCKET_ERROR("socket error"), + + /** + * A catch-all for any other unclassified reason. + */ + UNKNOWN("unknown"); + + private final String errorTag; + + /** + * Private constructor to associate a description with each enum constant. + * + * @param errorTag The detailed explanation of the error. + */ + DisconnectError(String errorTag) { + this.errorTag = errorTag; + } + + /** + * Gets the error string suitable for use as a metric tag. + * + *

If the reason is {@code GOAWAY}, this method requires the specific + * HTTP/2 error code to create the complete tag (e.g., "goaway PROTOCOL_ERROR"). + * For all other reasons, the parameter is ignored.

+ * + * @param goawayErrorCode The specific HTTP/2 error code. This is only + * used if the reason is GOAWAY and should not be null in that case. + * @return The formatted error string. + */ + public String getErrorString(@Nullable String goawayErrorCode) { + if (this == GOAWAY) { + if (goawayErrorCode == null || goawayErrorCode.isEmpty()) { + // Return the base tag if the code is missing, or consider throwing an exception + // throw new IllegalArgumentException("goawayErrorCode is required for GOAWAY reason."); + return this.errorTag; + } + return this.errorTag + " " + goawayErrorCode; + } + return this.errorTag; + } + } +} diff --git a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java index bed722f5f3a..4ac5fbac362 100644 --- a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java +++ b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java @@ -29,10 +29,13 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -48,6 +51,10 @@ import io.grpc.InternalLogId; import io.grpc.InternalWithLogId; import io.grpc.LoadBalancer; +import io.grpc.MetricInstrument; +import io.grpc.MetricRecorder; +import io.grpc.NameResolver; +import io.grpc.SecurityLevel; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.internal.InternalSubchannel.CallTracingTransport; @@ -68,6 +75,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -81,6 +89,9 @@ public class InternalSubchannelTest { public final MockitoRule mocks = MockitoJUnit.rule(); private static final String AUTHORITY = "fakeauthority"; + private static final String BACKEND_SERVICE = "ice-cream-factory-service"; + private static final String LOCALITY = "mars-olympus-mons-datacenter"; + private static final SecurityLevel SECURITY_LEVEL = SecurityLevel.PRIVACY_AND_INTEGRITY; private static final String USER_AGENT = "mosaic"; private static final ConnectivityStateInfo UNAVAILABLE_STATE = ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE); @@ -108,6 +119,10 @@ public void uncaughtException(Thread t, Throwable e) { @Mock private BackoffPolicy.Provider mockBackoffPolicyProvider; @Mock private ClientTransportFactory mockTransportFactory; + @Mock private BackoffPolicy mockBackoffPolicy; + private MetricRecorder mockMetricRecorder = mock(MetricRecorder.class, + delegatesTo(new MetricRecorderImpl())); + private final LinkedList callbackInvokes = new LinkedList<>(); private final InternalSubchannel.Callback mockInternalSubchannelCallback = new InternalSubchannel.Callback() { @@ -1446,7 +1461,136 @@ private void createInternalSubchannel(boolean reconnectDisabled, subchannelTracer, logId, new ChannelLoggerImpl(subchannelTracer, fakeClock.getTimeProvider()), - Collections.emptyList()); + Collections.emptyList(), + "", + new MetricRecorder() { + } + ); + } + + @Test + public void subchannelStateChanges_triggersAttemptFailedMetric() { + // 1. Setup: Standard subchannel initialization + when(mockBackoffPolicyProvider.get()).thenReturn(mockBackoffPolicy); + SocketAddress addr = mock(SocketAddress.class); + Attributes eagAttributes = Attributes.newBuilder() + .set(NameResolver.ATTR_BACKEND_SERVICE, BACKEND_SERVICE) + .set(EquivalentAddressGroup.ATTR_LOCALITY_NAME, LOCALITY) + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SECURITY_LEVEL) + .build(); + List addressGroups = + Arrays.asList(new EquivalentAddressGroup(Arrays.asList(addr), eagAttributes)); + InternalLogId logId = InternalLogId.allocate("Subchannel", /*details=*/ AUTHORITY); + ChannelTracer subchannelTracer = new ChannelTracer(logId, 10, + fakeClock.getTimeProvider().currentTimeNanos(), "Subchannel"); + LoadBalancer.CreateSubchannelArgs createSubchannelArgs = + LoadBalancer.CreateSubchannelArgs.newBuilder().setAddresses(addressGroups).build(); + internalSubchannel = new InternalSubchannel( + createSubchannelArgs, AUTHORITY, USER_AGENT, mockBackoffPolicyProvider, + mockTransportFactory, fakeClock.getScheduledExecutorService(), + fakeClock.getStopwatchSupplier(), syncContext, mockInternalSubchannelCallback, channelz, + CallTracer.getDefaultFactory().create(), subchannelTracer, logId, + new ChannelLoggerImpl(subchannelTracer, fakeClock.getTimeProvider()), + Collections.emptyList(), AUTHORITY, mockMetricRecorder + ); + + // --- Action: Simulate the "connecting to failed" transition --- + // a. Initiate the connection attempt. The subchannel is now CONNECTING. + internalSubchannel.obtainActiveTransport(); + MockClientTransportInfo transportInfo = transports.poll(); + assertNotNull("A connection attempt should have been made", transportInfo); + + // b. Fail the transport before it can signal `transportReady()`. + transportInfo.listener.transportShutdown( + Status.INTERNAL.withDescription("Simulated connect failure")); + fakeClock.runDueTasks(); // Process the failure event + + // --- Verification --- + // a. Verify that the "connection_attempts_failed" metric was recorded exactly once. + verify(mockMetricRecorder).addLongCounter( + eqMetricInstrumentName("grpc.subchannel.connection_attempts_failed"), + eq(1L), + eq(Arrays.asList(AUTHORITY)), + eq(Arrays.asList(BACKEND_SERVICE, LOCALITY)) + ); + + // b. Verify no other metrics were recorded. This confirms it wasn't incorrectly + // logged as a success, disconnection, or open connection. + verifyNoMoreInteractions(mockMetricRecorder); + } + + @Test + public void subchannelStateChanges_triggersSuccessAndDisconnectMetrics() { + // 1. Mock the backoff policy (needed for subchannel creation) + when(mockBackoffPolicyProvider.get()).thenReturn(mockBackoffPolicy); + + // 2. Setup Subchannel with attributes + SocketAddress addr = mock(SocketAddress.class); + Attributes eagAttributes = Attributes.newBuilder() + .set(NameResolver.ATTR_BACKEND_SERVICE, BACKEND_SERVICE) + .set(EquivalentAddressGroup.ATTR_LOCALITY_NAME, LOCALITY) + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SECURITY_LEVEL) + .build(); + List addressGroups = + Arrays.asList(new EquivalentAddressGroup(Arrays.asList(addr), eagAttributes)); + createInternalSubchannel(new EquivalentAddressGroup(addr)); + InternalLogId logId = InternalLogId.allocate("Subchannel", /*details=*/ AUTHORITY); + ChannelTracer subchannelTracer = new ChannelTracer(logId, 10, + fakeClock.getTimeProvider().currentTimeNanos(), "Subchannel"); + LoadBalancer.CreateSubchannelArgs createSubchannelArgs = + LoadBalancer.CreateSubchannelArgs.newBuilder().setAddresses(addressGroups).build(); + internalSubchannel = new InternalSubchannel( + createSubchannelArgs, AUTHORITY, USER_AGENT, mockBackoffPolicyProvider, + mockTransportFactory, fakeClock.getScheduledExecutorService(), + fakeClock.getStopwatchSupplier(), syncContext, mockInternalSubchannelCallback, channelz, + CallTracer.getDefaultFactory().create(), subchannelTracer, logId, + new ChannelLoggerImpl(subchannelTracer, fakeClock.getTimeProvider()), + Collections.emptyList(), AUTHORITY, mockMetricRecorder + ); + + // --- Action: Successful connection --- + internalSubchannel.obtainActiveTransport(); + MockClientTransportInfo transportInfo = transports.poll(); + assertNotNull(transportInfo); + transportInfo.listener.transportReady(); + fakeClock.runDueTasks(); // Process the successful connection + + // --- Action: Transport is shut down --- + transportInfo.listener.transportShutdown(Status.UNAVAILABLE.withDescription("unknown")); + fakeClock.runDueTasks(); // Process the shutdown + + // --- Verification --- + InOrder inOrder = inOrder(mockMetricRecorder); + + // Verify successful connection metrics + inOrder.verify(mockMetricRecorder).addLongCounter( + eqMetricInstrumentName("grpc.subchannel.connection_attempts_succeeded"), + eq(1L), + eq(Arrays.asList(AUTHORITY)), + eq(Arrays.asList(BACKEND_SERVICE, LOCALITY)) + ); + inOrder.verify(mockMetricRecorder).addLongUpDownCounter( + eqMetricInstrumentName("grpc.subchannel.open_connections"), + eq(1L), + eq(Arrays.asList(AUTHORITY)), + eq(Arrays.asList("privacy_and_integrity", BACKEND_SERVICE, LOCALITY)) + ); + + // Verify disconnection metrics + inOrder.verify(mockMetricRecorder).addLongCounter( + eqMetricInstrumentName("grpc.subchannel.disconnections"), + eq(1L), + eq(Arrays.asList(AUTHORITY)), + eq(Arrays.asList(BACKEND_SERVICE, LOCALITY, "unknown")) + ); + inOrder.verify(mockMetricRecorder).addLongUpDownCounter( + eqMetricInstrumentName("grpc.subchannel.open_connections"), + eq(-1L), + eq(Arrays.asList(AUTHORITY)), + eq(Arrays.asList("privacy_and_integrity", BACKEND_SERVICE, LOCALITY)) + ); + + inOrder.verifyNoMoreInteractions(); } private void assertNoCallbackInvoke() { @@ -1459,5 +1603,13 @@ private void assertExactCallbackInvokes(String ... expectedInvokes) { callbackInvokes.clear(); } + static class MetricRecorderImpl implements MetricRecorder { + } + + @SuppressWarnings("TypeParameterUnusedInFormals") + private T eqMetricInstrumentName(String name) { + return argThat(instrument -> instrument.getName().equals(name)); + } + private static class FakeSocketAddress extends SocketAddress {} } diff --git a/core/src/test/java/io/grpc/internal/MetricRecorderImplTest.java b/core/src/test/java/io/grpc/internal/MetricRecorderImplTest.java index 08f34a267f9..33bf9bb41e2 100644 --- a/core/src/test/java/io/grpc/internal/MetricRecorderImplTest.java +++ b/core/src/test/java/io/grpc/internal/MetricRecorderImplTest.java @@ -32,6 +32,7 @@ import io.grpc.LongCounterMetricInstrument; import io.grpc.LongGaugeMetricInstrument; import io.grpc.LongHistogramMetricInstrument; +import io.grpc.LongUpDownCounterMetricInstrument; import io.grpc.MetricInstrumentRegistry; import io.grpc.MetricInstrumentRegistryAccessor; import io.grpc.MetricRecorder; @@ -79,6 +80,9 @@ public class MetricRecorderImplTest { private final LongGaugeMetricInstrument longGaugeInstrument = registry.registerLongGauge("gauge0", DESCRIPTION, UNIT, REQUIRED_LABEL_KEYS, OPTIONAL_LABEL_KEYS, ENABLED); + private final LongUpDownCounterMetricInstrument longUpDownCounterInstrument = + registry.registerLongUpDownCounter("upDownCounter0", DESCRIPTION, UNIT, + REQUIRED_LABEL_KEYS, OPTIONAL_LABEL_KEYS, ENABLED); private MetricRecorder recorder; @Before @@ -88,7 +92,7 @@ public void setUp() { @Test public void addCounter() { - when(mockSink.getMeasuresSize()).thenReturn(4); + when(mockSink.getMeasuresSize()).thenReturn(6); recorder.addDoubleCounter(doubleCounterInstrument, 1.0, REQUIRED_LABEL_VALUES, OPTIONAL_LABEL_VALUES); @@ -100,6 +104,12 @@ public void addCounter() { verify(mockSink, times(2)).addLongCounter(eq(longCounterInstrument), eq(1L), eq(REQUIRED_LABEL_VALUES), eq(OPTIONAL_LABEL_VALUES)); + recorder.addLongUpDownCounter(longUpDownCounterInstrument, -10, REQUIRED_LABEL_VALUES, + OPTIONAL_LABEL_VALUES); + verify(mockSink, times(2)) + .addLongUpDownCounter(eq(longUpDownCounterInstrument), eq(-10L), + eq(REQUIRED_LABEL_VALUES), eq(OPTIONAL_LABEL_VALUES)); + verify(mockSink, never()).updateMeasures(registry.getMetricInstruments()); } @@ -190,6 +200,13 @@ public void newRegisteredMetricUpdateMeasures() { verify(mockSink, times(2)) .registerBatchCallback(any(Runnable.class), eq(longGaugeInstrument)); registration.close(); + + // Long UpDown Counter + recorder.addLongUpDownCounter(longUpDownCounterInstrument, -10, REQUIRED_LABEL_VALUES, + OPTIONAL_LABEL_VALUES); + verify(mockSink, times(12)).updateMeasures(anyList()); + verify(mockSink, times(2)).addLongUpDownCounter(eq(longUpDownCounterInstrument), eq(-10L), + eq(REQUIRED_LABEL_VALUES), eq(OPTIONAL_LABEL_VALUES)); } @Test(expected = IllegalArgumentException.class) @@ -208,6 +225,13 @@ public void addLongCounterMismatchedRequiredLabelValues() { OPTIONAL_LABEL_VALUES); } + @Test(expected = IllegalArgumentException.class) + public void addLongUpDownCounterMismatchedRequiredLabelValues() { + when(mockSink.getMeasuresSize()).thenReturn(6); + recorder.addLongUpDownCounter(longUpDownCounterInstrument, 1, ImmutableList.of(), + OPTIONAL_LABEL_VALUES); + } + @Test(expected = IllegalArgumentException.class) public void recordDoubleHistogramMismatchedRequiredLabelValues() { when(mockSink.getMeasuresSize()).thenReturn(4); @@ -260,6 +284,13 @@ public void addLongCounterMismatchedOptionalLabelValues() { ImmutableList.of()); } + @Test(expected = IllegalArgumentException.class) + public void addLongUpDownCounterMismatchedOptionalLabelValues() { + when(mockSink.getMeasuresSize()).thenReturn(6); + recorder.addLongUpDownCounter(longUpDownCounterInstrument, 1, REQUIRED_LABEL_VALUES, + ImmutableList.of()); + } + @Test(expected = IllegalArgumentException.class) public void recordDoubleHistogramMismatchedOptionalLabelValues() { when(mockSink.getMeasuresSize()).thenReturn(4); diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricSink.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricSink.java index 8f612804436..fd8af7f998f 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricSink.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricSink.java @@ -27,6 +27,7 @@ import io.grpc.LongCounterMetricInstrument; import io.grpc.LongGaugeMetricInstrument; import io.grpc.LongHistogramMetricInstrument; +import io.grpc.LongUpDownCounterMetricInstrument; import io.grpc.MetricInstrument; import io.grpc.MetricSink; import io.opentelemetry.api.common.Attributes; @@ -36,6 +37,7 @@ import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongHistogram; +import io.opentelemetry.api.metrics.LongUpDownCounter; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.ObservableLongMeasurement; import io.opentelemetry.api.metrics.ObservableMeasurement; @@ -117,6 +119,22 @@ public void addLongCounter(LongCounterMetricInstrument metricInstrument, long va counter.add(value, attributes); } + @Override + public void addLongUpDownCounter(LongUpDownCounterMetricInstrument metricInstrument, long value, + List requiredLabelValues, + List optionalLabelValues) { + MeasuresData instrumentData = measures.get(metricInstrument.getIndex()); + if (instrumentData == null) { + // Disabled metric + return; + } + Attributes attributes = createAttributes(metricInstrument.getRequiredLabelKeys(), + metricInstrument.getOptionalLabelKeys(), requiredLabelValues, optionalLabelValues, + instrumentData.getOptionalLabelsBitSet()); + LongUpDownCounter counter = (LongUpDownCounter) instrumentData.getMeasure(); + counter.add(value, attributes); + } + @Override public void recordDoubleHistogram(DoubleHistogramMetricInstrument metricInstrument, double value, List requiredLabelValues, List optionalLabelValues) { @@ -256,6 +274,11 @@ public void updateMeasures(List instruments) { .setDescription(description) .ofLongs() .buildObserver(); + } else if (instrument instanceof LongUpDownCounterMetricInstrument) { + openTelemetryMeasure = openTelemetryMeter.upDownCounterBuilder(name) + .setUnit(unit) + .setDescription(description) + .build(); } else { logger.log(Level.FINE, "Unsupported metric instrument type : {0}", instrument); openTelemetryMeasure = null; diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/internal/OpenTelemetryConstants.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/internal/OpenTelemetryConstants.java index 5214804d369..ef21903c8e7 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/internal/OpenTelemetryConstants.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/internal/OpenTelemetryConstants.java @@ -36,6 +36,12 @@ public final class OpenTelemetryConstants { public static final AttributeKey BACKEND_SERVICE_KEY = AttributeKey.stringKey("grpc.lb.backend_service"); + public static final AttributeKey DISCONNECT_ERROR_KEY = + AttributeKey.stringKey("grpc.disconnect_error"); + + public static final AttributeKey SECURITY_LEVEL_KEY = + AttributeKey.stringKey("grpc.security_level"); + public static final List LATENCY_BUCKETS = ImmutableList.of( 0d, 0.00001d, 0.00005d, 0.0001d, 0.0003d, 0.0006d, 0.0008d, 0.001d, 0.002d, diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricSinkTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricSinkTest.java index c538da55dcb..cced4de3cb4 100644 --- a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricSinkTest.java +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryMetricSinkTest.java @@ -24,6 +24,7 @@ import io.grpc.LongCounterMetricInstrument; import io.grpc.LongGaugeMetricInstrument; import io.grpc.LongHistogramMetricInstrument; +import io.grpc.LongUpDownCounterMetricInstrument; import io.grpc.MetricInstrument; import io.grpc.MetricSink; import io.grpc.opentelemetry.internal.OpenTelemetryConstants; @@ -144,16 +145,25 @@ public void addCounter_enabledMetric() { "Number of client calls started", "count", Collections.emptyList(), Collections.emptyList(), true); + LongUpDownCounterMetricInstrument longUpDownCounterInstrument = + new LongUpDownCounterMetricInstrument(2, "active_carrier_pigeons", + "Active Carrier Pigeons", "pigeons", + Collections.emptyList(), + Collections.emptyList(), true); + // Create sink sink = new OpenTelemetryMetricSink(testMeter, enabledMetrics, false, Collections.emptyList()); // Invoke updateMeasures - sink.updateMeasures(Arrays.asList(longCounterInstrument, doubleCounterInstrument)); + sink.updateMeasures(Arrays.asList(longCounterInstrument, doubleCounterInstrument, + longUpDownCounterInstrument)); sink.addLongCounter(longCounterInstrument, 123L, Collections.emptyList(), Collections.emptyList()); sink.addDoubleCounter(doubleCounterInstrument, 12.0, Collections.emptyList(), Collections.emptyList()); + sink.addLongUpDownCounter(longUpDownCounterInstrument, -3L, Collections.emptyList(), + Collections.emptyList()); assertThat(openTelemetryTesting.getMetrics()) .satisfiesExactlyInAnyOrder( @@ -184,7 +194,21 @@ public void addCounter_enabledMetric() { .hasPointsSatisfying( point -> point - .hasValue(12.0D)))); + .hasValue(12.0D))), + metric -> + assertThat(metric) + .hasInstrumentationScope(InstrumentationScopeInfo.create( + OpenTelemetryConstants.INSTRUMENTATION_SCOPE)) + .hasName("active_carrier_pigeons") + .hasDescription("Active Carrier Pigeons") + .hasUnit("pigeons") + .hasLongSumSatisfying( + longSum -> + longSum + .hasPointsSatisfying( + point -> + point + .hasValue(-3L)))); } @Test @@ -192,18 +216,27 @@ public void addCounter_disabledMetric() { // set up sink with disabled metric Map enabledMetrics = new HashMap<>(); enabledMetrics.put("client_latency", false); + enabledMetrics.put("active_carrier_pigeons", false); LongCounterMetricInstrument instrument = new LongCounterMetricInstrument(0, "client_latency", "Client latency", "s", Collections.emptyList(), Collections.emptyList(), true); + LongUpDownCounterMetricInstrument longUpDownCounterInstrument = + new LongUpDownCounterMetricInstrument(1, "active_carrier_pigeons", + "Active Carrier Pigeons", "pigeons", + Collections.emptyList(), + Collections.emptyList(), false); + // Create sink sink = new OpenTelemetryMetricSink(testMeter, enabledMetrics, true, Collections.emptyList()); // Invoke updateMeasures - sink.updateMeasures(Arrays.asList(instrument)); + sink.updateMeasures(Arrays.asList(instrument, longUpDownCounterInstrument)); sink.addLongCounter(instrument, 123L, Collections.emptyList(), Collections.emptyList()); + sink.addLongUpDownCounter(longUpDownCounterInstrument, -13L, Collections.emptyList(), + Collections.emptyList()); assertThat(openTelemetryTesting.getMetrics()).isEmpty(); } @@ -377,6 +410,7 @@ public void registerBatchCallback_bothEnabledAndDisabled() { public void recordLabels() { Map enabledMetrics = new HashMap<>(); enabledMetrics.put("client_latency", true); + enabledMetrics.put("ghosts_in_the_wire", true); List optionalLabels = Arrays.asList("optional_label_key_2"); @@ -384,16 +418,24 @@ public void recordLabels() { new LongCounterMetricInstrument(0, "client_latency", "Client latency", "s", ImmutableList.of("required_label_key_1", "required_label_key_2"), ImmutableList.of("optional_label_key_1", "optional_label_key_2"), false); + LongUpDownCounterMetricInstrument longUpDownCounterInstrument = + new LongUpDownCounterMetricInstrument(1, "ghosts_in_the_wire", + "Number of Ghosts Haunting the Wire", "{ghosts}", + ImmutableList.of("required_label_key_1", "required_label_key_2"), + ImmutableList.of("optional_label_key_1", "optional_label_key_2"), false); // Create sink sink = new OpenTelemetryMetricSink(testMeter, enabledMetrics, false, optionalLabels); // Invoke updateMeasures - sink.updateMeasures(Arrays.asList(longCounterInstrument)); + sink.updateMeasures(Arrays.asList(longCounterInstrument, longUpDownCounterInstrument)); sink.addLongCounter(longCounterInstrument, 123L, ImmutableList.of("required_label_value_1", "required_label_value_2"), ImmutableList.of("optional_label_value_1", "optional_label_value_2")); + sink.addLongUpDownCounter(longUpDownCounterInstrument, -400L, + ImmutableList.of("required_label_value_1", "required_label_value_2"), + ImmutableList.of("optional_label_value_1", "optional_label_value_2")); io.opentelemetry.api.common.Attributes expectedAtrributes = io.opentelemetry.api.common.Attributes.of( @@ -417,6 +459,22 @@ public void recordLabels() { point -> point .hasAttributes(expectedAtrributes) - .hasValue(123L)))); + .hasValue(123L))), + metric -> + assertThat(metric) + .hasInstrumentationScope(InstrumentationScopeInfo.create( + OpenTelemetryConstants.INSTRUMENTATION_SCOPE)) + .hasName("ghosts_in_the_wire") + .hasDescription("Number of Ghosts Haunting the Wire") + .hasUnit("{ghosts}") + .hasLongSumSatisfying( + longSum -> + longSum + .hasPointsSatisfying( + point -> + point + .hasAttributes(expectedAtrributes) + .hasValue(-400L)))); + } } diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java index 034cdee0815..fba66e2e8d7 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java @@ -305,7 +305,7 @@ private List withAdditionalAttributes( private ClusterLocality createClusterLocalityFromAttributes(Attributes addressAttributes) { Locality locality = addressAttributes.get(XdsAttributes.ATTR_LOCALITY); - String localityName = addressAttributes.get(XdsAttributes.ATTR_LOCALITY_NAME); + String localityName = addressAttributes.get(EquivalentAddressGroup.ATTR_LOCALITY_NAME); // Endpoint addresses resolved by ClusterResolverLoadBalancer should always contain // attributes with its locality, including endpoints in LOGICAL_DNS clusters. diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index e333c46750c..f57cada52e9 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -408,7 +408,7 @@ public void run() { Attributes attr = endpoint.eag().getAttributes().toBuilder() .set(XdsAttributes.ATTR_LOCALITY, locality) - .set(XdsAttributes.ATTR_LOCALITY_NAME, localityName) + .set(EquivalentAddressGroup.ATTR_LOCALITY_NAME, localityName) .set(XdsAttributes.ATTR_LOCALITY_WEIGHT, localityLbInfo.localityWeight()) .set(XdsAttributes.ATTR_SERVER_WEIGHT, weight) @@ -659,7 +659,7 @@ public Status onResult2(final ResolutionResult resolutionResult) { String localityName = localityName(LOGICAL_DNS_CLUSTER_LOCALITY); Attributes attr = eag.getAttributes().toBuilder() .set(XdsAttributes.ATTR_LOCALITY, LOGICAL_DNS_CLUSTER_LOCALITY) - .set(XdsAttributes.ATTR_LOCALITY_NAME, localityName) + .set(EquivalentAddressGroup.ATTR_LOCALITY_NAME, localityName) .set(XdsAttributes.ATTR_ADDRESS_NAME, dnsHostName) .build(); eag = new EquivalentAddressGroup(eag.getAddresses(), attr); diff --git a/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java index ab1abb1da15..1a12412f923 100644 --- a/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java @@ -74,7 +74,7 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { Map localityWeights = new HashMap<>(); for (EquivalentAddressGroup eag : resolvedAddresses.getAddresses()) { Attributes eagAttrs = eag.getAttributes(); - String locality = eagAttrs.get(XdsAttributes.ATTR_LOCALITY_NAME); + String locality = eagAttrs.get(EquivalentAddressGroup.ATTR_LOCALITY_NAME); Integer localityWeight = eagAttrs.get(XdsAttributes.ATTR_LOCALITY_WEIGHT); if (locality == null) { diff --git a/xds/src/main/java/io/grpc/xds/XdsAttributes.java b/xds/src/main/java/io/grpc/xds/XdsAttributes.java index 4a64fdb1453..2e165201e5f 100644 --- a/xds/src/main/java/io/grpc/xds/XdsAttributes.java +++ b/xds/src/main/java/io/grpc/xds/XdsAttributes.java @@ -81,13 +81,6 @@ final class XdsAttributes { static final Attributes.Key ATTR_LOCALITY = Attributes.Key.create("io.grpc.xds.XdsAttributes.locality"); - /** - * The name of the locality that this EquivalentAddressGroup is in. - */ - @EquivalentAddressGroup.Attr - static final Attributes.Key ATTR_LOCALITY_NAME = - Attributes.Key.create("io.grpc.xds.XdsAttributes.localityName"); - /** * Endpoint weight for load balancing purposes. */ diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index 7df0630b779..c5e3f80f170 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -1017,7 +1017,7 @@ public String toString() { Attributes.Builder attributes = Attributes.newBuilder() .set(XdsAttributes.ATTR_LOCALITY, locality) // Unique but arbitrary string - .set(XdsAttributes.ATTR_LOCALITY_NAME, locality.toString()); + .set(EquivalentAddressGroup.ATTR_LOCALITY_NAME, locality.toString()); if (authorityHostname != null) { attributes.set(XdsAttributes.ATTR_ADDRESS_NAME, authorityHostname); } diff --git a/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java index b6a5d8dbf73..584c32738c5 100644 --- a/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java @@ -254,7 +254,7 @@ public String toString() { } Attributes.Builder attrBuilder = Attributes.newBuilder() - .set(XdsAttributes.ATTR_LOCALITY_NAME, locality); + .set(EquivalentAddressGroup.ATTR_LOCALITY_NAME, locality); if (localityWeight != null) { attrBuilder.set(XdsAttributes.ATTR_LOCALITY_WEIGHT, localityWeight); } From b2a95cae725169870e3fc0869439b488b15a6190 Mon Sep 17 00:00:00 2001 From: jiangyuan Date: Wed, 10 Sep 2025 20:09:33 +0800 Subject: [PATCH 14/22] netty, okhttp: Add allow header for response code 405 (#12334) Fixes #12329 --- .../io/grpc/netty/NettyServerHandler.java | 13 ++++++++++++- .../io/grpc/netty/NettyServerHandlerTest.java | 4 +++- .../io/grpc/okhttp/OkHttpServerTransport.java | 14 +++++++++++++- .../okhttp/OkHttpServerTransportTest.java | 19 +++++++++++++++---- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java index 48f1aae91a1..69128e0e7a6 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java @@ -60,6 +60,7 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http2.DecoratingHttp2ConnectionEncoder; import io.netty.handler.codec.http2.DecoratingHttp2FrameWriter; import io.netty.handler.codec.http2.DefaultHttp2Connection; @@ -70,6 +71,7 @@ import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController; import io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController; +import io.netty.handler.codec.http2.EmptyHttp2Headers; import io.netty.handler.codec.http2.Http2Connection; import io.netty.handler.codec.http2.Http2ConnectionAdapter; import io.netty.handler.codec.http2.Http2ConnectionDecoder; @@ -480,8 +482,10 @@ private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers } if (!HTTP_METHOD.contentEquals(headers.method())) { + Http2Headers extraHeaders = new DefaultHttp2Headers(); + extraHeaders.add(HttpHeaderNames.ALLOW, HTTP_METHOD); respondWithHttpError(ctx, streamId, 405, Status.Code.INTERNAL, - String.format("Method '%s' is not supported", headers.method())); + String.format("Method '%s' is not supported", headers.method()), extraHeaders); return; } @@ -869,6 +873,12 @@ public boolean visit(Http2Stream stream) throws Http2Exception { private void respondWithHttpError( ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg) { + respondWithHttpError(ctx, streamId, code, statusCode, msg, EmptyHttp2Headers.INSTANCE); + } + + private void respondWithHttpError( + ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg, + Http2Headers extraHeaders) { Metadata metadata = new Metadata(); metadata.put(InternalStatus.CODE_KEY, statusCode.toStatus()); metadata.put(InternalStatus.MESSAGE_KEY, msg); @@ -880,6 +890,7 @@ private void respondWithHttpError( for (int i = 0; i < serialized.length; i += 2) { headers.add(new AsciiString(serialized[i], false), new AsciiString(serialized[i + 1], false)); } + headers.add(extraHeaders); encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise()); ByteBuf msgBuf = ByteBufUtil.writeUtf8(ctx.alloc(), msg); encoder().writeData(ctx, streamId, msgBuf, 0, true, ctx.newPromise()); diff --git a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java index 28217937adc..0d5a9bab176 100644 --- a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java @@ -78,6 +78,7 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.Http2CodecUtil; import io.netty.handler.codec.http2.Http2Error; @@ -542,7 +543,8 @@ public void headersWithInvalidMethodShouldFail() throws Exception { .set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.INTERNAL.value())) .set(InternalStatus.MESSAGE_KEY.name(), "Method 'FAKE' is not supported") .status("" + 405) - .set(CONTENT_TYPE_HEADER, "text/plain; charset=utf-8"); + .set(CONTENT_TYPE_HEADER, "text/plain; charset=utf-8") + .set(HttpHeaderNames.ALLOW, HTTP_METHOD); verifyWrite() .writeHeaders( diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java index cc52bee85eb..b744bca3116 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java @@ -20,6 +20,7 @@ import static io.grpc.okhttp.OkHttpServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED; import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.errorprone.annotations.concurrent.GuardedBy; @@ -52,6 +53,7 @@ import java.io.IOException; import java.net.Socket; import java.net.SocketException; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; @@ -91,6 +93,7 @@ final class OkHttpServerTransport implements ServerTransport, private static final ByteString TE_TRAILERS = ByteString.encodeUtf8("trailers"); private static final ByteString CONTENT_TYPE = ByteString.encodeUtf8("content-type"); private static final ByteString CONTENT_LENGTH = ByteString.encodeUtf8("content-length"); + private static final ByteString ALLOW = ByteString.encodeUtf8("allow"); private final Config config; private final Variant variant = new Http2(); @@ -772,8 +775,9 @@ public void headers(boolean outFinished, } if (!POST_METHOD.equals(httpMethod)) { + List
extraHeaders = Lists.newArrayList(new Header(ALLOW, POST_METHOD)); respondWithHttpError(streamId, inFinished, 405, Status.Code.INTERNAL, - "HTTP Method is not supported: " + asciiString(httpMethod)); + "HTTP Method is not supported: " + asciiString(httpMethod), extraHeaders); return; } @@ -1066,11 +1070,19 @@ private void streamError(int streamId, ErrorCode errorCode, String reason) { private void respondWithHttpError( int streamId, boolean inFinished, int httpCode, Status.Code statusCode, String msg) { + respondWithHttpError(streamId, inFinished, httpCode, statusCode, msg, + Collections.emptyList()); + } + + private void respondWithHttpError( + int streamId, boolean inFinished, int httpCode, Status.Code statusCode, String msg, + List
extraHeaders) { Metadata metadata = new Metadata(); metadata.put(InternalStatus.CODE_KEY, statusCode.toStatus()); metadata.put(InternalStatus.MESSAGE_KEY, msg); List
headers = Headers.createHttpResponseHeaders(httpCode, "text/plain; charset=utf-8", metadata); + headers.addAll(extraHeaders); Buffer data = new Buffer().writeUtf8(msg); synchronized (lock) { diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java index d64d314d7d8..4d2744dc9c7 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; +import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import io.grpc.Attributes; import io.grpc.InternalChannelz.SocketStats; @@ -62,6 +63,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Deque; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -919,8 +921,9 @@ public void httpGet_failsWith405() throws Exception { CONTENT_TYPE_HEADER, TE_HEADER)); clientFrameWriter.flush(); - - verifyHttpError(1, 405, Status.Code.INTERNAL, "HTTP Method is not supported: GET"); + List
extraHeaders = Lists.newArrayList(new Header("allow", "POST")); + verifyHttpError(1, 405, Status.Code.INTERNAL, "HTTP Method is not supported: GET", + extraHeaders); shutdownAndTerminate(/*lastStreamId=*/ 1); } @@ -976,7 +979,8 @@ public void httpErrorsAdhereToFlowControl() throws Exception { new Header(":status", "405"), new Header("content-type", "text/plain; charset=utf-8"), new Header("grpc-status", "" + Status.Code.INTERNAL.value()), - new Header("grpc-message", errorDescription)); + new Header("grpc-message", errorDescription), + new Header("allow", "POST")); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); verify(clientFramesRead) .headers(false, false, 1, -1, responseHeaders, HeadersMode.HTTP_20_HEADERS); @@ -1398,11 +1402,18 @@ private void pingPong() throws IOException { private void verifyHttpError( int streamId, int httpCode, Status.Code grpcCode, String errorDescription) throws Exception { - List
responseHeaders = Arrays.asList( + verifyHttpError(streamId, httpCode, grpcCode, errorDescription, Collections.emptyList()); + } + + private void verifyHttpError( + int streamId, int httpCode, Status.Code grpcCode, String errorDescription, + List
extraHeaders) throws Exception { + List
responseHeaders = Lists.newArrayList( new Header(":status", "" + httpCode), new Header("content-type", "text/plain; charset=utf-8"), new Header("grpc-status", "" + grpcCode.value()), new Header("grpc-message", errorDescription)); + responseHeaders.addAll(extraHeaders); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); verify(clientFramesRead) .headers(false, false, streamId, -1, responseHeaders, HeadersMode.HTTP_20_HEADERS); From 7d6ea288a2286af8cac7617810e758c3c9c03f5a Mon Sep 17 00:00:00 2001 From: John Cormie Date: Thu, 11 Sep 2025 08:13:56 -0700 Subject: [PATCH 15/22] Add a basic SocketStats with just the local and remote addresses. (#12349) This will make channelz more useful. --- .../grpc/binder/internal/BinderTransport.java | 12 ++++++++- .../RobolectricBinderTransportTest.java | 25 ++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) 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 0fe131a0728..8fde69d8530 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -31,7 +31,9 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.errorprone.annotations.concurrent.GuardedBy; import io.grpc.Attributes; +import io.grpc.Grpc; import io.grpc.Internal; +import io.grpc.InternalChannelz; import io.grpc.InternalChannelz.SocketStats; import io.grpc.InternalLogId; import io.grpc.Status; @@ -205,7 +207,15 @@ public final ScheduledExecutorService getScheduledExecutorService() { // Override in child class. public final ListenableFuture getStats() { - return immediateFuture(null); + Attributes attributes = getAttributes(); + return immediateFuture( + new InternalChannelz.SocketStats( + /* data= */ null, // TODO: Keep track of these stats with TransportTracer or similar. + /* local= */ attributes.get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR), + /* remote= */ attributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR), + // TODO: SocketOptions are meaningless for binder but we're still forced to provide one. + new InternalChannelz.SocketOptions.Builder().build(), + /* security= */ null)); } // Override in child class. diff --git a/binder/src/test/java/io/grpc/binder/internal/RobolectricBinderTransportTest.java b/binder/src/test/java/io/grpc/binder/internal/RobolectricBinderTransportTest.java index 8f1209f389b..b1822f2f85e 100644 --- a/binder/src/test/java/io/grpc/binder/internal/RobolectricBinderTransportTest.java +++ b/binder/src/test/java/io/grpc/binder/internal/RobolectricBinderTransportTest.java @@ -39,6 +39,7 @@ import androidx.test.core.content.pm.PackageInfoBuilder; import com.google.common.collect.ImmutableList; import io.grpc.Attributes; +import io.grpc.InternalChannelz.SocketStats; import io.grpc.ServerStreamTracer; import io.grpc.Status; import io.grpc.binder.AndroidComponentAddress; @@ -52,6 +53,7 @@ import io.grpc.internal.GrpcUtil; import io.grpc.internal.InternalServer; import io.grpc.internal.ManagedClientTransport; +import io.grpc.internal.MockServerTransportListener; import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourcePool; import java.util.List; @@ -301,13 +303,30 @@ public void clientIgnoresDuplicateSetupTransaction() throws Exception { } assertThat(((ConnectionClientTransport) client).getAttributes().get(REMOTE_UID)) - .isEqualTo(myUid()); + .isEqualTo(myUid()); } @Test - @Ignore("See BinderTransportTest#socketStats.") @Override - public void socketStats() {} + // We don't quite pass the official/abstract version of this test yet because + // today's binder client and server transports have different ideas of each others' address. + // TODO(#12347): Remove this @Override once this difference is resolved. + public void socketStats() throws Exception { + server.start(serverListener); + ManagedClientTransport client = newClientTransport(server); + startTransport(client, mockClientTransportListener); + + SocketStats clientSocketStats = client.getStats().get(); + assertThat(clientSocketStats.local).isInstanceOf(AndroidComponentAddress.class); + assertThat(((AndroidComponentAddress) clientSocketStats.remote).getPackage()) + .isEqualTo(((AndroidComponentAddress) server.getListenSocketAddress()).getPackage()); + + MockServerTransportListener serverTransportListener = + serverListener.takeListenerOrFail(TIMEOUT_MS, MILLISECONDS); + SocketStats serverSocketStats = serverTransportListener.transport.getStats().get(); + assertThat(serverSocketStats.local).isEqualTo(server.getListenSocketAddress()); + assertThat(serverSocketStats.remote).isEqualTo(new BoundClientAddress(myUid())); + } @Test @Ignore("See BinderTransportTest#flowControlPushBack") From e91378cf96ac8dc031ae013eb169022f50bf4229 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Fri, 12 Sep 2025 16:23:54 -0700 Subject: [PATCH 16/22] binder: Only accept post-setup transactions from the server UID we actually authorize. (#12359) --- .../internal/BinderClientTransport.java | 1 + .../grpc/binder/internal/BinderTransport.java | 17 +++++ .../binder/internal/LeakSafeOneWayBinder.java | 16 ++++- .../binder/internal/TransactionUtils.java | 24 +++++++ .../RobolectricBinderTransportTest.java | 47 +++++++++++++ .../binder/internal/TransactionUtilsTest.java | 70 +++++++++++++++++++ 6 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 binder/src/test/java/io/grpc/binder/internal/TransactionUtilsTest.java diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java b/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java index 82c9e17b871..144ad56eec3 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java @@ -339,6 +339,7 @@ protected void handleSetupTransport(Parcel parcel) { shutdownInternal( Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true); } else { + restrictIncomingBinderToCallsFrom(remoteUid); attributes = setSecurityAttrs(attributes, remoteUid); authResultFuture = checkServerAuthorizationAsync(remoteUid); Futures.addCallback( 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 8fde69d8530..6c89c56ffd4 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.util.concurrent.Futures.immediateFuture; +import static io.grpc.binder.internal.TransactionUtils.newCallerFilteringHandler; import android.os.DeadObjectException; import android.os.IBinder; @@ -39,6 +40,7 @@ import io.grpc.Status; import io.grpc.StatusException; import io.grpc.binder.InboundParcelablePolicy; +import io.grpc.binder.internal.LeakSafeOneWayBinder.TransactionHandler; import io.grpc.internal.ObjectPool; import java.util.ArrayList; import java.util.Iterator; @@ -155,6 +157,7 @@ protected enum TransportState { private final ObjectPool executorServicePool; private final ScheduledExecutorService scheduledExecutorService; private final InternalLogId logId; + @GuardedBy("this") private final LeakSafeOneWayBinder incomingBinder; protected final ConcurrentHashMap> ongoingCalls; @@ -476,6 +479,15 @@ private boolean handleTransactionInternal(int code, Parcel parcel) { } } + @BinderThread + @GuardedBy("this") + protected void restrictIncomingBinderToCallsFrom(int allowedCallingUid) { + TransactionHandler currentHandler = incomingBinder.getHandler(); + if (currentHandler != null) { + incomingBinder.setHandler(newCallerFilteringHandler(allowedCallingUid, currentHandler)); + } + } + @Nullable @GuardedBy("this") protected Inbound createInbound(int callId) { @@ -561,6 +573,11 @@ Map> getOngoingCalls() { return ongoingCalls; } + @VisibleForTesting + LeakSafeOneWayBinder getIncomingBinderForTesting() { + return this.incomingBinder; + } + private static Status statusFromRemoteException(RemoteException e) { if (e instanceof DeadObjectException || e instanceof TransactionTooLargeException) { // These are to be expected from time to time and can simply be retried. diff --git a/binder/src/main/java/io/grpc/binder/internal/LeakSafeOneWayBinder.java b/binder/src/main/java/io/grpc/binder/internal/LeakSafeOneWayBinder.java index e7837b520f8..c36bc7d5bd3 100644 --- a/binder/src/main/java/io/grpc/binder/internal/LeakSafeOneWayBinder.java +++ b/binder/src/main/java/io/grpc/binder/internal/LeakSafeOneWayBinder.java @@ -73,7 +73,21 @@ public void detach() { setHandler(null); } - /** Replaces the current {@link TransactionHandler} with `handler`. */ + /** Returns the current {@link TransactionHandler} or null if already detached. */ + public @Nullable TransactionHandler getHandler() { + return handler; + } + + /** + * Replaces the current {@link TransactionHandler} with `handler`. + * + *

{@link TransactionHandler} mutations race against incoming transactions except in the + * special case where the caller is already handling an incoming transaction on this same {@link + * LeakSafeOneWayBinder} instance. In that case, mutations are safe and the provided 'handler' is + * guaranteed to be used for the very next transaction. This follows from the one-at-a-time + * property of one-way Binder transactions as explained by {@link + * TransactionHandler#handleTransaction}. + */ public void setHandler(@Nullable TransactionHandler handler) { this.handler = handler; } diff --git a/binder/src/main/java/io/grpc/binder/internal/TransactionUtils.java b/binder/src/main/java/io/grpc/binder/internal/TransactionUtils.java index c962554d125..2777a78d4ac 100644 --- a/binder/src/main/java/io/grpc/binder/internal/TransactionUtils.java +++ b/binder/src/main/java/io/grpc/binder/internal/TransactionUtils.java @@ -16,9 +16,13 @@ package io.grpc.binder.internal; +import android.os.Binder; import android.os.Parcel; import io.grpc.MethodDescriptor.MethodType; import io.grpc.Status; +import java.util.logging.Level; +import java.util.logging.Logger; +import io.grpc.binder.internal.LeakSafeOneWayBinder.TransactionHandler; import javax.annotation.Nullable; /** Constants and helpers for managing inbound / outbound transactions. */ @@ -99,4 +103,24 @@ static void fillInFlags(Parcel parcel, int flags) { parcel.writeInt(flags); parcel.setDataPosition(pos); } + + /** + * Decorates the given {@link TransactionHandler} with a wrapper that only forwards transactions + * from the given `allowedCallingUid`. + */ + static TransactionHandler newCallerFilteringHandler( + int allowedCallingUid, TransactionHandler wrapped) { + final Logger logger = Logger.getLogger(TransactionUtils.class.getName()); + return new TransactionHandler() { + @Override + public boolean handleTransaction(int code, Parcel data) { + int callingUid = Binder.getCallingUid(); + if (callingUid != allowedCallingUid) { + logger.log(Level.WARNING, "dropped txn from " + callingUid + " !=" + allowedCallingUid); + return false; + } + return wrapped.handleTransaction(code, data); + } + }; + } } diff --git a/binder/src/test/java/io/grpc/binder/internal/RobolectricBinderTransportTest.java b/binder/src/test/java/io/grpc/binder/internal/RobolectricBinderTransportTest.java index b1822f2f85e..d3d73f0e9eb 100644 --- a/binder/src/test/java/io/grpc/binder/internal/RobolectricBinderTransportTest.java +++ b/binder/src/test/java/io/grpc/binder/internal/RobolectricBinderTransportTest.java @@ -16,12 +16,17 @@ package io.grpc.binder.internal; +import static android.os.IBinder.FLAG_ONEWAY; import static android.os.Process.myUid; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static io.grpc.binder.internal.BinderTransport.REMOTE_UID; import static io.grpc.binder.internal.BinderTransport.SETUP_TRANSPORT; +import static io.grpc.binder.internal.BinderTransport.SHUTDOWN_TRANSPORT; import static io.grpc.binder.internal.BinderTransport.WIRE_FORMAT_VERSION; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -48,6 +53,7 @@ import io.grpc.binder.SecurityPolicies; import io.grpc.binder.internal.SettableAsyncSecurityPolicy.AuthRequest; import io.grpc.internal.AbstractTransportTest; +import io.grpc.internal.ClientTransport; import io.grpc.internal.ClientTransportFactory.ClientTransportOptions; import io.grpc.internal.ConnectionClientTransport; import io.grpc.internal.GrpcUtil; @@ -64,6 +70,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -102,6 +110,9 @@ public final class RobolectricBinderTransportTest extends AbstractTransportTest @Mock AsyncSecurityPolicy mockClientSecurityPolicy; + @Captor + ArgumentCaptor statusCaptor; + ApplicationInfo serverAppInfo; PackageInfo serverPkgInfo; ServiceInfo serviceInfo; @@ -306,6 +317,42 @@ public void clientIgnoresDuplicateSetupTransaction() throws Exception { .isEqualTo(myUid()); } + @Test + public void clientIgnoresTransactionFromNonServerUids() throws Exception { + server.start(serverListener); + client = newClientTransport(server); + startTransport(client, mockClientTransportListener); + + int serverUid = ((ConnectionClientTransport) client).getAttributes().get(REMOTE_UID); + int someOtherUid = 1 + serverUid; + sendShutdownTransportTransactionAsUid(client, someOtherUid); + + // Demonstrate that the transport is still working and that shutdown transaction was ignored. + ClientTransport.PingCallback mockPingCallback = mock(ClientTransport.PingCallback.class); + client.ping(mockPingCallback, directExecutor()); + verify(mockPingCallback, timeout(TIMEOUT_MS)).onSuccess(anyLong()); + + // Try again as the expected uid to demonstrate that this wasn't ignored for some other reason. + sendShutdownTransportTransactionAsUid(client, serverUid); + + verify(mockClientTransportListener, timeout(TIMEOUT_MS)) + .transportShutdown(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.UNAVAILABLE); + assertThat(statusCaptor.getValue().getDescription()).contains("shutdown"); + } + + static void sendShutdownTransportTransactionAsUid(ClientTransport client, int sendingUid) { + int originalUid = Binder.getCallingUid(); + try { + ShadowBinder.setCallingUid(sendingUid); + ((BinderClientTransport) client) + .getIncomingBinderForTesting() + .onTransact(SHUTDOWN_TRANSPORT, null, null, FLAG_ONEWAY); + } finally { + ShadowBinder.setCallingUid(originalUid); + } + } + @Test @Override // We don't quite pass the official/abstract version of this test yet because diff --git a/binder/src/test/java/io/grpc/binder/internal/TransactionUtilsTest.java b/binder/src/test/java/io/grpc/binder/internal/TransactionUtilsTest.java new file mode 100644 index 00000000000..44a3ce3ef26 --- /dev/null +++ b/binder/src/test/java/io/grpc/binder/internal/TransactionUtilsTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2025 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.binder.internal; + +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.binder.internal.TransactionUtils.newCallerFilteringHandler; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Binder; +import android.os.Parcel; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowBinder; + +@RunWith(RobolectricTestRunner.class) +public final class TransactionUtilsTest { + + @Rule public MockitoRule mocks = MockitoJUnit.rule(); + + @Mock LeakSafeOneWayBinder.TransactionHandler mockHandler; + + @Test + public void shouldIgnoreTransactionFromWrongUid() { + Parcel p = Parcel.obtain(); + int originalUid = Binder.getCallingUid(); + try { + when(mockHandler.handleTransaction(eq(1234), same(p))).thenReturn(true); + LeakSafeOneWayBinder.TransactionHandler uid100OnlyHandler = + newCallerFilteringHandler(1000, mockHandler); + + ShadowBinder.setCallingUid(9999); + boolean result = uid100OnlyHandler.handleTransaction(1234, p); + assertThat(result).isFalse(); + verify(mockHandler, never()).handleTransaction(anyInt(), any()); + + ShadowBinder.setCallingUid(1000); + result = uid100OnlyHandler.handleTransaction(1234, p); + assertThat(result).isTrue(); + verify(mockHandler).handleTransaction(1234, p); + } finally { + ShadowBinder.setCallingUid(originalUid); + p.recycle(); + } + } +} From 24e3ba1f40d99739a81775bb16310cdc6e17be64 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:21:15 -0700 Subject: [PATCH 17/22] Pre-factor out the guts of the BinderClientTransport handshake # Conflicts: # binder/src/main/java/io/grpc/binder/internal/BinderTransport.java --- .../main/java/io/grpc/binder/internal/BinderTransport.java | 4 ++++ 1 file changed, 4 insertions(+) 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 6c89c56ffd4..b3987e1ee1f 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -21,12 +21,16 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static io.grpc.binder.internal.TransactionUtils.newCallerFilteringHandler; +import android.content.Context; +import android.content.pm.ServiceInfo; +import android.os.Binder; import android.os.DeadObjectException; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.os.TransactionTooLargeException; import androidx.annotation.BinderThread; +import androidx.annotation.MainThread; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Verify; import com.google.common.util.concurrent.ListenableFuture; From 9ff81d9f460f1fdf933f1d2cb824f1627c0cecec Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:21:15 -0700 Subject: [PATCH 18/22] Pre-factor out the guts of the BinderClientTransport handshake # Conflicts: # binder/src/main/java/io/grpc/binder/internal/BinderTransport.java --- .../src/main/java/io/grpc/binder/internal/BinderTransport.java | 1 + 1 file changed, 1 insertion(+) 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 b3987e1ee1f..b4d4502b23e 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -21,6 +21,7 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static io.grpc.binder.internal.TransactionUtils.newCallerFilteringHandler; +import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; import android.os.Binder; From 72024dfbffea2ede29746f6e1dda9125bc8ac5de Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:31:34 -0700 Subject: [PATCH 19/22] ComponentName --- .../src/main/java/io/grpc/binder/internal/BinderTransport.java | 1 - 1 file changed, 1 deletion(-) 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 b4d4502b23e..b3987e1ee1f 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -21,7 +21,6 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static io.grpc.binder.internal.TransactionUtils.newCallerFilteringHandler; -import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; import android.os.Binder; From 0e2e9bc920ead7a0863db699700436c39b83a703 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:21:15 -0700 Subject: [PATCH 20/22] Pre-factor out the guts of the BinderClientTransport handshake # Conflicts: # binder/src/main/java/io/grpc/binder/internal/BinderTransport.java --- .../src/main/java/io/grpc/binder/internal/BinderTransport.java | 1 + 1 file changed, 1 insertion(+) 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 b3987e1ee1f..b4d4502b23e 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -21,6 +21,7 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static io.grpc.binder.internal.TransactionUtils.newCallerFilteringHandler; +import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; import android.os.Binder; From b60b721c53aa62cb7072ca67fdda4dbb4f10f79f Mon Sep 17 00:00:00 2001 From: John Cormie Date: Tue, 19 Aug 2025 17:31:34 -0700 Subject: [PATCH 21/22] ComponentName --- .../src/main/java/io/grpc/binder/internal/BinderTransport.java | 1 - 1 file changed, 1 deletion(-) 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 b4d4502b23e..b3987e1ee1f 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -21,7 +21,6 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static io.grpc.binder.internal.TransactionUtils.newCallerFilteringHandler; -import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; import android.os.Binder; From 883e47128de3b1fbf46581cb0a6d9a59661aa1cd Mon Sep 17 00:00:00 2001 From: jdcormie Date: Sat, 30 Aug 2025 00:03:33 +0100 Subject: [PATCH 22/22] factor out the decorate() and wrap() calls common to all handshakes --- .../internal/BinderClientTransport.java | 122 +++++++++++++----- 1 file changed, 87 insertions(+), 35 deletions(-) diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java b/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java index 144ad56eec3..16a4f60ef02 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderClientTransport.java @@ -25,6 +25,10 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Process; + +import androidx.annotation.BinderThread; +import androidx.annotation.MainThread; + import com.google.common.base.Ticker; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -146,7 +150,9 @@ void releaseExecutors() { @Override public synchronized void onBound(IBinder binder) { - sendSetupTransaction(binderDecorator.decorate(OneWayBinderProxy.wrap(binder, offloadExecutor))); + OneWayBinderProxy binderProxy = OneWayBinderProxy.wrap(binder, offloadExecutor); + binderProxy = binderDecorator.decorate(binderProxy); + handshake.onBound(binderProxy); } @Override @@ -339,23 +345,8 @@ protected void handleSetupTransport(Parcel parcel) { shutdownInternal( Status.UNAVAILABLE.withDescription("Malformed SETUP_TRANSPORT data"), true); } else { - restrictIncomingBinderToCallsFrom(remoteUid); - attributes = setSecurityAttrs(attributes, remoteUid); - authResultFuture = checkServerAuthorizationAsync(remoteUid); - Futures.addCallback( - authResultFuture, - new FutureCallback() { - @Override - public void onSuccess(Status result) { - handleAuthResult(binder, result); - } - - @Override - public void onFailure(Throwable t) { - handleAuthResult(t); - } - }, - offloadExecutor); + OneWayBinderProxy binderProxy = OneWayBinderProxy.wrap(binder, offloadExecutor); + handshake.handleSetupTransport(binderProxy); } } } @@ -366,29 +357,69 @@ private ListenableFuture checkServerAuthorizationAsync(int remoteUid) { : Futures.submit(() -> securityPolicy.checkAuthorization(remoteUid), offloadExecutor); } - private synchronized void handleAuthResult(IBinder binder, Status authorization) { - if (inState(TransportState.SETUP)) { - if (!authorization.isOk()) { - shutdownInternal(authorization, true); - } else if (!setOutgoingBinder(OneWayBinderProxy.wrap(binder, offloadExecutor))) { - shutdownInternal( - Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true); - } else { - // Check state again, since a failure inside setOutgoingBinder (or a callback it - // triggers), could have shut us down. - if (!isShutdown()) { - setState(TransportState.READY); - attributes = clientTransportListener.filterTransport(attributes); - clientTransportListener.transportReady(); - if (readyTimeoutFuture != null) { - readyTimeoutFuture.cancel(false); - readyTimeoutFuture = null; + class LegacyClientHandshake implements ClientHandshake { + @Override + @MainThread + @GuardedBy("BinderClientTransport.this") + public void onBound(OneWayBinderProxy binder) { + sendSetupTransaction(binder); + } + + @Override + @BinderThread + @GuardedBy("BinderClientTransport.this") + public void handleSetupTransport(OneWayBinderProxy binder) { + int remoteUid = Binder.getCallingUid(); + attributes = setSecurityAttrs(attributes, remoteUid); + authResultFuture = checkServerAuthorizationAsync(remoteUid); + Futures.addCallback( + authResultFuture, + new FutureCallback() { + @Override + public void onSuccess(Status result) { + synchronized (BinderClientTransport.this) { + handleAuthResult(binder, result); + } + } + + @Override + public void onFailure(Throwable t) { + handleAuthResult(t); + } + }, + offloadExecutor); + } + + @GuardedBy("BinderClientTransport.this") + private void handleAuthResult(OneWayBinderProxy binder, Status authorization) { + if (inState(TransportState.SETUP)) { + if (!authorization.isOk()) { + shutdownInternal(authorization, true); + } else if (!setOutgoingBinder(binder)) { + shutdownInternal( + Status.UNAVAILABLE.withDescription("Failed to observe outgoing binder"), true); + } else { + // Check state again, since a failure inside setOutgoingBinder (or a callback it + // triggers), could have shut us down. + if (!isShutdown()) { + onHandshakeComplete(); } } } } } + @GuardedBy("this") + private void onHandshakeComplete() { + setState(TransportState.READY); + attributes = clientTransportListener.filterTransport(attributes); + clientTransportListener.transportReady(); + if (readyTimeoutFuture != null) { + readyTimeoutFuture.cancel(false); + readyTimeoutFuture = null; + } + } + private synchronized void handleAuthResult(Throwable t) { shutdownInternal( Status.INTERNAL.withDescription("Could not evaluate SecurityPolicy").withCause(t), true); @@ -400,6 +431,27 @@ protected void handlePingResponse(Parcel parcel) { pingTracker.onPingResponse(parcel.readInt()); } + /** + * An abstraction of the client handshake, used to transition off a problematic legacy approach. + */ + interface ClientHandshake { + /** + * Notifies the implementation that the binding has succeeded and we are now connected to the + * server 'endpointBinder'. + */ + @GuardedBy("this") + @MainThread + void onBound(OneWayBinderProxy endpointBinder); + + /** + * Notifies the implementation that we've received a valid SETUP_TRANSPORT transaction from a + * server that can be reached at 'serverBinder'. + */ + @GuardedBy("this") + @BinderThread + void handleSetupTransport(OneWayBinderProxy serverBinder); + } + private static ClientStream newFailingClientStream( Status failure, Attributes attributes, Metadata headers, ClientStreamTracer[] tracers) { StatsTraceContext statsTraceContext =