+ * Handshaker service accepts a stream of handshaker request, returning a + * stream of handshaker response. Client is expected to send exactly one + * message with either client_start or server_start followed by one or more + * messages with next. Each time client sends a request, the handshaker + * service expects to respond. Client does not have to wait for service's + * response before sending next request. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * Gets the backend distribution for RPCs sent by a test client. + *+ */ + public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetClientStatsMethod(), getCallOptions(), request); + } + + /** + *
+ * Gets the accumulated stats for RPCs sent by a test client. + *+ */ + public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service LoadBalancerStatsService. + *
+ * A service used to obtain stats for verifying LB behavior. + *+ */ public static final class LoadBalancerStatsServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Returns the values of all the gauges that are currently being maintained by + * the service + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall, io.grpc.testing.integration.Metrics.GaugeResponse> + getAllGauges(io.grpc.testing.integration.Metrics.EmptyMessage request) { + return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( + getChannel(), getGetAllGaugesMethod(), getCallOptions(), request); + } + + /** + *
+ * Returns the value of one gauge + *+ */ + public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetGaugeMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service MetricsService. + */ public static final class MetricsServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A service used to control reconnect server. + *+ */ public static final class ReconnectServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * One empty request followed by one empty response. + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getEmptyCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by one response. + *+ */ + public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnaryCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by one response. Response has cache control + * headers set such that a caching HTTP proxy (such as GFE) can + * satisfy subsequent requests. + *+ */ + public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by a sequence of responses (streamed download). + * The server returns the payload with client desired type and sizes. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall, io.grpc.testing.integration.Messages.StreamingOutputCallResponse> + streamingOutputCall(io.grpc.testing.integration.Messages.StreamingOutputCallRequest request) { + return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( + getChannel(), getStreamingOutputCallMethod(), getCallOptions(), request); + } + + /** + *
+ * A sequence of requests followed by one response (streamed upload). + * The server returns the aggregated size of client payload as the result. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * A sequence of requests with each request served by the server immediately. + * As one request could lead to multiple responses, this interface + * demonstrates the idea of full duplexing. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * A sequence of requests followed by a sequence of responses. + * The server buffers all the client requests and then serves them in order. A + * stream of responses are returned to the client when the server starts with + * first request. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * The test server will not implement this method. It will be used + * to test the behavior when clients call unimplemented methods. + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnimplementedCallMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service TestService. + *
+ * A simple service to test the various types of RPCs and experiment with + * performance with various types of payload. + *+ */ public static final class TestServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A call that no server should implement + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnimplementedCallMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service UnimplementedService. + *
+ * A simple service NOT implemented at servers so clients can test for + * that case. + *+ */ public static final class UnimplementedServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Update the tes client's configuration. + *+ */ + public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getConfigureMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service XdsUpdateClientConfigureService. + *
+ * A service to dynamically update the configuration of an xDS test client. + *+ */ public static final class XdsUpdateClientConfigureServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A service to remotely control health status of an xDS test server. + *+ */ public static final class XdsUpdateHealthServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Gets the backend distribution for RPCs sent by a test client. + *+ */ + public io.grpc.testing.integration.Messages.LoadBalancerStatsResponse getClientStats(io.grpc.testing.integration.Messages.LoadBalancerStatsRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetClientStatsMethod(), getCallOptions(), request); + } + + /** + *
+ * Gets the accumulated stats for RPCs sent by a test client. + *+ */ + public io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsResponse getClientAccumulatedStats(io.grpc.testing.integration.Messages.LoadBalancerAccumulatedStatsRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetClientAccumulatedStatsMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service LoadBalancerStatsService. + *
+ * A service used to obtain stats for verifying LB behavior. + *+ */ public static final class LoadBalancerStatsServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Returns the values of all the gauges that are currently being maintained by + * the service + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall, io.grpc.testing.integration.Metrics.GaugeResponse> + getAllGauges(io.grpc.testing.integration.Metrics.EmptyMessage request) { + return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( + getChannel(), getGetAllGaugesMethod(), getCallOptions(), request); + } + + /** + *
+ * Returns the value of one gauge + *+ */ + public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetGaugeMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service MetricsService. + */ public static final class MetricsServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A service used to control reconnect server. + *+ */ public static final class ReconnectServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * One empty request followed by one empty response. + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getEmptyCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by one response. + *+ */ + public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnaryCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by one response. Response has cache control + * headers set such that a caching HTTP proxy (such as GFE) can + * satisfy subsequent requests. + *+ */ + public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request); + } + + /** + *
+ * One request followed by a sequence of responses (streamed download). + * The server returns the payload with client desired type and sizes. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall, io.grpc.testing.integration.Messages.StreamingOutputCallResponse> + streamingOutputCall(io.grpc.testing.integration.Messages.StreamingOutputCallRequest request) { + return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall( + getChannel(), getStreamingOutputCallMethod(), getCallOptions(), request); + } + + /** + *
+ * A sequence of requests followed by one response (streamed upload). + * The server returns the aggregated size of client payload as the result. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * A sequence of requests with each request served by the server immediately. + * As one request could lead to multiple responses, this interface + * demonstrates the idea of full duplexing. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * A sequence of requests followed by a sequence of responses. + * The server buffers all the client requests and then serves them in order. A + * stream of responses are returned to the client when the server starts with + * first request. + *+ */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall
+ * The test server will not implement this method. It will be used + * to test the behavior when clients call unimplemented methods. + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnimplementedCallMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service TestService. + *
+ * A simple service to test the various types of RPCs and experiment with + * performance with various types of payload. + *+ */ public static final class TestServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A call that no server should implement + *+ */ + public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUnimplementedCallMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service UnimplementedService. + *
+ * A simple service NOT implemented at servers so clients can test for + * that case. + *+ */ public static final class UnimplementedServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * Update the tes client's configuration. + *+ */ + public io.grpc.testing.integration.Messages.ClientConfigureResponse configure(io.grpc.testing.integration.Messages.ClientConfigureRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getConfigureMethod(), getCallOptions(), request); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service XdsUpdateClientConfigureService. + *
+ * A service to dynamically update the configuration of an xDS test client. + *+ */ public static final class XdsUpdateClientConfigureServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
+ * A service to remotely control health status of an xDS test server. + *+ */ public static final class XdsUpdateHealthServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub
It is called for each individual RPC, within the {@link Context} of the call, before the * stream is about to be created on a transport. Implementations should not block in this * method. If metadata is not immediately available, e.g., needs to be fetched from network, the - * implementation may give the {@code applier} to an asynchronous task which will eventually call + * implementation may give the {@code appExecutor} an asynchronous task which will eventually call * the {@code applier}. The RPC proceeds only after the {@code applier} is called. * * @param requestInfo request-related information diff --git a/api/src/main/java/io/grpc/CallOptions.java b/api/src/main/java/io/grpc/CallOptions.java index 87493d2ba0b..800bdfb6c90 100644 --- a/api/src/main/java/io/grpc/CallOptions.java +++ b/api/src/main/java/io/grpc/CallOptions.java @@ -17,16 +17,18 @@ package io.grpc; import static com.google.common.base.Preconditions.checkArgument; +import static io.grpc.TimeUtils.convertToNanos; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.CheckReturnValue; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -79,6 +81,8 @@ public final class CallOptions { private final Integer maxInboundMessageSize; @Nullable private final Integer maxOutboundMessageSize; + @Nullable + private final Integer onReadyThreshold; private CallOptions(Builder builder) { this.deadline = builder.deadline; @@ -91,6 +95,7 @@ private CallOptions(Builder builder) { this.waitForReady = builder.waitForReady; this.maxInboundMessageSize = builder.maxInboundMessageSize; this.maxOutboundMessageSize = builder.maxOutboundMessageSize; + this.onReadyThreshold = builder.onReadyThreshold; } static class Builder { @@ -105,6 +110,7 @@ static class Builder { Boolean waitForReady; Integer maxInboundMessageSize; Integer maxOutboundMessageSize; + Integer onReadyThreshold; private CallOptions build() { return new CallOptions(this); @@ -172,6 +178,11 @@ public CallOptions withDeadlineAfter(long duration, TimeUnit unit) { return withDeadline(Deadline.after(duration, unit)); } + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657") + public CallOptions withDeadlineAfter(Duration duration) { + return withDeadlineAfter(convertToNanos(duration), TimeUnit.NANOSECONDS); + } + /** * Returns the deadline or {@code null} if the deadline is not set. */ @@ -203,6 +214,46 @@ public CallOptions withoutWaitForReady() { return builder.build(); } + /** + * Specifies how many bytes must be queued before the call is + * considered not ready to send more messages. + * + * @param numBytes The number of bytes that must be queued. Must be a + * positive integer. + */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11021") + public CallOptions withOnReadyThreshold(int numBytes) { + checkArgument(numBytes > 0, "numBytes must be positive: %s", numBytes); + Builder builder = toBuilder(this); + builder.onReadyThreshold = numBytes; + return builder.build(); + } + + /** + * Resets to the default number of bytes that must be queued before the + * call will leave the + * 'wait for ready' state. + */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11021") + public CallOptions clearOnReadyThreshold() { + Builder builder = toBuilder(this); + builder.onReadyThreshold = null; + return builder.build(); + } + + /** + * Returns to the default number of bytes that must be queued before the + * call will leave the + * 'wait for ready' state. + * + * @return null if the default threshold is used. + */ + @Nullable + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11021") + public Integer getOnReadyThreshold() { + return onReadyThreshold; + } + /** * Returns the compressor's name. */ @@ -468,6 +519,7 @@ private static Builder toBuilder(CallOptions other) { builder.waitForReady = other.waitForReady; builder.maxInboundMessageSize = other.maxInboundMessageSize; builder.maxOutboundMessageSize = other.maxOutboundMessageSize; + builder.onReadyThreshold = other.onReadyThreshold; return builder; } @@ -483,6 +535,7 @@ public String toString() { .add("waitForReady", isWaitForReady()) .add("maxInboundMessageSize", maxInboundMessageSize) .add("maxOutboundMessageSize", maxOutboundMessageSize) + .add("onReadyThreshold", onReadyThreshold) .add("streamTracerFactories", streamTracerFactories) .toString(); } diff --git a/api/src/main/java/io/grpc/CallbackMetricInstrument.java b/api/src/main/java/io/grpc/CallbackMetricInstrument.java new file mode 100644 index 00000000000..1d66d5340ed --- /dev/null +++ b/api/src/main/java/io/grpc/CallbackMetricInstrument.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024 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; + +/** + * Tagging interface for MetricInstruments that can be used with batch callbacks. + */ +@Internal +public interface CallbackMetricInstrument extends MetricInstrument {} diff --git a/api/src/main/java/io/grpc/Channel.java b/api/src/main/java/io/grpc/Channel.java index 60ff76ff082..e2787eb2f26 100644 --- a/api/src/main/java/io/grpc/Channel.java +++ b/api/src/main/java/io/grpc/Channel.java @@ -16,7 +16,6 @@ package io.grpc; -import javax.annotation.concurrent.ThreadSafe; /** * A virtual connection to a conceptual endpoint, to perform RPCs. A channel is free to have zero or @@ -29,8 +28,9 @@ * implementations using {@link ClientInterceptor}. It is expected that most application * code will not use this class directly but rather work with stubs that have been bound to a * Channel that was decorated during application initialization. + * + *
This class is thread-safe. */ -@ThreadSafe public abstract class Channel { /** * Create a {@link ClientCall} to the remote operation specified by the given diff --git a/api/src/main/java/io/grpc/ChannelLogger.java b/api/src/main/java/io/grpc/ChannelLogger.java index ce654ec9d5b..2cdf4c84724 100644 --- a/api/src/main/java/io/grpc/ChannelLogger.java +++ b/api/src/main/java/io/grpc/ChannelLogger.java @@ -16,15 +16,15 @@ package io.grpc; -import javax.annotation.concurrent.ThreadSafe; /** * A Channel-specific logger provided by GRPC library to {@link LoadBalancer} implementations. * Information logged here goes to Channelz, and to the Java logger of this class * as well. + * + *
This class is thread-safe.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5029")
-@ThreadSafe
public abstract class ChannelLogger {
/**
* Log levels. See the table below for the mapping from the ChannelLogger levels to Channelz
diff --git a/api/src/main/java/io/grpc/ClientCall.java b/api/src/main/java/io/grpc/ClientCall.java
index df9e15001e1..c915c8beaac 100644
--- a/api/src/main/java/io/grpc/ClientCall.java
+++ b/api/src/main/java/io/grpc/ClientCall.java
@@ -67,7 +67,7 @@
* manner, and notifies gRPC library to receive additional response after one is consumed by
* a fictional processResponse().
*
- *
+ *
* call = channel.newCall(bidiStreamingMethod, callOptions);
* listener = new ClientCall.Listener<FooResponse>() {
* @Override
diff --git a/api/src/main/java/io/grpc/ClientInterceptor.java b/api/src/main/java/io/grpc/ClientInterceptor.java
index c27c31c8474..d6c8cd7e6fb 100644
--- a/api/src/main/java/io/grpc/ClientInterceptor.java
+++ b/api/src/main/java/io/grpc/ClientInterceptor.java
@@ -16,7 +16,6 @@
package io.grpc;
-import javax.annotation.concurrent.ThreadSafe;
/**
* Interface for intercepting outgoing calls before they are dispatched by a {@link Channel}.
@@ -37,8 +36,10 @@
* without completing the previous ones first. Refer to the
* {@link io.grpc.ClientCall.Listener ClientCall.Listener} docs for more details regarding thread
* safety of the returned listener.
+ *
+ * This is thread-safe and should be considered
+ * for the errorprone ThreadSafe annotation in the future.
*/
-@ThreadSafe
public interface ClientInterceptor {
/**
* Intercept {@link ClientCall} creation by the {@code next} {@link Channel}.
diff --git a/api/src/main/java/io/grpc/ClientStreamTracer.java b/api/src/main/java/io/grpc/ClientStreamTracer.java
index cb2f5538e34..8e11e781e7c 100644
--- a/api/src/main/java/io/grpc/ClientStreamTracer.java
+++ b/api/src/main/java/io/grpc/ClientStreamTracer.java
@@ -19,13 +19,13 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.MoreObjects;
-import javax.annotation.concurrent.ThreadSafe;
/**
* {@link StreamTracer} for the client-side.
+ *
+ *
This class is thread-safe.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
-@ThreadSafe
public abstract class ClientStreamTracer extends StreamTracer {
/**
* Indicates how long the call was delayed, in nanoseconds, due to waiting for name resolution
@@ -70,15 +70,35 @@ public void inboundHeaders() {
}
/**
- * Trailing metadata has been received from the server.
+ * Headers has been received from the server. This method does not pass ownership to {@code
+ * headers}, so implementations must not access the metadata after returning. Modifications to the
+ * metadata within this method will be seen by interceptors and the application.
+ *
+ * @param headers the received header metadata
+ */
+ public void inboundHeaders(Metadata headers) {
+ inboundHeaders();
+ }
+
+ /**
+ * Trailing metadata has been received from the server. This method does not pass ownership to
+ * {@code trailers}, so implementations must not access the metadata after returning.
+ * Modifications to the metadata within this method will be seen by interceptors and the
+ * application.
*
- * @param trailers the mutable trailing metadata. Modifications to it will be seen by
- * interceptors and the application.
+ * @param trailers the received trailing metadata
* @since 1.17.0
*/
public void inboundTrailers(Metadata trailers) {
}
+ /**
+ * Information providing context to the call became available.
+ */
+ @Internal
+ public void addOptionalLabel(String key, String value) {
+ }
+
/**
* Factory class for {@link ClientStreamTracer}.
*/
@@ -112,12 +132,15 @@ public static final class StreamInfo {
private final CallOptions callOptions;
private final int previousAttempts;
private final boolean isTransparentRetry;
+ private final boolean isHedging;
StreamInfo(
- CallOptions callOptions, int previousAttempts, boolean isTransparentRetry) {
+ CallOptions callOptions, int previousAttempts, boolean isTransparentRetry,
+ boolean isHedging) {
this.callOptions = checkNotNull(callOptions, "callOptions");
this.previousAttempts = previousAttempts;
this.isTransparentRetry = isTransparentRetry;
+ this.isHedging = isHedging;
}
/**
@@ -145,6 +168,15 @@ public boolean isTransparentRetry() {
return isTransparentRetry;
}
+ /**
+ * Whether the stream is hedging.
+ *
+ * @since 1.74.0
+ */
+ public boolean isHedging() {
+ return isHedging;
+ }
+
/**
* Converts this StreamInfo into a new Builder.
*
@@ -154,7 +186,9 @@ public Builder toBuilder() {
return new Builder()
.setCallOptions(callOptions)
.setPreviousAttempts(previousAttempts)
- .setIsTransparentRetry(isTransparentRetry);
+ .setIsTransparentRetry(isTransparentRetry)
+ .setIsHedging(isHedging);
+
}
/**
@@ -172,6 +206,7 @@ public String toString() {
.add("callOptions", callOptions)
.add("previousAttempts", previousAttempts)
.add("isTransparentRetry", isTransparentRetry)
+ .add("isHedging", isHedging)
.toString();
}
@@ -184,6 +219,7 @@ public static final class Builder {
private CallOptions callOptions = CallOptions.DEFAULT;
private int previousAttempts;
private boolean isTransparentRetry;
+ private boolean isHedging;
Builder() {
}
@@ -216,11 +252,21 @@ public Builder setIsTransparentRetry(boolean isTransparentRetry) {
return this;
}
+ /**
+ * Sets whether the stream is hedging.
+ *
+ * @since 1.74.0
+ */
+ public Builder setIsHedging(boolean isHedging) {
+ this.isHedging = isHedging;
+ return this;
+ }
+
/**
* Builds a new StreamInfo.
*/
public StreamInfo build() {
- return new StreamInfo(callOptions, previousAttempts, isTransparentRetry);
+ return new StreamInfo(callOptions, previousAttempts, isTransparentRetry, isHedging);
}
}
}
diff --git a/api/src/main/java/io/grpc/Configurator.java b/api/src/main/java/io/grpc/Configurator.java
new file mode 100644
index 00000000000..90468769a8d
--- /dev/null
+++ b/api/src/main/java/io/grpc/Configurator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 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;
+
+/**
+ * Provides hooks for modifying gRPC channels and servers during their construction.
+ */
+interface Configurator {
+ /**
+ * Allows implementations to modify the channel builder.
+ *
+ * @param channelBuilder the channel builder being constructed
+ */
+ default void configureChannelBuilder(ManagedChannelBuilder> channelBuilder) {}
+
+ /**
+ * Allows implementations to modify the server builder.
+ *
+ * @param serverBuilder the server builder being constructed
+ */
+ default void configureServerBuilder(ServerBuilder> serverBuilder) {}
+}
diff --git a/api/src/main/java/io/grpc/ConfiguratorRegistry.java b/api/src/main/java/io/grpc/ConfiguratorRegistry.java
new file mode 100644
index 00000000000..19d6703d308
--- /dev/null
+++ b/api/src/main/java/io/grpc/ConfiguratorRegistry.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2024 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 com.google.errorprone.annotations.concurrent.GuardedBy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A registry for {@link Configurator} instances.
+ *
+ *
This class is responsible for maintaining a list of configurators and providing access to
+ * them. The default registry can be obtained using {@link #getDefaultRegistry()}.
+ */
+final class ConfiguratorRegistry {
+ private static ConfiguratorRegistry instance;
+
+ @GuardedBy("this")
+ private boolean wasConfiguratorsSet;
+ @GuardedBy("this")
+ private List configurators = Collections.emptyList();
+ @GuardedBy("this")
+ private int configuratorsCallCountBeforeSet = 0;
+
+ ConfiguratorRegistry() {}
+
+ /**
+ * Returns the default global instance of the configurator registry.
+ */
+ public static synchronized ConfiguratorRegistry getDefaultRegistry() {
+ if (instance == null) {
+ instance = new ConfiguratorRegistry();
+ }
+ return instance;
+ }
+
+ /**
+ * Sets the configurators in this registry. This method can only be called once.
+ *
+ * @param configurators the configurators to set
+ * @throws IllegalStateException if this method is called more than once
+ */
+ public synchronized void setConfigurators(List extends Configurator> configurators) {
+ if (wasConfiguratorsSet) {
+ throw new IllegalStateException("Configurators are already set");
+ }
+ this.configurators = Collections.unmodifiableList(new ArrayList<>(configurators));
+ wasConfiguratorsSet = true;
+ }
+
+ /**
+ * Returns a list of the configurators in this registry.
+ */
+ public synchronized List getConfigurators() {
+ if (!wasConfiguratorsSet) {
+ configuratorsCallCountBeforeSet++;
+ }
+ return configurators;
+ }
+
+ /**
+ * Returns the number of times getConfigurators() was called before
+ * setConfigurators() was successfully invoked.
+ */
+ public synchronized int getConfiguratorsCallCountBeforeSet() {
+ return configuratorsCallCountBeforeSet;
+ }
+
+ public synchronized boolean wasSetConfiguratorsCalled() {
+ return wasConfiguratorsSet;
+ }
+}
diff --git a/api/src/main/java/io/grpc/ConnectivityState.java b/api/src/main/java/io/grpc/ConnectivityState.java
index 677039b2517..a7407efb2e9 100644
--- a/api/src/main/java/io/grpc/ConnectivityState.java
+++ b/api/src/main/java/io/grpc/ConnectivityState.java
@@ -20,7 +20,7 @@
* The connectivity states.
*
* @see
- * more information
+ * more information
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4359")
public enum ConnectivityState {
diff --git a/api/src/main/java/io/grpc/DoubleCounterMetricInstrument.java b/api/src/main/java/io/grpc/DoubleCounterMetricInstrument.java
new file mode 100644
index 00000000000..3f07d83d58f
--- /dev/null
+++ b/api/src/main/java/io/grpc/DoubleCounterMetricInstrument.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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 double-valued counter metric instrument.
+ */
+@Internal
+public final class DoubleCounterMetricInstrument extends PartialMetricInstrument {
+ public DoubleCounterMetricInstrument(int index, String name, String description, String unit,
+ List requiredLabelKeys, List optionalLabelKeys, boolean enableByDefault) {
+ super(index, name, description, unit, requiredLabelKeys, optionalLabelKeys, enableByDefault);
+ }
+}
diff --git a/api/src/main/java/io/grpc/DoubleHistogramMetricInstrument.java b/api/src/main/java/io/grpc/DoubleHistogramMetricInstrument.java
new file mode 100644
index 00000000000..9039a8c62c1
--- /dev/null
+++ b/api/src/main/java/io/grpc/DoubleHistogramMetricInstrument.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 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 double-valued histogram metric instrument.
+ */
+@Internal
+public final class DoubleHistogramMetricInstrument extends PartialMetricInstrument {
+ private final List bucketBoundaries;
+
+ public DoubleHistogramMetricInstrument(int index, String name, String description, String unit,
+ List bucketBoundaries, List requiredLabelKeys, List optionalLabelKeys,
+ boolean enableByDefault) {
+ super(index, name, description, unit, requiredLabelKeys, optionalLabelKeys, enableByDefault);
+ this.bucketBoundaries = bucketBoundaries;
+ }
+
+ public List getBucketBoundaries() {
+ return bucketBoundaries;
+ }
+}
diff --git a/api/src/main/java/io/grpc/EquivalentAddressGroup.java b/api/src/main/java/io/grpc/EquivalentAddressGroup.java
index 4b3db006684..2dd52fe7f21 100644
--- a/api/src/main/java/io/grpc/EquivalentAddressGroup.java
+++ b/api/src/main/java/io/grpc/EquivalentAddressGroup.java
@@ -50,6 +50,26 @@ 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");
+ /**
+ * The backend service associated with this EquivalentAddressGroup.
+ */
+ @Attr
+ static final Attributes.Key ATTR_BACKEND_SERVICE =
+ Attributes.Key.create("io.grpc.EquivalentAddressGroup.BACKEND_SERVICE");
+ /**
+ * Endpoint weight for load balancing purposes. While the type is Long, it must be a valid uint32.
+ * Must not be zero. The weight is proportional to the other endpoints; if an endpoint's weight is
+ * twice that of another endpoint, it is intended to receive twice the load.
+ */
+ @Attr
+ static final Attributes.Key ATTR_WEIGHT =
+ Attributes.Key.create("io.grpc.EquivalentAddressGroup.ATTR_WEIGHT");
+
private final List addrs;
private final Attributes attrs;
@@ -108,7 +128,9 @@ public Attributes getAttributes() {
@Override
public String toString() {
- // TODO(zpencer): Summarize return value if addr is very large
+ // EquivalentAddressGroup is intended to contain a small number of addresses for the same
+ // endpoint(e.g., IPv4/IPv6). Aggregating many groups into a single EquivalentAddressGroup
+ // is no longer done, so this no longer needs summarization.
return "[" + addrs + "/" + attrs + "]";
}
diff --git a/api/src/main/java/io/grpc/FeatureFlags.java b/api/src/main/java/io/grpc/FeatureFlags.java
new file mode 100644
index 00000000000..0e414ed7b31
--- /dev/null
+++ b/api/src/main/java/io/grpc/FeatureFlags.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2026 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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+
+class FeatureFlags {
+ private static boolean enableRfc3986Uris = getFlag("GRPC_ENABLE_RFC3986_URIS", false);
+
+ /** Whether to parse targets as RFC 3986 URIs (true), or use {@link java.net.URI} (false). */
+ @VisibleForTesting
+ static boolean setRfc3986UrisEnabled(boolean value) {
+ boolean prevValue = enableRfc3986Uris;
+ enableRfc3986Uris = value;
+ return prevValue;
+ }
+
+ /** Whether to parse targets as RFC 3986 URIs (true), or use {@link java.net.URI} (false). */
+ static boolean getRfc3986UrisEnabled() {
+ return enableRfc3986Uris;
+ }
+
+ static boolean getFlag(String envVarName, boolean enableByDefault) {
+ String envVar = System.getenv(envVarName);
+ if (envVar == null) {
+ envVar = System.getProperty(envVarName);
+ }
+ if (envVar != null) {
+ envVar = envVar.trim();
+ }
+ if (enableByDefault) {
+ return Strings.isNullOrEmpty(envVar) || Boolean.parseBoolean(envVar);
+ } else {
+ return !Strings.isNullOrEmpty(envVar) && Boolean.parseBoolean(envVar);
+ }
+ }
+
+ private FeatureFlags() {}
+}
diff --git a/api/src/main/java/io/grpc/ForwardingChannelBuilder2.java b/api/src/main/java/io/grpc/ForwardingChannelBuilder2.java
index 12ed275c06e..78fe730d91a 100644
--- a/api/src/main/java/io/grpc/ForwardingChannelBuilder2.java
+++ b/api/src/main/java/io/grpc/ForwardingChannelBuilder2.java
@@ -94,6 +94,12 @@ public T intercept(ClientInterceptor... interceptors) {
return thisT();
}
+ @Override
+ protected T interceptWithTarget(InterceptorFactory factory) {
+ delegate().interceptWithTarget(factory);
+ return thisT();
+ }
+
@Override
public T addTransportFilter(ClientTransportFilter transportFilter) {
delegate().addTransportFilter(transportFilter);
@@ -251,6 +257,18 @@ public T disableServiceConfigLookUp() {
return thisT();
}
+ @Override
+ protected T addMetricSink(MetricSink metricSink) {
+ delegate().addMetricSink(metricSink);
+ return thisT();
+ }
+
+ @Override
+ public T setNameResolverArg(NameResolver.Args.Key key, X value) {
+ delegate().setNameResolverArg(key, value);
+ return thisT();
+ }
+
/**
* Returns the {@link ManagedChannel} built by the delegate by default. Overriding method can
* return different value.
diff --git a/api/src/main/java/io/grpc/ForwardingServerBuilder.java b/api/src/main/java/io/grpc/ForwardingServerBuilder.java
index 9cef7cfa331..d1f183dd824 100644
--- a/api/src/main/java/io/grpc/ForwardingServerBuilder.java
+++ b/api/src/main/java/io/grpc/ForwardingServerBuilder.java
@@ -201,6 +201,12 @@ public Server build() {
return delegate().build();
}
+ @Override
+ public T addMetricSink(MetricSink metricSink) {
+ delegate().addMetricSink(metricSink);
+ return thisT();
+ }
+
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
diff --git a/api/src/main/java/io/grpc/GlobalInterceptors.java b/api/src/main/java/io/grpc/GlobalInterceptors.java
deleted file mode 100644
index e5fd86170f0..00000000000
--- a/api/src/main/java/io/grpc/GlobalInterceptors.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2022 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 static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/** The collection of global interceptors and global server stream tracers. */
-@Internal
-final class GlobalInterceptors {
- private static List clientInterceptors = null;
- private static List serverInterceptors = null;
- private static List serverStreamTracerFactories =
- null;
- private static boolean isGlobalInterceptorsTracersSet;
- private static boolean isGlobalInterceptorsTracersGet;
-
- // Prevent instantiation
- private GlobalInterceptors() {}
-
- /**
- * Sets the list of global interceptors and global server stream tracers.
- *
- * If {@code setInterceptorsTracers()} is called again, this method will throw {@link
- * IllegalStateException}.
- *
- *
It is only safe to call early. This method throws {@link IllegalStateException} after any of
- * the get calls [{@link #getClientInterceptors()}, {@link #getServerInterceptors()} or {@link
- * #getServerStreamTracerFactories()}] has been called, in order to limit changes to the result of
- * {@code setInterceptorsTracers()}.
- *
- * @param clientInterceptorList list of {@link ClientInterceptor} that make up global Client
- * Interceptors.
- * @param serverInterceptorList list of {@link ServerInterceptor} that make up global Server
- * Interceptors.
- * @param serverStreamTracerFactoryList list of {@link ServerStreamTracer.Factory} that make up
- * global ServerStreamTracer factories.
- */
- static synchronized void setInterceptorsTracers(
- List clientInterceptorList,
- List serverInterceptorList,
- List serverStreamTracerFactoryList) {
- if (isGlobalInterceptorsTracersGet) {
- throw new IllegalStateException("Set cannot be called after any get call");
- }
- if (isGlobalInterceptorsTracersSet) {
- throw new IllegalStateException("Global interceptors and tracers are already set");
- }
- checkNotNull(clientInterceptorList);
- checkNotNull(serverInterceptorList);
- checkNotNull(serverStreamTracerFactoryList);
- clientInterceptors = Collections.unmodifiableList(new ArrayList<>(clientInterceptorList));
- serverInterceptors = Collections.unmodifiableList(new ArrayList<>(serverInterceptorList));
- serverStreamTracerFactories =
- Collections.unmodifiableList(new ArrayList<>(serverStreamTracerFactoryList));
- isGlobalInterceptorsTracersSet = true;
- }
-
- /** Returns the list of global {@link ClientInterceptor}. If not set, this returns null. */
- static synchronized List getClientInterceptors() {
- isGlobalInterceptorsTracersGet = true;
- return clientInterceptors;
- }
-
- /** Returns list of global {@link ServerInterceptor}. If not set, this returns null. */
- static synchronized List getServerInterceptors() {
- isGlobalInterceptorsTracersGet = true;
- return serverInterceptors;
- }
-
- /** Returns list of global {@link ServerStreamTracer.Factory}. If not set, this returns null. */
- static synchronized List getServerStreamTracerFactories() {
- isGlobalInterceptorsTracersGet = true;
- return serverStreamTracerFactories;
- }
-}
diff --git a/api/src/main/java/io/grpc/Grpc.java b/api/src/main/java/io/grpc/Grpc.java
index baa9f5f0ab6..a45c613fd18 100644
--- a/api/src/main/java/io/grpc/Grpc.java
+++ b/api/src/main/java/io/grpc/Grpc.java
@@ -56,6 +56,13 @@ private Grpc() {
public static final Attributes.Key TRANSPORT_ATTR_SSL_SESSION =
Attributes.Key.create("io.grpc.Grpc.TRANSPORT_ATTR_SSL_SESSION");
+ /**
+ * The value for the custom label of per-RPC metrics. Defaults to empty string when unset. Must
+ * not be set to {@code null}.
+ */
+ public static final CallOptions.Key CALL_OPTION_CUSTOM_LABEL =
+ CallOptions.Key.createWithDefault("io.grpc.Grpc.CALL_OPTION_CUSTOM_LABEL", "");
+
/**
* Annotation for transport attributes. It follows the annotation semantics defined
* by {@link Attributes}.
diff --git a/api/src/main/java/io/grpc/HandlerRegistry.java b/api/src/main/java/io/grpc/HandlerRegistry.java
index 4aaf0114fb1..148573ada9a 100644
--- a/api/src/main/java/io/grpc/HandlerRegistry.java
+++ b/api/src/main/java/io/grpc/HandlerRegistry.java
@@ -19,12 +19,12 @@
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
-import javax.annotation.concurrent.ThreadSafe;
/**
* Registry of services and their methods used by servers to dispatching incoming calls.
+ *
+ * This class is thread-safe.
*/
-@ThreadSafe
public abstract class HandlerRegistry {
/**
diff --git a/api/src/main/java/io/grpc/HttpConnectProxiedSocketAddress.java b/api/src/main/java/io/grpc/HttpConnectProxiedSocketAddress.java
index d59c53db1d1..0df8dc452c1 100644
--- a/api/src/main/java/io/grpc/HttpConnectProxiedSocketAddress.java
+++ b/api/src/main/java/io/grpc/HttpConnectProxiedSocketAddress.java
@@ -23,6 +23,9 @@
import com.google.common.base.Objects;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import javax.annotation.Nullable;
/**
@@ -33,6 +36,8 @@ public final class HttpConnectProxiedSocketAddress extends ProxiedSocketAddress
private final SocketAddress proxyAddress;
private final InetSocketAddress targetAddress;
+ @SuppressWarnings("serial")
+ private final Map headers;
@Nullable
private final String username;
@Nullable
@@ -41,6 +46,7 @@ public final class HttpConnectProxiedSocketAddress extends ProxiedSocketAddress
private HttpConnectProxiedSocketAddress(
SocketAddress proxyAddress,
InetSocketAddress targetAddress,
+ Map headers,
@Nullable String username,
@Nullable String password) {
checkNotNull(proxyAddress, "proxyAddress");
@@ -53,6 +59,7 @@ private HttpConnectProxiedSocketAddress(
}
this.proxyAddress = proxyAddress;
this.targetAddress = targetAddress;
+ this.headers = headers;
this.username = username;
this.password = password;
}
@@ -87,6 +94,14 @@ public InetSocketAddress getTargetAddress() {
return targetAddress;
}
+ /**
+ * Returns the custom HTTP headers to be sent during the HTTP CONNECT handshake.
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/12479")
+ public Map getHeaders() {
+ return headers;
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof HttpConnectProxiedSocketAddress)) {
@@ -95,13 +110,14 @@ public boolean equals(Object o) {
HttpConnectProxiedSocketAddress that = (HttpConnectProxiedSocketAddress) o;
return Objects.equal(proxyAddress, that.proxyAddress)
&& Objects.equal(targetAddress, that.targetAddress)
+ && Objects.equal(headers, that.headers)
&& Objects.equal(username, that.username)
&& Objects.equal(password, that.password);
}
@Override
public int hashCode() {
- return Objects.hashCode(proxyAddress, targetAddress, username, password);
+ return Objects.hashCode(proxyAddress, targetAddress, username, password, headers);
}
@Override
@@ -109,6 +125,7 @@ public String toString() {
return MoreObjects.toStringHelper(this)
.add("proxyAddr", proxyAddress)
.add("targetAddr", targetAddress)
+ .add("headers", headers)
.add("username", username)
// Intentionally mask out password
.add("hasPassword", password != null)
@@ -129,6 +146,7 @@ public static final class Builder {
private SocketAddress proxyAddress;
private InetSocketAddress targetAddress;
+ private Map headers = Collections.emptyMap();
@Nullable
private String username;
@Nullable
@@ -153,6 +171,18 @@ public Builder setTargetAddress(InetSocketAddress targetAddress) {
return this;
}
+ /**
+ * Sets custom HTTP headers to be sent during the HTTP CONNECT handshake. This is an optional
+ * field. The headers will be sent in addition to any authentication headers (if username and
+ * password are set).
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/12479")
+ public Builder setHeaders(Map headers) {
+ this.headers = Collections.unmodifiableMap(
+ new HashMap<>(checkNotNull(headers, "headers")));
+ return this;
+ }
+
/**
* Sets the username used to connect to the proxy. This is an optional field and can be {@code
* null}.
@@ -175,7 +205,8 @@ public Builder setPassword(@Nullable String password) {
* Creates an {@code HttpConnectProxiedSocketAddress}.
*/
public HttpConnectProxiedSocketAddress build() {
- return new HttpConnectProxiedSocketAddress(proxyAddress, targetAddress, username, password);
+ return new HttpConnectProxiedSocketAddress(
+ proxyAddress, targetAddress, headers, username, password);
}
}
}
diff --git a/api/src/main/java/io/grpc/IgnoreJRERequirement.java b/api/src/main/java/io/grpc/IgnoreJRERequirement.java
new file mode 100644
index 00000000000..2db406c5953
--- /dev/null
+++ b/api/src/main/java/io/grpc/IgnoreJRERequirement.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Disables Animal Sniffer's signature checking. This is our own package-private version to avoid
+ * dependening on animalsniffer-annotations.
+ *
+ * FIELD is purposefully not supported, as Android wouldn't be able to ignore a field. Instead,
+ * the entire class would need to be avoided on Android.
+ */
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
+@interface IgnoreJRERequirement {}
diff --git a/api/src/main/java/io/grpc/InternalConfigSelector.java b/api/src/main/java/io/grpc/InternalConfigSelector.java
index 38856f440b4..a63009361d4 100644
--- a/api/src/main/java/io/grpc/InternalConfigSelector.java
+++ b/api/src/main/java/io/grpc/InternalConfigSelector.java
@@ -35,7 +35,7 @@ public abstract class InternalConfigSelector {
= Attributes.Key.create("internal:io.grpc.config-selector");
// Use PickSubchannelArgs for SelectConfigArgs for now. May change over time.
- /** Selects the config for an PRC. */
+ /** Selects the config for an RPC. */
public abstract Result selectConfig(LoadBalancer.PickSubchannelArgs args);
public static final class Result {
diff --git a/api/src/main/java/io/grpc/InternalConfigurator.java b/api/src/main/java/io/grpc/InternalConfigurator.java
new file mode 100644
index 00000000000..7091767a265
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalConfigurator.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 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 access to Configurator API.
+ */
+@Internal
+public interface InternalConfigurator extends Configurator {}
diff --git a/api/src/main/java/io/grpc/InternalConfiguratorRegistry.java b/api/src/main/java/io/grpc/InternalConfiguratorRegistry.java
new file mode 100644
index 00000000000..f567dab74c4
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalConfiguratorRegistry.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 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;
+
+/**
+ * Access internal global configurators.
+ */
+@Internal
+public final class InternalConfiguratorRegistry {
+ private InternalConfiguratorRegistry() {}
+
+ public static void setConfigurators(List configurators) {
+ ConfiguratorRegistry.getDefaultRegistry().setConfigurators(configurators);
+ }
+
+ public static List> getConfigurators() {
+ return ConfiguratorRegistry.getDefaultRegistry().getConfigurators();
+ }
+
+ public static void configureChannelBuilder(ManagedChannelBuilder> channelBuilder) {
+ for (Configurator configurator : ConfiguratorRegistry.getDefaultRegistry().getConfigurators()) {
+ configurator.configureChannelBuilder(channelBuilder);
+ }
+ }
+
+ public static void configureServerBuilder(ServerBuilder> serverBuilder) {
+ for (Configurator configurator : ConfiguratorRegistry.getDefaultRegistry().getConfigurators()) {
+ configurator.configureServerBuilder(serverBuilder);
+ }
+ }
+
+ public static boolean wasSetConfiguratorsCalled() {
+ return ConfiguratorRegistry.getDefaultRegistry().wasSetConfiguratorsCalled();
+ }
+
+ public static int getConfiguratorsCallCountBeforeSet() {
+ return ConfiguratorRegistry.getDefaultRegistry().getConfiguratorsCallCountBeforeSet();
+ }
+}
diff --git a/api/src/main/java/io/grpc/InternalEquivalentAddressGroup.java b/api/src/main/java/io/grpc/InternalEquivalentAddressGroup.java
new file mode 100644
index 00000000000..cd171208af7
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalEquivalentAddressGroup.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2026 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
+public final class InternalEquivalentAddressGroup {
+ private InternalEquivalentAddressGroup() {}
+
+ /**
+ * Endpoint weight for load balancing purposes. While the type is Long, it must be a valid uint32.
+ * Must not be zero. The weight is proportional to the other endpoints; if an endpoint's weight is
+ * twice that of another endpoint, it is intended to receive twice the load.
+ */
+ public static final Attributes.Key ATTR_WEIGHT = EquivalentAddressGroup.ATTR_WEIGHT;
+
+ /**
+ * The backend service associated with this EquivalentAddressGroup.
+ */
+ public static final Attributes.Key ATTR_BACKEND_SERVICE =
+ EquivalentAddressGroup.ATTR_BACKEND_SERVICE;
+}
diff --git a/api/src/main/java/io/grpc/InternalFeatureFlags.java b/api/src/main/java/io/grpc/InternalFeatureFlags.java
new file mode 100644
index 00000000000..a1e771a7571
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalFeatureFlags.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2026 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 com.google.common.annotations.VisibleForTesting;
+
+/** Global variables that govern major changes to the behavior of more than one grpc module. */
+@Internal
+public class InternalFeatureFlags {
+
+ /** Whether to parse targets as RFC 3986 URIs (true), or use {@link java.net.URI} (false). */
+ @VisibleForTesting
+ public static boolean setRfc3986UrisEnabled(boolean value) {
+ return FeatureFlags.setRfc3986UrisEnabled(value);
+ }
+
+ /** Whether to parse targets as RFC 3986 URIs (true), or use {@link java.net.URI} (false). */
+ public static boolean getRfc3986UrisEnabled() {
+ return FeatureFlags.getRfc3986UrisEnabled();
+ }
+
+ public static boolean getFlag(String envVarName, boolean enableByDefault) {
+ return FeatureFlags.getFlag(envVarName, enableByDefault);
+ }
+
+ private InternalFeatureFlags() {}
+}
diff --git a/api/src/main/java/io/grpc/InternalGlobalInterceptors.java b/api/src/main/java/io/grpc/InternalGlobalInterceptors.java
deleted file mode 100644
index db0ff6e2ce9..00000000000
--- a/api/src/main/java/io/grpc/InternalGlobalInterceptors.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2022 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;
-
-/** Accessor to internal methods of {@link GlobalInterceptors}. */
-@Internal
-public final class InternalGlobalInterceptors {
-
- public static void setInterceptorsTracers(
- List clientInterceptorList,
- List serverInterceptorList,
- List serverStreamTracerFactoryList) {
- GlobalInterceptors.setInterceptorsTracers(
- clientInterceptorList, serverInterceptorList, serverStreamTracerFactoryList);
- }
-
- public static List getClientInterceptors() {
- return GlobalInterceptors.getClientInterceptors();
- }
-
- public static List getServerInterceptors() {
- return GlobalInterceptors.getServerInterceptors();
- }
-
- public static List getServerStreamTracerFactories() {
- return GlobalInterceptors.getServerStreamTracerFactories();
- }
-
- private InternalGlobalInterceptors() {}
-}
diff --git a/api/src/main/java/io/grpc/InternalManagedChannelBuilder.java b/api/src/main/java/io/grpc/InternalManagedChannelBuilder.java
new file mode 100644
index 00000000000..083cad40098
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalManagedChannelBuilder.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 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 accessors for {@link ManagedChannelBuilder}.
+ */
+@Internal
+public final class InternalManagedChannelBuilder {
+ private InternalManagedChannelBuilder() {}
+
+ public static > T interceptWithTarget(
+ ManagedChannelBuilder builder, InternalInterceptorFactory factory) {
+ return builder.interceptWithTarget(factory);
+ }
+
+ public static > T addMetricSink(
+ ManagedChannelBuilder builder, MetricSink metricSink) {
+ return builder.addMetricSink(metricSink);
+ }
+
+ public interface InternalInterceptorFactory extends ManagedChannelBuilder.InterceptorFactory {}
+}
diff --git a/api/src/main/java/io/grpc/InternalMethodDescriptor.java b/api/src/main/java/io/grpc/InternalMethodDescriptor.java
index 23bb039e0f1..345f6065813 100644
--- a/api/src/main/java/io/grpc/InternalMethodDescriptor.java
+++ b/api/src/main/java/io/grpc/InternalMethodDescriptor.java
@@ -30,10 +30,12 @@ public InternalMethodDescriptor(InternalKnownTransport transport) {
this.transport = checkNotNull(transport, "transport");
}
+ @SuppressWarnings("EnumOrdinal")
public Object geRawMethodName(MethodDescriptor, ?> descriptor) {
return descriptor.getRawMethodName(transport.ordinal());
}
+ @SuppressWarnings("EnumOrdinal")
public void setRawMethodName(MethodDescriptor, ?> descriptor, Object o) {
descriptor.setRawMethodName(transport.ordinal(), o);
}
diff --git a/api/src/main/java/io/grpc/InternalServiceProviders.java b/api/src/main/java/io/grpc/InternalServiceProviders.java
index c72e01db67a..debc786a82a 100644
--- a/api/src/main/java/io/grpc/InternalServiceProviders.java
+++ b/api/src/main/java/io/grpc/InternalServiceProviders.java
@@ -17,7 +17,9 @@
package io.grpc;
import com.google.common.annotations.VisibleForTesting;
+import java.util.Iterator;
import java.util.List;
+import java.util.ServiceLoader;
@Internal
public final class InternalServiceProviders {
@@ -27,12 +29,17 @@ private InternalServiceProviders() {
/**
* Accessor for method.
*/
- public static T load(
+ @Deprecated
+ public static List loadAll(
Class klass,
- Iterable> hardcoded,
+ Iterable> hardCodedClasses,
ClassLoader classLoader,
PriorityAccessor priorityAccessor) {
- return ServiceProviders.load(klass, hardcoded, classLoader, priorityAccessor);
+ return loadAll(
+ klass,
+ ServiceLoader.load(klass, classLoader).iterator(),
+ () -> hardCodedClasses,
+ priorityAccessor);
}
/**
@@ -40,10 +47,10 @@ public static T load(
*/
public static List loadAll(
Class klass,
- Iterable> hardCodedClasses,
- ClassLoader classLoader,
+ Iterator serviceLoader,
+ Supplier>> hardCodedClasses,
PriorityAccessor priorityAccessor) {
- return ServiceProviders.loadAll(klass, hardCodedClasses, classLoader, priorityAccessor);
+ return ServiceProviders.loadAll(klass, serviceLoader, hardCodedClasses::get, priorityAccessor);
}
/**
@@ -71,4 +78,8 @@ public static boolean isAndroid(ClassLoader cl) {
}
public interface PriorityAccessor extends ServiceProviders.PriorityAccessor {}
+
+ public interface Supplier {
+ T get();
+ }
}
diff --git a/api/src/main/java/io/grpc/InternalStatus.java b/api/src/main/java/io/grpc/InternalStatus.java
index b6549bb435f..56df1decf38 100644
--- a/api/src/main/java/io/grpc/InternalStatus.java
+++ b/api/src/main/java/io/grpc/InternalStatus.java
@@ -38,12 +38,11 @@ private InternalStatus() {}
public static final Metadata.Key CODE_KEY = Status.CODE_KEY;
/**
- * Create a new {@link StatusRuntimeException} with the internal option of skipping the filling
- * of the stack trace.
+ * Create a new {@link StatusRuntimeException} skipping the filling of the stack trace.
*/
@Internal
- public static final StatusRuntimeException asRuntimeException(Status status,
- @Nullable Metadata trailers, boolean fillInStackTrace) {
- return new StatusRuntimeException(status, trailers, fillInStackTrace);
+ public static StatusRuntimeException asRuntimeExceptionWithoutStacktrace(Status status,
+ @Nullable Metadata trailers) {
+ return new InternalStatusRuntimeException(status, trailers);
}
}
diff --git a/api/src/main/java/io/grpc/InternalStatusRuntimeException.java b/api/src/main/java/io/grpc/InternalStatusRuntimeException.java
new file mode 100644
index 00000000000..6090b701f0b
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalStatusRuntimeException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 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 javax.annotation.Nullable;
+
+/**
+ * StatusRuntimeException without stack trace, implemented as a subclass, as the
+ * {@code String, Throwable, boolean, boolean} constructor is not available in the supported
+ * version of Android.
+ *
+ * @see StatusRuntimeException
+ */
+class InternalStatusRuntimeException extends StatusRuntimeException {
+ private static final long serialVersionUID = 0;
+
+ public InternalStatusRuntimeException(Status status, @Nullable Metadata trailers) {
+ super(status, trailers);
+ }
+
+ @Override
+ public synchronized Throwable fillInStackTrace() {
+ return this;
+ }
+}
diff --git a/api/src/main/java/io/grpc/InternalSubchannelAddressAttributes.java b/api/src/main/java/io/grpc/InternalSubchannelAddressAttributes.java
new file mode 100644
index 00000000000..cfc2f7c5137
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalSubchannelAddressAttributes.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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;
+
+/**
+ * An internal class. Do not use.
+ *
+ * An interface to provide the attributes for address connected by subchannel.
+ */
+@Internal
+public interface InternalSubchannelAddressAttributes {
+
+ /**
+ * Return attributes of the server address connected by sub channel.
+ */
+ public Attributes getConnectedAddressAttributes();
+}
diff --git a/api/src/main/java/io/grpc/InternalTcpMetrics.java b/api/src/main/java/io/grpc/InternalTcpMetrics.java
new file mode 100644
index 00000000000..3dd89b6f76c
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalTcpMetrics.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2026 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.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * TCP Metrics defined to be shared across transport implementations.
+ * These metrics and their definitions are specified in
+ * gRFC
+ * A80.
+ */
+@Internal
+public final class InternalTcpMetrics {
+
+ private InternalTcpMetrics() {
+ }
+
+ private static final List OPTIONAL_LABELS = Arrays.asList(
+ "network.local.address",
+ "network.local.port",
+ "network.peer.address",
+ "network.peer.port");
+
+ public static final DoubleHistogramMetricInstrument MIN_RTT_INSTRUMENT =
+ MetricInstrumentRegistry.getDefaultRegistry()
+ .registerDoubleHistogram(
+ "grpc.tcp.min_rtt",
+ "Minimum round-trip time of a TCP connection",
+ "s",
+ Collections.emptyList(),
+ Collections.emptyList(),
+ OPTIONAL_LABELS,
+ false);
+
+ public static final LongCounterMetricInstrument CONNECTIONS_CREATED_INSTRUMENT =
+ MetricInstrumentRegistry
+ .getDefaultRegistry()
+ .registerLongCounter(
+ "grpc.tcp.connections_created",
+ "The total number of TCP connections established.",
+ "{connection}",
+ Collections.emptyList(),
+ OPTIONAL_LABELS,
+ false);
+
+ public static final LongUpDownCounterMetricInstrument CONNECTION_COUNT_INSTRUMENT =
+ MetricInstrumentRegistry
+ .getDefaultRegistry()
+ .registerLongUpDownCounter(
+ "grpc.tcp.connection_count",
+ "The current number of active TCP connections.",
+ "{connection}",
+ Collections.emptyList(),
+ OPTIONAL_LABELS,
+ false);
+
+ public static final LongCounterMetricInstrument PACKETS_RETRANSMITTED_INSTRUMENT =
+ MetricInstrumentRegistry
+ .getDefaultRegistry()
+ .registerLongCounter(
+ "grpc.tcp.packets_retransmitted",
+ "The total number of packets retransmitted for all TCP connections.",
+ "{packet}",
+ Collections.emptyList(),
+ OPTIONAL_LABELS,
+ false);
+
+ public static final LongCounterMetricInstrument RECURRING_RETRANSMITS_INSTRUMENT =
+ MetricInstrumentRegistry
+ .getDefaultRegistry()
+ .registerLongCounter(
+ "grpc.tcp.recurring_retransmits",
+ "The total number of times the retransmit timer "
+ + "popped for all TCP connections.",
+ "{timeout}",
+ Collections.emptyList(),
+ OPTIONAL_LABELS,
+ false);
+
+}
diff --git a/api/src/main/java/io/grpc/InternalTimeUtils.java b/api/src/main/java/io/grpc/InternalTimeUtils.java
new file mode 100644
index 00000000000..ef8022f53c5
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalTimeUtils.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2024 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.time.Duration;
+
+@Internal
+public final class InternalTimeUtils {
+ public static long convert(Duration duration) {
+ return TimeUtils.convertToNanos(duration);
+ }
+}
diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java
index 84f108c4198..ae83af2804c 100644
--- a/api/src/main/java/io/grpc/LoadBalancer.java
+++ b/api/src/main/java/io/grpc/LoadBalancer.java
@@ -32,7 +32,6 @@
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
-import javax.annotation.concurrent.ThreadSafe;
/**
* A pluggable component that receives resolved addresses from {@link NameResolver} and provides the
@@ -64,7 +63,7 @@
* allows implementations to schedule tasks to be run in the same Synchronization Context, with or
* without a delay, thus those tasks don't need to worry about synchronizing with the balancer
* methods.
- *
+ *
* However, the actual running thread may be the network thread, thus the following rules must be
* followed to prevent blocking or even dead-locking in a network:
*
@@ -121,6 +120,12 @@ public abstract class LoadBalancer {
HEALTH_CONSUMER_LISTENER_ARG_KEY =
LoadBalancer.CreateSubchannelArgs.Key.create("internal:health-check-consumer-listener");
+ @Internal
+ public static final LoadBalancer.CreateSubchannelArgs.Key
+ DISABLE_SUBCHANNEL_RECONNECT_KEY =
+ LoadBalancer.CreateSubchannelArgs.Key.createWithDefault(
+ "internal:disable-subchannel-reconnect", Boolean.FALSE);
+
@Internal
public static final Attributes.Key
HAS_HEALTH_PRODUCER_LISTENER_KEY =
@@ -150,15 +155,16 @@ public String toString() {
private int recursionCount;
/**
- * Handles newly resolved server groups and metadata attributes from name resolution system.
- * {@code servers} contained in {@link EquivalentAddressGroup} should be considered equivalent
- * but may be flattened into a single list if needed.
- *
- * Implementations should not modify the given {@code servers}.
+ * Handles newly resolved addresses and metadata attributes from name resolution system.
+ * Addresses in {@link EquivalentAddressGroup} should be considered equivalent but may be
+ * flattened into a single list if needed.
*
* @param resolvedAddresses the resolved server addresses, attributes, and config.
* @since 1.21.0
+ *
+ * @deprecated Use instead {@link #acceptResolvedAddresses(ResolvedAddresses)}
*/
+ @Deprecated
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
if (recursionCount++ == 0) {
// Note that the information about the addresses actually being accepted will be lost
@@ -173,12 +179,10 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
* EquivalentAddressGroup} addresses should be considered equivalent but may be flattened into a
* single list if needed.
*
- *
Implementations can choose to reject the given addresses by returning {@code false}.
- *
- *
Implementations should not modify the given {@code addresses}.
+ * @param resolvedAddresses the resolved server addresses, attributes, and config
+ * @return {@code Status.OK} if the resolved addresses were accepted, otherwise an error to report
+ * to the name resolver
*
- * @param resolvedAddresses the resolved server addresses, attributes, and config.
- * @return {@code true} if the resolved addresses were accepted. {@code false} if rejected.
* @since 1.49.0
*/
public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
@@ -206,7 +210,7 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
*
* @since 1.21.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
public static final class ResolvedAddresses {
private final List addresses;
@NameResolver.ResolutionResultAttr
@@ -412,7 +416,16 @@ public void handleSubchannelState(
*
* This method should always return a constant value. It's not specified when this will be
* called.
+ *
+ *
Note that this method is only called when implementing {@code handleResolvedAddresses()}
+ * instead of {@code acceptResolvedAddresses()}.
+ *
+ * @deprecated Instead of overwriting this and {@code handleResolvedAddresses()}, only
+ * overwrite {@code acceptResolvedAddresses()} which indicates if the addresses provided
+ * by the name resolver are acceptable with the {@code boolean} return value.
*/
+ @Deprecated
+ @SuppressWarnings("InlineMeSuggester")
public boolean canHandleEmptyAddressListFromNameResolution() {
return false;
}
@@ -436,7 +449,6 @@ public void requestConnection() {}
*
* @since 1.2.0
*/
- @ThreadSafe
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract static class SubchannelPicker {
/**
@@ -446,18 +458,6 @@ public abstract static class SubchannelPicker {
* @since 1.3.0
*/
public abstract PickResult pickSubchannel(PickSubchannelArgs args);
-
- /**
- * Tries to establish connections now so that the upcoming RPC may then just pick a ready
- * connection without having to connect first.
- *
- *
No-op if unsupported.
- *
- * @deprecated override {@link LoadBalancer#requestConnection} instead.
- * @since 1.11.0
- */
- @Deprecated
- public void requestConnection() {}
}
/**
@@ -490,6 +490,29 @@ public abstract static class PickSubchannelArgs {
* @since 1.2.0
*/
public abstract MethodDescriptor, ?> getMethodDescriptor();
+
+ /**
+ * Gets an object that can be informed about what sort of pick was made.
+ */
+ @Internal
+ public PickDetailsConsumer getPickDetailsConsumer() {
+ return new PickDetailsConsumer() {};
+ }
+ }
+
+ /** Receives information about the pick being chosen. */
+ @Internal
+ public interface PickDetailsConsumer {
+ /**
+ * Optional labels that provide context of how the pick was routed. Particularly helpful for
+ * per-RPC metrics.
+ *
+ * @throws NullPointerException if key or value is {@code null}
+ */
+ default void addOptionalLabel(String key, String value) {
+ checkNotNull(key, "key");
+ checkNotNull(value, "value");
+ }
}
/**
@@ -523,6 +546,7 @@ public static final class PickResult {
private final Status status;
// True if the result is created by withDrop()
private final boolean drop;
+ @Nullable private final String authorityOverride;
private PickResult(
@Nullable Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory,
@@ -531,6 +555,17 @@ private PickResult(
this.streamTracerFactory = streamTracerFactory;
this.status = checkNotNull(status, "status");
this.drop = drop;
+ this.authorityOverride = null;
+ }
+
+ private PickResult(
+ @Nullable Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory,
+ Status status, boolean drop, @Nullable String authorityOverride) {
+ this.subchannel = subchannel;
+ this.streamTracerFactory = streamTracerFactory;
+ this.status = checkNotNull(status, "status");
+ this.drop = drop;
+ this.authorityOverride = authorityOverride;
}
/**
@@ -603,6 +638,8 @@ private PickResult(
* stream is created at all in some cases.
* @since 1.3.0
*/
+ // TODO(shivaspeaks): Need to deprecate old APIs and create new ones,
+ // per https://github.com/grpc/grpc-java/issues/12662.
public static PickResult withSubchannel(
Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory) {
return new PickResult(
@@ -610,6 +647,19 @@ public static PickResult withSubchannel(
false);
}
+ /**
+ * Same as {@code withSubchannel(subchannel, streamTracerFactory)} but with an authority name
+ * to override in the host header.
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11656")
+ public static PickResult withSubchannel(
+ Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory,
+ @Nullable String authorityOverride) {
+ return new PickResult(
+ checkNotNull(subchannel, "subchannel"), streamTracerFactory, Status.OK,
+ false, authorityOverride);
+ }
+
/**
* Equivalent to {@code withSubchannel(subchannel, null)}.
*
@@ -619,6 +669,28 @@ public static PickResult withSubchannel(Subchannel subchannel) {
return withSubchannel(subchannel, null);
}
+ /**
+ * Creates a new {@code PickResult} with the given {@code subchannel},
+ * but retains all other properties from this {@code PickResult}.
+ *
+ * @since 1.80.0
+ */
+ public PickResult copyWithSubchannel(Subchannel subchannel) {
+ return new PickResult(checkNotNull(subchannel, "subchannel"), streamTracerFactory,
+ status, drop, authorityOverride);
+ }
+
+ /**
+ * Creates a new {@code PickResult} with the given {@code streamTracerFactory},
+ * but retains all other properties from this {@code PickResult}.
+ *
+ * @since 1.80.0
+ */
+ public PickResult copyWithStreamTracerFactory(
+ @Nullable ClientStreamTracer.Factory streamTracerFactory) {
+ return new PickResult(subchannel, streamTracerFactory, status, drop, authorityOverride);
+ }
+
/**
* A decision to report a connectivity error to the RPC. If the RPC is {@link
* CallOptions#withWaitForReady wait-for-ready}, it will stay buffered. Otherwise, it will fail
@@ -653,6 +725,13 @@ public static PickResult withNoResult() {
return NO_RESULT;
}
+ /** Returns the authority override if any. */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11656")
+ @Nullable
+ public String getAuthorityOverride() {
+ return authorityOverride;
+ }
+
/**
* The Subchannel if this result was created by {@link #withSubchannel withSubchannel()}, or
* null otherwise.
@@ -693,6 +772,13 @@ public boolean isDrop() {
return drop;
}
+ /**
+ * Returns {@code true} if the pick was not created with {@link #withNoResult()}.
+ */
+ public boolean hasResult() {
+ return !(subchannel == null && status.isOk());
+ }
+
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@@ -700,6 +786,7 @@ public String toString() {
.add("streamTracerFactory", streamTracerFactory)
.add("status", status)
.add("drop", drop)
+ .add("authority-override", authorityOverride)
.toString();
}
@@ -798,9 +885,11 @@ public String toString() {
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public static final class Builder {
+ private static final Object[][] EMPTY_CUSTOM_OPTIONS = new Object[0][2];
+
private List addrs;
private Attributes attrs = Attributes.EMPTY;
- private Object[][] customOptions = new Object[0][2];
+ private Object[][] customOptions = EMPTY_CUSTOM_OPTIONS;
Builder() {
}
@@ -939,9 +1028,10 @@ public String toString() {
/**
* Provides essentials for LoadBalancer implementations.
*
+ * This class is thread-safe.
+ *
* @since 1.2.0
*/
- @ThreadSafe
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract static class Helper {
/**
@@ -964,8 +1054,8 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) {
}
/**
- * Out-of-band channel for LoadBalancer’s own RPC needs, e.g., talking to an external
- * load-balancer service.
+ * Create an out-of-band channel for the LoadBalancer’s own RPC needs, e.g., talking to an
+ * external load-balancer service.
*
*
The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB
* channels within {@link #shutdown}.
@@ -975,7 +1065,12 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) {
public abstract ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority);
/**
- * Accept a list of EAG for multiple authorities: https://github.com/grpc/grpc-java/issues/4618
+ * Create an out-of-band channel for the LoadBalancer's own RPC needs, e.g., talking to an
+ * external load-balancer service. This version of the method allows multiple EAGs, so different
+ * addresses can have different authorities.
+ *
+ *
The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB
+ * channels within {@link #shutdown}.
* */
public ManagedChannel createOobChannel(List eag,
String authority) {
@@ -1127,6 +1222,10 @@ public void ignoreRefreshNameResolutionCheck() {
* Returns a {@link SynchronizationContext} that runs tasks in the same Synchronization Context
* as that the callback methods on the {@link LoadBalancer} interface are run in.
*
+ * Work added to the synchronization context might not run immediately, so LB implementations
+ * must be careful to ensure that any assumptions still hold when it is executed. In particular,
+ * the LB might have been shut down or subchannels might have changed state.
+ *
*
Pro-tip: in order to call {@link SynchronizationContext#schedule}, you need to provide a
* {@link ScheduledExecutorService}. {@link #getScheduledExecutorService} is provided for your
* convenience.
@@ -1162,6 +1261,13 @@ public ScheduledExecutorService getScheduledExecutorService() {
*/
public abstract String getAuthority();
+ /**
+ * Returns the target string of the channel, guaranteed to include its scheme.
+ */
+ public String getChannelTarget() {
+ throw new UnsupportedOperationException();
+ }
+
/**
* Returns the ChannelCredentials used to construct the channel, without bearer tokens.
*
@@ -1212,10 +1318,20 @@ public NameResolver.Args getNameResolverArgs() {
public NameResolverRegistry getNameResolverRegistry() {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Returns the {@link MetricRecorder} that the channel uses to record metrics.
+ *
+ * @since 1.64.0
+ */
+ @Internal
+ public MetricRecorder getMetricRecorder() {
+ return new MetricRecorder() {};
+ }
}
/**
- * A logical connection to a server, or a group of equivalent servers represented by an {@link
+ * A logical connection to a server, or a group of equivalent servers represented by an {@link
* EquivalentAddressGroup}.
*
*
It maintains at most one physical connection (aka transport) for sending new RPCs, while
@@ -1381,6 +1497,18 @@ public void updateAddresses(List addrs) {
public Object getInternalSubchannel() {
throw new UnsupportedOperationException();
}
+
+ /**
+ * (Internal use only) returns attributes of the address subchannel is connected to.
+ *
+ * Warning: this is INTERNAL API, is not supposed to be used by external users, and may
+ * change without notice. If you think you must use it, please file an issue and we can consider
+ * removing its "internal" status.
+ */
+ @Internal
+ public Attributes getConnectedAddressAttributes() {
+ throw new UnsupportedOperationException();
+ }
}
/**
@@ -1422,9 +1550,10 @@ public interface SubchannelStateListener {
/**
* Factory to create {@link LoadBalancer} instance.
*
+ *
This class is thread-safe.
+ *
* @since 1.2.0
*/
- @ThreadSafe
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract static class Factory {
/**
@@ -1479,5 +1608,19 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
public String toString() {
return "FixedResultPicker(" + result + ")";
}
+
+ @Override
+ public int hashCode() {
+ return result.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof FixedResultPicker)) {
+ return false;
+ }
+ FixedResultPicker that = (FixedResultPicker) o;
+ return this.result.equals(that.result);
+ }
}
}
diff --git a/api/src/main/java/io/grpc/LoadBalancerProvider.java b/api/src/main/java/io/grpc/LoadBalancerProvider.java
index bb4c574211e..7dc30d6baaf 100644
--- a/api/src/main/java/io/grpc/LoadBalancerProvider.java
+++ b/api/src/main/java/io/grpc/LoadBalancerProvider.java
@@ -81,7 +81,7 @@ public abstract class LoadBalancerProvider extends LoadBalancer.Factory {
* @return a tuple of the fully parsed and validated balancer configuration, else the Status.
* @since 1.20.0
* @see
- * A24-lb-policy-config.md
+ * A24-lb-policy-config.md
*/
public ConfigOrError parseLoadBalancingPolicyConfig(Map rawLoadBalancingPolicyConfig) {
return UNKNOWN_CONFIG;
diff --git a/api/src/main/java/io/grpc/LoadBalancerRegistry.java b/api/src/main/java/io/grpc/LoadBalancerRegistry.java
index f6b69f978b8..a8fbc102f5f 100644
--- a/api/src/main/java/io/grpc/LoadBalancerRegistry.java
+++ b/api/src/main/java/io/grpc/LoadBalancerRegistry.java
@@ -26,6 +26,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -42,7 +43,6 @@
public final class LoadBalancerRegistry {
private static final Logger logger = Logger.getLogger(LoadBalancerRegistry.class.getName());
private static LoadBalancerRegistry instance;
- private static final Iterable> HARDCODED_CLASSES = getHardCodedClasses();
private final LinkedHashSet allProviders =
new LinkedHashSet<>();
@@ -101,8 +101,10 @@ public static synchronized LoadBalancerRegistry getDefaultRegistry() {
if (instance == null) {
List providerList = ServiceProviders.loadAll(
LoadBalancerProvider.class,
- HARDCODED_CLASSES,
- LoadBalancerProvider.class.getClassLoader(),
+ ServiceLoader
+ .load(LoadBalancerProvider.class, LoadBalancerProvider.class.getClassLoader())
+ .iterator(),
+ LoadBalancerRegistry::getHardCodedClasses,
new LoadBalancerPriorityAccessor());
instance = new LoadBalancerRegistry();
for (LoadBalancerProvider provider : providerList) {
diff --git a/api/src/main/java/io/grpc/LongCounterMetricInstrument.java b/api/src/main/java/io/grpc/LongCounterMetricInstrument.java
new file mode 100644
index 00000000000..73516dfb9e4
--- /dev/null
+++ b/api/src/main/java/io/grpc/LongCounterMetricInstrument.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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 counter metric instrument.
+ */
+@Internal
+public final class LongCounterMetricInstrument extends PartialMetricInstrument {
+ public LongCounterMetricInstrument(int index, String name, String description, String unit,
+ List requiredLabelKeys, List optionalLabelKeys, boolean enableByDefault) {
+ super(index, name, description, unit, requiredLabelKeys, optionalLabelKeys, enableByDefault);
+ }
+}
diff --git a/api/src/main/java/io/grpc/LongGaugeMetricInstrument.java b/api/src/main/java/io/grpc/LongGaugeMetricInstrument.java
new file mode 100644
index 00000000000..393bdeb355c
--- /dev/null
+++ b/api/src/main/java/io/grpc/LongGaugeMetricInstrument.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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 gauge metric instrument.
+ */
+@Internal
+public final class LongGaugeMetricInstrument extends PartialMetricInstrument
+ implements CallbackMetricInstrument {
+ public LongGaugeMetricInstrument(int index, String name, String description, String unit,
+ List requiredLabelKeys, List optionalLabelKeys, boolean enableByDefault) {
+ super(index, name, description, unit, requiredLabelKeys, optionalLabelKeys, enableByDefault);
+ }
+}
diff --git a/api/src/main/java/io/grpc/LongHistogramMetricInstrument.java b/api/src/main/java/io/grpc/LongHistogramMetricInstrument.java
new file mode 100644
index 00000000000..2a4e56ffd5a
--- /dev/null
+++ b/api/src/main/java/io/grpc/LongHistogramMetricInstrument.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 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 histogram metric instrument.
+ */
+@Internal
+public final class LongHistogramMetricInstrument extends PartialMetricInstrument {
+ private final List bucketBoundaries;
+
+ public LongHistogramMetricInstrument(int index, String name, String description, String unit,
+ List bucketBoundaries, List requiredLabelKeys, List optionalLabelKeys,
+ boolean enableByDefault) {
+ super(index, name, description, unit, requiredLabelKeys, optionalLabelKeys, enableByDefault);
+ this.bucketBoundaries = bucketBoundaries;
+ }
+
+ public List getBucketBoundaries() {
+ return bucketBoundaries;
+ }
+}
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/ManagedChannel.java b/api/src/main/java/io/grpc/ManagedChannel.java
index 7875fdb57f2..2b1d89946bf 100644
--- a/api/src/main/java/io/grpc/ManagedChannel.java
+++ b/api/src/main/java/io/grpc/ManagedChannel.java
@@ -17,12 +17,12 @@
package io.grpc;
import java.util.concurrent.TimeUnit;
-import javax.annotation.concurrent.ThreadSafe;
/**
* A {@link Channel} that provides lifecycle management.
+ *
+ * This class is thread-safe.
*/
-@ThreadSafe
public abstract class ManagedChannel extends Channel {
/**
* Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
diff --git a/api/src/main/java/io/grpc/ManagedChannelBuilder.java b/api/src/main/java/io/grpc/ManagedChannelBuilder.java
index 7fe183f2049..3f370ab3003 100644
--- a/api/src/main/java/io/grpc/ManagedChannelBuilder.java
+++ b/api/src/main/java/io/grpc/ManagedChannelBuilder.java
@@ -159,6 +159,21 @@ public T offloadExecutor(Executor executor) {
*/
public abstract T intercept(ClientInterceptor... interceptors);
+ /**
+ * Internal-only: Adds a factory that will construct an interceptor based on the channel's target.
+ * This can be used to work around nameResolverFactory() changing the target string.
+ */
+ @Internal
+ protected T interceptWithTarget(InterceptorFactory factory) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** Internal-only. */
+ @Internal
+ protected interface InterceptorFactory {
+ ClientInterceptor newInterceptor(String target);
+ }
+
/**
* Adds a {@link ClientTransportFilter}. The order of filters being added is the order they will
* be executed
@@ -359,9 +374,17 @@ public T maxInboundMetadataSize(int bytes) {
* notice when they are causing excessive load. Clients are strongly encouraged to use only as
* small of a value as necessary.
*
+ *
When the channel implementation supports TCP_USER_TIMEOUT, enabling keepalive will also
+ * enable TCP_USER_TIMEOUT for the connection. This requires all sent packets to receive
+ * a TCP acknowledgement before the keepalive timeout. The keepalive time is not used for
+ * TCP_USER_TIMEOUT, except as a signal to enable the feature. grpc-netty supports
+ * TCP_USER_TIMEOUT on Linux platforms supported by netty-transport-native-epoll.
+ *
* @throws UnsupportedOperationException if unsupported
* @see gRFC A8
* Client-side Keepalive
+ * @see gRFC A18
+ * TCP User Timeout
* @since 1.7.0
*/
public T keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
@@ -378,6 +401,8 @@ public T keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
* @throws UnsupportedOperationException if unsupported
* @see gRFC A8
* Client-side Keepalive
+ * @see gRFC A18
+ * TCP User Timeout
* @since 1.7.0
*/
public T keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
@@ -607,6 +632,35 @@ public T disableServiceConfigLookUp() {
throw new UnsupportedOperationException();
}
+ /**
+ * Adds a {@link MetricSink} for channel to use for configuring and recording metrics.
+ *
+ * @return this
+ * @since 1.64.0
+ */
+ @Internal
+ protected T addMetricSink(MetricSink metricSink) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Provides a "custom" argument for the {@link NameResolver}, if applicable, replacing any 'value'
+ * previously provided for 'key'.
+ *
+ *
NB: If the selected {@link NameResolver} does not understand 'key', or target URI resolution
+ * isn't needed at all, your custom argument will be silently ignored.
+ *
+ *
See {@link NameResolver.Args#getArg(NameResolver.Args.Key)} for more.
+ *
+ * @param key identifies the argument in a type-safe manner
+ * @param value the argument itself
+ * @return this
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
+ public T setNameResolverArg(NameResolver.Args.Key key, X value) {
+ throw new UnsupportedOperationException();
+ }
+
/**
* Builds a channel using the given parameters.
*
diff --git a/api/src/main/java/io/grpc/ManagedChannelRegistry.java b/api/src/main/java/io/grpc/ManagedChannelRegistry.java
index 31f874b8094..ec47b325ffc 100644
--- a/api/src/main/java/io/grpc/ManagedChannelRegistry.java
+++ b/api/src/main/java/io/grpc/ManagedChannelRegistry.java
@@ -18,6 +18,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.concurrent.GuardedBy;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
@@ -28,9 +29,9 @@
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
/**
@@ -100,8 +101,10 @@ public static synchronized ManagedChannelRegistry getDefaultRegistry() {
if (instance == null) {
List providerList = ServiceProviders.loadAll(
ManagedChannelProvider.class,
- getHardCodedClasses(),
- ManagedChannelProvider.class.getClassLoader(),
+ ServiceLoader
+ .load(ManagedChannelProvider.class, ManagedChannelProvider.class.getClassLoader())
+ .iterator(),
+ ManagedChannelRegistry::getHardCodedClasses,
new ManagedChannelPriorityAccessor());
instance = new ManagedChannelRegistry();
for (ManagedChannelProvider provider : providerList) {
@@ -160,8 +163,11 @@ ManagedChannelBuilder> newChannelBuilder(NameResolverRegistry nameResolverRegi
String target, ChannelCredentials creds) {
NameResolverProvider nameResolverProvider = null;
try {
- URI uri = new URI(target);
- nameResolverProvider = nameResolverRegistry.getProviderForScheme(uri.getScheme());
+ String scheme =
+ FeatureFlags.getRfc3986UrisEnabled()
+ ? Uri.parse(target).getScheme()
+ : new URI(target).getScheme();
+ nameResolverProvider = nameResolverRegistry.getProviderForScheme(scheme);
} catch (URISyntaxException ignore) {
// bad URI found, just ignore and continue
}
diff --git a/api/src/main/java/io/grpc/Metadata.java b/api/src/main/java/io/grpc/Metadata.java
index 58fcefe1373..8a958d127df 100644
--- a/api/src/main/java/io/grpc/Metadata.java
+++ b/api/src/main/java/io/grpc/Metadata.java
@@ -16,12 +16,14 @@
package io.grpc;
-import static com.google.common.base.Charsets.US_ASCII;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;
import java.io.ByteArrayInputStream;
@@ -32,8 +34,6 @@
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -325,7 +325,7 @@ public Set keys() {
if (isEmpty()) {
return Collections.emptySet();
}
- Set ks = new HashSet<>(size);
+ Set ks = Sets.newHashSetWithExpectedSize(size);
for (int i = 0; i < size; i++) {
ks.add(new String(name(i), 0 /* hibyte */));
}
@@ -526,7 +526,7 @@ public void merge(Metadata other) {
public void merge(Metadata other, Set> keys) {
Preconditions.checkNotNull(other, "other");
// Use ByteBuffer for equals and hashCode.
- Map> asciiKeys = new HashMap<>(keys.size());
+ Map> asciiKeys = Maps.newHashMapWithExpectedSize(keys.size());
for (Key> key : keys) {
asciiKeys.put(ByteBuffer.wrap(key.asciiName()), key);
}
diff --git a/api/src/main/java/io/grpc/MethodDescriptor.java b/api/src/main/java/io/grpc/MethodDescriptor.java
index 1bfaccb4201..a02eb840deb 100644
--- a/api/src/main/java/io/grpc/MethodDescriptor.java
+++ b/api/src/main/java/io/grpc/MethodDescriptor.java
@@ -20,9 +20,9 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.CheckReturnValue;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicReferenceArray;
-import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
diff --git a/api/src/main/java/io/grpc/MetricInstrument.java b/api/src/main/java/io/grpc/MetricInstrument.java
new file mode 100644
index 00000000000..1930319060d
--- /dev/null
+++ b/api/src/main/java/io/grpc/MetricInstrument.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 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 metric instrument. Metric instrument contains information used to describe a metric.
+ */
+@Internal
+public interface MetricInstrument {
+ /**
+ * Returns the unique index of this metric instrument.
+ *
+ * @return the index of the metric instrument.
+ */
+ public int getIndex();
+
+ /**
+ * Returns the name of the metric.
+ *
+ * @return the name of the metric.
+ */
+ public String getName();
+
+ /**
+ * Returns a description of the metric.
+ *
+ * @return a description of the metric.
+ */
+ public String getDescription();
+
+ /**
+ * Returns the unit of measurement for the metric.
+ *
+ * @return the unit of measurement.
+ */
+ public String getUnit();
+
+ /**
+ * Returns a list of required label keys for this metric instrument.
+ *
+ * @return a list of required label keys.
+ */
+ public List getRequiredLabelKeys();
+
+ /**
+ * Returns a list of optional label keys for this metric instrument.
+ *
+ * @return a list of optional label keys.
+ */
+ public List getOptionalLabelKeys();
+
+ /**
+ * Indicates whether this metric instrument is enabled by default.
+ *
+ * @return {@code true} if this metric instrument is enabled by default,
+ * {@code false} otherwise.
+ */
+ public boolean isEnableByDefault();
+}
diff --git a/api/src/main/java/io/grpc/MetricInstrumentRegistry.java b/api/src/main/java/io/grpc/MetricInstrumentRegistry.java
new file mode 100644
index 00000000000..ce0f8f1b5cb
--- /dev/null
+++ b/api/src/main/java/io/grpc/MetricInstrumentRegistry.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2024 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 static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.errorprone.annotations.concurrent.GuardedBy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A registry for globally registered metric instruments.
+ */
+@Internal
+public final class MetricInstrumentRegistry {
+ static final int INITIAL_INSTRUMENT_CAPACITY = 5;
+ private static MetricInstrumentRegistry instance;
+ private final Object lock = new Object();
+ @GuardedBy("lock")
+ private final Set registeredMetricNames = new HashSet<>();
+ @GuardedBy("lock")
+ private MetricInstrument[] metricInstruments =
+ new MetricInstrument[INITIAL_INSTRUMENT_CAPACITY];
+ @GuardedBy("lock")
+ private int nextAvailableMetricIndex;
+
+ @VisibleForTesting
+ MetricInstrumentRegistry() {}
+
+ /**
+ * Returns the default metric instrument registry.
+ */
+ public static synchronized MetricInstrumentRegistry getDefaultRegistry() {
+ if (instance == null) {
+ instance = new MetricInstrumentRegistry();
+ }
+ return instance;
+ }
+
+ /**
+ * Returns a list of registered metric instruments.
+ */
+ public List getMetricInstruments() {
+ synchronized (lock) {
+ return Collections.unmodifiableList(
+ Arrays.asList(Arrays.copyOfRange(metricInstruments, 0, nextAvailableMetricIndex)));
+ }
+ }
+
+ /**
+ * Registers a new Double 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 DoubleCounterMetricInstrument
+ * @throws IllegalStateException if a metric with the same name already exists
+ */
+ public DoubleCounterMetricInstrument registerDoubleCounter(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();
+ }
+ // TODO(dnvindhya): add limit for number of optional labels allowed
+ DoubleCounterMetricInstrument instrument = new DoubleCounterMetricInstrument(
+ index, name, description, unit, requiredLabelKeys, optionalLabelKeys,
+ enableByDefault);
+ metricInstruments[index] = instrument;
+ registeredMetricNames.add(name);
+ nextAvailableMetricIndex += 1;
+ return instrument;
+ }
+ }
+
+ /**
+ * Registers a new Long 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 LongCounterMetricInstrument
+ * @throws IllegalStateException if a metric with the same name already exists
+ */
+ public LongCounterMetricInstrument registerLongCounter(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();
+ }
+ LongCounterMetricInstrument instrument = new LongCounterMetricInstrument(
+ index, name, description, unit, requiredLabelKeys, optionalLabelKeys,
+ enableByDefault);
+ metricInstruments[index] = instrument;
+ registeredMetricNames.add(name);
+ nextAvailableMetricIndex += 1;
+ return instrument;
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param name the name of the metric
+ * @param description a description of the metric
+ * @param unit the unit of measurement for the metric
+ * @param bucketBoundaries recommended set of explicit bucket boundaries for the histogram
+ * @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 DoubleHistogramMetricInstrument
+ * @throws IllegalStateException if a metric with the same name already exists
+ */
+ public DoubleHistogramMetricInstrument registerDoubleHistogram(String name,
+ String description, String unit, List bucketBoundaries,
+ List requiredLabelKeys, List optionalLabelKeys, boolean enableByDefault) {
+ checkArgument(!Strings.isNullOrEmpty(name), "missing metric name");
+ checkNotNull(description, "description");
+ checkNotNull(unit, "unit");
+ checkNotNull(bucketBoundaries, "bucketBoundaries");
+ 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();
+ }
+ DoubleHistogramMetricInstrument instrument = new DoubleHistogramMetricInstrument(
+ index, name, description, unit, bucketBoundaries, requiredLabelKeys,
+ optionalLabelKeys,
+ enableByDefault);
+ metricInstruments[index] = instrument;
+ registeredMetricNames.add(name);
+ nextAvailableMetricIndex += 1;
+ return instrument;
+ }
+ }
+
+ /**
+ * Registers a new Long Histogram 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 bucketBoundaries recommended set of explicit bucket boundaries for the histogram
+ * @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 LongHistogramMetricInstrument
+ * @throws IllegalStateException if a metric with the same name already exists
+ */
+ public LongHistogramMetricInstrument registerLongHistogram(String name,
+ String description, String unit, List bucketBoundaries, List requiredLabelKeys,
+ List optionalLabelKeys, boolean enableByDefault) {
+ checkArgument(!Strings.isNullOrEmpty(name), "missing metric name");
+ checkNotNull(description, "description");
+ checkNotNull(unit, "unit");
+ checkNotNull(bucketBoundaries, "bucketBoundaries");
+ 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();
+ }
+ LongHistogramMetricInstrument instrument = new LongHistogramMetricInstrument(
+ index, name, description, unit, bucketBoundaries, requiredLabelKeys,
+ optionalLabelKeys,
+ enableByDefault);
+ metricInstruments[index] = instrument;
+ registeredMetricNames.add(name);
+ nextAvailableMetricIndex += 1;
+ return instrument;
+ }
+ }
+
+
+ /**
+ * Registers a new Long Gauge 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 LongGaugeMetricInstrument
+ * @throws IllegalStateException if a metric with the same name already exists
+ */
+ public LongGaugeMetricInstrument registerLongGauge(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();
+ }
+ LongGaugeMetricInstrument instrument = new LongGaugeMetricInstrument(
+ index, name, description, unit, requiredLabelKeys, optionalLabelKeys,
+ enableByDefault);
+ metricInstruments[index] = instrument;
+ registeredMetricNames.add(name);
+ nextAvailableMetricIndex += 1;
+ return instrument;
+ }
+ }
+
+ @GuardedBy("lock")
+ private void resizeMetricInstruments() {
+ // Increase the capacity of the metricInstruments array by INITIAL_INSTRUMENT_CAPACITY
+ int newInstrumentsCapacity = metricInstruments.length + INITIAL_INSTRUMENT_CAPACITY;
+ MetricInstrument[] resizedMetricInstruments = Arrays.copyOf(metricInstruments,
+ newInstrumentsCapacity);
+ metricInstruments = resizedMetricInstruments;
+ }
+}
diff --git a/api/src/main/java/io/grpc/MetricRecorder.java b/api/src/main/java/io/grpc/MetricRecorder.java
new file mode 100644
index 00000000000..897c28011cd
--- /dev/null
+++ b/api/src/main/java/io/grpc/MetricRecorder.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2024 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 static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.List;
+
+/**
+ * An interface used for recording gRPC metrics. Implementations of this interface are responsible
+ * for collecting and potentially reporting metrics from various gRPC components.
+ */
+@Internal
+public interface MetricRecorder {
+ /**
+ * Adds a value for a double-precision counter metric instrument.
+ *
+ * @param metricInstrument The counter metric instrument to add the value against.
+ * @param value The value to add.
+ * @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 addDoubleCounter(DoubleCounterMetricInstrument metricInstrument, double 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());
+ }
+
+ /**
+ * 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. 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) {
+ 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());
+ }
+
+ /**
+ * 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.
+ *
+ * @param metricInstrument The histogram metric instrument to record the value against.
+ * @param value The value to record.
+ * @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 recordDoubleHistogram(DoubleHistogramMetricInstrument metricInstrument, double 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 long valued histogram metric instrument.
+ *
+ * @param metricInstrument The histogram metric instrument to record the value against.
+ * @param value The value to record.
+ * @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 recordLongHistogram(LongHistogramMetricInstrument 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());
+ }
+
+ /**
+ * Registers a callback to produce metric values for only the listed instruments. The returned
+ * registration must be closed when no longer needed, which will remove the callback.
+ *
+ * @param callback The callback to call to record.
+ * @param metricInstruments The metric instruments the callback will record against.
+ */
+ default Registration registerBatchCallback(BatchCallback callback,
+ CallbackMetricInstrument... metricInstruments) {
+ return () -> { };
+ }
+
+ /** Callback to record gauge values. */
+ interface BatchCallback {
+ /** Records instrument values into {@code recorder}. */
+ void accept(BatchRecorder recorder);
+ }
+
+ /** Recorder for instrument values produced by a batch callback. */
+ interface BatchRecorder {
+ /**
+ * Record a long gauge value.
+ *
+ * @param value The value to record.
+ * @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 recordLongGauge(LongGaugeMetricInstrument 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());
+ }
+ }
+
+ /** A handle to a registration, that allows unregistration. */
+ interface Registration extends AutoCloseable {
+ // Redefined to not throw an exception.
+ /** Unregister. */
+ @Override
+ void close();
+ }
+}
diff --git a/api/src/main/java/io/grpc/MetricSink.java b/api/src/main/java/io/grpc/MetricSink.java
new file mode 100644
index 00000000000..ce5d3822520
--- /dev/null
+++ b/api/src/main/java/io/grpc/MetricSink.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2024 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;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An internal interface representing a receiver or aggregator of gRPC metrics data.
+ */
+@Internal
+public interface MetricSink {
+
+ /**
+ * Returns a set of names for the metrics that are currently enabled or disabled.
+ *
+ * @return A set of enabled metric names.
+ */
+ Map getEnabledMetrics();
+
+ /**
+ * Returns a set of optional label names for metrics that the sink actually wants.
+ *
+ * @return A set of optional label names.
+ */
+ Set getOptionalLabels();
+
+ /**
+ * Returns size of metric measures used to record metric values. These measures are created
+ * based on registered metrics (via MetricInstrumentRegistry) and are ordered according to their
+ * registration sequence.
+ *
+ * @return Size of metric measures.
+ */
+ int getMeasuresSize();
+
+ /**
+ * Adds a value for a double-precision counter associated with specified metric instrument.
+ *
+ * @param metricInstrument The counter metric instrument identifies metric measure to add.
+ * @param value The value to record.
+ * @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 addDoubleCounter(DoubleCounterMetricInstrument metricInstrument, double value,
+ List requiredLabelValues, List optionalLabelValues) {
+ }
+
+ /**
+ * 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. 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) {
+ }
+
+ /**
+ * 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) {
+ }
+
+ /**
+ * Records a value for a double-precision histogram metric associated with specified metric
+ * instrument.
+ *
+ * @param metricInstrument The histogram metric instrument identifies metric measure to record.
+ * @param value The value to record.
+ * @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 recordDoubleHistogram(DoubleHistogramMetricInstrument metricInstrument, double value,
+ List requiredLabelValues, List optionalLabelValues) {
+ }
+
+ /**
+ * Records a value for a long valued histogram metric associated with specified metric
+ * instrument.
+ *
+ * @param metricInstrument The histogram metric instrument identifies metric measure to record.
+ * @param value The value to record.
+ * @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 recordLongHistogram(LongHistogramMetricInstrument metricInstrument, long value,
+ List requiredLabelValues, List optionalLabelValues) {
+ }
+
+ /**
+ * Record a long gauge value.
+ *
+ * @param value The value to record.
+ * @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 recordLongGauge(LongGaugeMetricInstrument metricInstrument, long value,
+ List requiredLabelValues, List optionalLabelValues){
+ }
+
+ /**
+ * Registers a callback to produce metric values for only the listed instruments. The returned
+ * registration must be closed when no longer needed, which will remove the callback.
+ *
+ * @param callback The callback to call to record.
+ * @param metricInstruments The metric instruments the callback will record against.
+ */
+ default Registration registerBatchCallback(Runnable callback,
+ CallbackMetricInstrument... metricInstruments) {
+ return () -> { };
+ }
+
+ interface Registration extends MetricRecorder.Registration {}
+
+ void updateMeasures(List instruments);
+}
diff --git a/api/src/main/java/io/grpc/NameResolver.java b/api/src/main/java/io/grpc/NameResolver.java
index a74512eb7e3..80bc338d86b 100644
--- a/api/src/main/java/io/grpc/NameResolver.java
+++ b/api/src/main/java/io/grpc/NameResolver.java
@@ -20,20 +20,21 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.base.Objects;
import com.google.errorprone.annotations.InlineMe;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URI;
-import java.util.ArrayList;
import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nullable;
-import javax.annotation.concurrent.ThreadSafe;
+import javax.annotation.concurrent.Immutable;
/**
* A pluggable component that resolves a target {@link URI} and return addresses to the caller.
@@ -76,7 +77,7 @@ public abstract class NameResolver {
* Starts the resolution. The method is not supposed to throw any exceptions. That might cause the
* Channel that the name resolver is serving to crash. Errors should be propagated
* through {@link Listener#onError}.
- *
+ *
* An instance may not be started more than once, by any overload of this method, even after
* an intervening call to {@link #shutdown}.
*
@@ -95,7 +96,14 @@ public void onError(Status error) {
@Override
public void onResult(ResolutionResult resolutionResult) {
- listener.onAddresses(resolutionResult.getAddresses(), resolutionResult.getAttributes());
+ StatusOr> addressesOrError =
+ resolutionResult.getAddressesOrError();
+ if (addressesOrError.hasValue()) {
+ listener.onAddresses(addressesOrError.getValue(),
+ resolutionResult.getAttributes());
+ } else {
+ listener.onError(addressesOrError.getStatus());
+ }
}
});
}
@@ -105,7 +113,7 @@ public void onResult(ResolutionResult resolutionResult) {
* Starts the resolution. The method is not supposed to throw any exceptions. That might cause the
* Channel that the name resolver is serving to crash. Errors should be propagated
* through {@link Listener2#onError}.
- *
+ *
* An instance may not be started more than once, by any overload of this method, even after
* an intervening call to {@link #shutdown}.
*
@@ -149,6 +157,10 @@ public abstract static class Factory {
* cannot be resolved by this factory. The decision should be solely based on the scheme of the
* URI.
*
+ *
This method will eventually be deprecated and removed as part of a migration from {@code
+ * java.net.URI} to {@code io.grpc.Uri}. Implementations will override {@link
+ * #newNameResolver(Uri, Args)} instead.
+ *
* @param targetUri the target URI to be resolved, whose scheme must not be {@code null}
* @param args other information that may be useful
*
@@ -156,6 +168,37 @@ public abstract static class Factory {
*/
public abstract NameResolver newNameResolver(URI targetUri, final Args args);
+ /**
+ * Creates a {@link NameResolver} for the given target URI.
+ *
+ *
Implementations return {@code null} if 'targetUri' cannot be resolved by this factory. The
+ * decision should be solely based on the target's scheme.
+ *
+ *
All {@link NameResolver.Factory} implementations should override this method, as it will
+ * eventually replace {@link #newNameResolver(URI, Args)}. For backwards compatibility, this
+ * default implementation delegates to {@link #newNameResolver(URI, Args)} if 'targetUri' can be
+ * converted to a java.net.URI.
+ *
+ *
NB: Conversion is not always possible, for example {@code scheme:#frag} is a valid {@link
+ * Uri} but not a valid {@link URI} because its path is empty. The default implementation throws
+ * IllegalArgumentException in these cases.
+ *
+ * @param targetUri the target URI to be resolved
+ * @param args other information that may be useful
+ * @throws IllegalArgumentException if targetUri does not have the expected form
+ * @since 1.79
+ */
+ public NameResolver newNameResolver(Uri targetUri, final Args args) {
+ // Not every io.grpc.Uri can be converted but in the ordinary ManagedChannel creation flow,
+ // any IllegalArgumentException thrown here would happened anyway, just earlier. That's
+ // because parse/toString is transparent so java.net.URI#create here sees the original target
+ // string just like it did before the io.grpc.Uri migration.
+ //
+ // Throwing IAE shouldn't surprise non-framework callers either. After all, many existing
+ // Factory impls are picky about targetUri and throw IAE when it doesn't look how they expect.
+ return newNameResolver(URI.create(targetUri.toString()), args);
+ }
+
/**
* Returns the default scheme, which will be used to construct a URI when {@link
* ManagedChannelBuilder#forTarget(String)} is given an authority string instead of a compliant
@@ -171,10 +214,11 @@ public abstract static class Factory {
*
*
All methods are expected to return quickly.
*
+ *
This interface is thread-safe.
+ *
* @since 1.0.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
- @ThreadSafe
public interface Listener {
/**
* Handles updates on resolved addresses and attributes.
@@ -218,19 +262,26 @@ public abstract static class Listener2 implements Listener {
@Override
@Deprecated
@InlineMe(
- replacement = "this.onResult(ResolutionResult.newBuilder().setAddresses(servers)"
- + ".setAttributes(attributes).build())",
- imports = "io.grpc.NameResolver.ResolutionResult")
+ replacement = "this.onResult(ResolutionResult.newBuilder().setAddressesOrError("
+ + "StatusOr.fromValue(servers)).setAttributes(attributes).build())",
+ imports = {"io.grpc.NameResolver.ResolutionResult", "io.grpc.StatusOr"})
public final void onAddresses(
List servers, @ResolutionResultAttr Attributes attributes) {
// TODO(jihuncho) need to promote Listener2 if we want to use ConfigOrError
+ // Calling onResult and not onResult2 because onResult2 can only be called from a
+ // synchronization context.
onResult(
- ResolutionResult.newBuilder().setAddresses(servers).setAttributes(attributes).build());
+ ResolutionResult.newBuilder().setAddressesOrError(
+ StatusOr.fromValue(servers)).setAttributes(attributes).build());
}
/**
* Handles updates on resolved addresses and attributes. If
- * {@link ResolutionResult#getAddresses()} is empty, {@link #onError(Status)} will be called.
+ * {@link ResolutionResult#getAddressesOrError()} is empty, {@link #onError(Status)} will be
+ * called.
+ *
+ * Newer NameResolver implementations should prefer calling onResult2. This method exists to
+ * facilitate older {@link Listener} implementations to migrate to {@link Listener2}.
*
* @param resolutionResult the resolved server addresses, attributes, and Service Config.
* @since 1.21.0
@@ -241,11 +292,31 @@ public final void onAddresses(
* Handles a name resolving error from the resolver. The listener is responsible for eventually
* invoking {@link NameResolver#refresh()} to re-attempt resolution.
*
+ *
New NameResolver implementations should prefer calling onResult2 which will have the
+ * address resolution error in {@link ResolutionResult}'s addressesOrError. This method exists
+ * to facilitate older implementations using {@link Listener} to migrate to {@link Listener2}.
+ *
* @param error a non-OK status
* @since 1.21.0
*/
@Override
public abstract void onError(Status error);
+
+ /**
+ * Handles updates on resolved addresses and attributes. Must be called from the same
+ * {@link SynchronizationContext} available in {@link NameResolver.Args} that is passed
+ * from the channel.
+ *
+ * @param resolutionResult the resolved server addresses or error in address resolution,
+ * attributes, and Service Config or error
+ * @return status indicating whether the resolutionResult was accepted by the listener,
+ * typically the result from a load balancer.
+ * @since 1.66
+ */
+ public Status onResult2(ResolutionResult resolutionResult) {
+ onResult(resolutionResult);
+ return Status.OK;
+ }
}
/**
@@ -257,10 +328,20 @@ public final void onAddresses(
@Documented
public @interface ResolutionResultAttr {}
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11989")
+ @ResolutionResultAttr
+ public static final Attributes.Key ATTR_BACKEND_SERVICE =
+ Attributes.Key.create("io.grpc.NameResolver.ATTR_BACKEND_SERVICE");
+
/**
* Information that a {@link Factory} uses to create a {@link NameResolver}.
*
- *