Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Eager connect flag for Netty based client #1931

Merged
merged 4 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/main/paradox/client/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Currently service discovery is only queried on creation of the client. A client

## Debug logging

To enable fine grained debug running the following logging configuration can be used.
To enable fine-grained debug running the following logging configuration can be used.

Put this in a file `grpc-debug-logging.properties`:

Expand Down
11 changes: 11 additions & 0 deletions docs/src/main/paradox/client/details.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ connection has been closed or shutdown due to errors, respectively. When you are
you should call `close` on the channel, rather than the individual clients. Calling `close` on a generated client
that was created with a shared channel will throw a @apidoc[GrpcClientCloseException].

## Channel laziness

The Netty based channel backing a client will only actually connect once the client sees a first request. It is possible
to configure it to eagerly connect as soon as the client, or channel, is created by setting `eager-connection = true` or
programmatically through @apidoc[GrpcClientSettings] `withEagerConnection`.

If the client does not see any traffic it will transition to an idle state, and close the connection, after 5 minutes.
You can find some more details about the Netty channel behavior in [the grpc connectivity-semantics-and-api docs](https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md).

The Akka HTTP backed client always connects eagerly.

## Load balancing

When multiple endpoints are discovered for a gRPC client, currently one is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ class GreeterSpec extends Matchers with AnyWordSpecLike with BeforeAndAfterAll w
val reply = clients.head.sayHello(HelloRequest("Alice"))
reply.futureValue should ===(HelloReply("Hello, Alice", Some(Timestamp.apply(123456, 123))))
}

"reply to single request (eager connect client)" in {
val eagerClient = GreeterServiceClient(
GrpcClientSettings
.connectToServiceAt("127.0.0.1", 8080)(clientSystem.asInstanceOf[ClassicActorSystemProvider])
.withEagerConnection(true)
.withTls(false))(clientSystem.asInstanceOf[ClassicActorSystemProvider])
// no clear way to test it actually connected eagerly, but at least cover that it works
val reply = eagerClient.sayHello(HelloRequest("Alice"))
reply.futureValue should ===(HelloReply("Hello, Alice", Some(Timestamp.apply(123456, 123))))
}
}

"GreeterServicePowerApi" should {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# eager client connections, internal constructor
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.grpc.GrpcClientSettings.this")
12 changes: 8 additions & 4 deletions runtime/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ akka.grpc.client."*" {
# Host to use if service-discovery-mechanism is set to static or grpc-dns
host = ""

# Service discovery mechamism to use. The default is to use a static host
johanandren marked this conversation as resolved.
Show resolved Hide resolved
# and port that will be resolved via DNS.
# Any of the mechanisms described in https://doc.akka.io/docs/akka-management/current/discovery/index.html can be used
# including Kubernetes, Consul, AWS API
service-discovery {
mechanism = "static"
# Service name to use if a service-discovery.mechanism other than static or grpc-dns
Expand Down Expand Up @@ -54,9 +58,9 @@ akka.grpc.client."*" {
# interpreted as retrying 'indefinitely'.
connection-attempts = 20

# Service discovery mechamism to use. The default is to use a static host
# and port that will be resolved via DNS.
# Any of the mechanisms described in https://doc.akka.io/docs/akka-management/current/discovery/index.html can be used
# including Kubernetes, Consul, AWS API
# Request that the client try to connect the service immediately when the client is created
# rather than on the first request. Only supported for the Netty client backend, the akka-http client backend
# is always eager.
johanandren marked this conversation as resolved.
Show resolved Hide resolved
eager-connection = off
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add something to the client reference docs about this? Seems like something you want to enable most of the time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure it is something you want enabled most of the time, it since it would also means you might create a bunch of connections for clients that don't see any traffic so wastes resource usage (at least I'm guessing that is why it is lazy by default). For the record clients will also idle timeout after a default 5 minutes without any requests and switch to an "IDLE" state and only reconnect when a new request is made.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioning in docs seems good though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, yes we should not change the default

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 6222128

}
//#defaults
20 changes: 16 additions & 4 deletions runtime/src/main/scala/akka/grpc/GrpcClientSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ object GrpcClientSettings {
getOptionalString(clientConfiguration, "load-balancing-policy"),
clientConfiguration.getString("backend"),
identity,
getOptionalDuration(clientConfiguration, "service-discovery.refresh-interval"))
getOptionalDuration(clientConfiguration, "service-discovery.refresh-interval"),
clientConfiguration.getBoolean("eager-connection"))

private def getOptionalString(config: Config, path: String): Option[String] =
config.getString(path) match {
Expand Down Expand Up @@ -205,7 +206,8 @@ final class GrpcClientSettings private (
val loadBalancingPolicy: Option[String],
val backend: String,
val channelBuilderOverrides: NettyChannelBuilder => NettyChannelBuilder = identity,
val discoveryRefreshInterval: Option[FiniteDuration]) {
val discoveryRefreshInterval: Option[FiniteDuration],
val eagerConnection: Boolean) {
require(
sslContext.isEmpty || trustManager.isEmpty,
"Configuring the sslContext or the trustManager is mutually exclusive")
Expand Down Expand Up @@ -315,6 +317,14 @@ final class GrpcClientSettings private (
def withDiscoveryRefreshInterval(refreshInterval: java.time.Duration): GrpcClientSettings =
copy(discoveryRefreshInterval = Some(refreshInterval.asScala))

/**
* Request that the client try to connect the service immediately when the client is created
* rather than on the first request. Only supported for the Netty client backend, the akka-http client backend
* is always eager.
*/
def withEagerConnection(eagerConnection: Boolean): GrpcClientSettings =
copy(eagerConnection = eagerConnection)

private def copy(
serviceName: String = serviceName,
servicePortName: Option[String] = servicePortName,
Expand All @@ -333,7 +343,8 @@ final class GrpcClientSettings private (
loadBalancingPolicy: Option[String] = loadBalancingPolicy,
backend: String = backend,
channelBuilderOverrides: NettyChannelBuilder => NettyChannelBuilder = channelBuilderOverrides,
discoveryRefreshInterval: Option[FiniteDuration] = discoveryRefreshInterval): GrpcClientSettings =
discoveryRefreshInterval: Option[FiniteDuration] = discoveryRefreshInterval,
eagerConnection: Boolean = eagerConnection): GrpcClientSettings =
new GrpcClientSettings(
callCredentials = callCredentials,
serviceDiscovery = serviceDiscovery,
Expand All @@ -353,5 +364,6 @@ final class GrpcClientSettings private (
loadBalancingPolicy = loadBalancingPolicy,
backend = backend,
channelBuilderOverrides = channelBuilderOverrides,
discoveryRefreshInterval = discoveryRefreshInterval)
discoveryRefreshInterval = discoveryRefreshInterval,
eagerConnection = eagerConnection)
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ object NettyClientUtils {
channelClosedPromise.tryFailure(e)
}

if (settings.eagerConnection)
channel.getState(true)

new InternalChannel {
override def shutdown() = channel.shutdown()
override def done = channelClosedPromise.future
Expand Down
Loading