We have something special today. It all started with that I updated grpc-java
version from 1.43.2
to 1.60.0
. Alright, I just updated dependency and anticipated it working. However, I run into exception on ManagedChannel build at the client side:
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8081)
.usePlaintext()
.build();
Exception I got looks like this:
java.lang.IllegalArgumentException: Address types of NameResolver 'unix' for 'localhost:8081' not supported by transport
at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:750)
at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:771)
at io.grpc.internal.ManagedChannelImpl.<init>(ManagedChannelImpl.java:635)
at io.grpc.internal.ManagedChannelImplBuilder.build(ManagedChannelImplBuilder.java:668)
at io.grpc.ForwardingChannelBuilder2.build(ForwardingChannelBuilder2.java:260)
at org.example.grpcbase.client.GrpcClient.main(GrpcClient.java:14)
Another modifications:
Exception in thread "main" java.lang.IllegalArgumentException: Could not find a NameResolverProvider for dns:///localhost:8081
at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:741)
at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:771)
at io.grpc.internal.ManagedChannelImpl.<init>(ManagedChannelImpl.java:635)
at io.grpc.internal.ManagedChannelImplBuilder.build(ManagedChannelImplBuilder.java:668)
at io.grpc.ForwardingChannelBuilder2.build(ForwardingChannelBuilder2.java:260)
at org.example.grpcbase.client.GrpcClient.main(GrpcClient.java:19)
Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: connect(..) failed: Address family not supported by protocol: /localhost:8081
Caused by: java.net.ConnectException: connect(..) failed: Address family not supported by protocol
at io.netty.channel.unix.Errors.newConnectException0(Errors.java:155)
at io.netty.channel.unix.Errors.handleConnectErrno(Errors.java:128)
at io.netty.channel.unix.Socket.connect(Socket.java:312)
Exception in thread "main" io.grpc.StatusRuntimeException: UNKNOWN
at io.grpc.stub.ClientCalls.toStatusRuntimeException(ClientCalls.java:271)
at io.grpc.stub.ClientCalls.getUnchecked(ClientCalls.java:252)
at io.grpc.stub.ClientCalls.blockingUnaryCall(ClientCalls.java:165)
at org.example.grpc.base.HelloServiceGrpc$HelloServiceBlockingStub.hello(HelloServiceGrpc.java:157)
at org.example.grpcbase.client.GrpcClient.main(GrpcClient.java:19)
Caused by: java.nio.channels.UnsupportedAddressTypeException
If you’re looking for the simplest & quickest no-brainer workaround (TL;DR) – here you go:
NameResolverRegistry.getDefaultRegistry().register(new DnsNameResolverProvider());
Just add this line before building your ManagedChannel / NettyChannel. If you want elaboration on that or you want to know more correct solution – keep reading further.
Let’s start with some theory knowledge. Two key concepts here are:
- NameResolvers
- ChannelProviders
NameResolver is a gRPC component responsible for resolving the target address of a service. It translates a logical server address (such as a hostname) into a physical addresses (such as IP address) that can be used to establish a connection. gRPC has at least two flavors of it:
- DNS NameResolver. This NameResolver uses the Domain Name System to resolve service names to IP addresses. It’s when you create gRPC channel and specify network address like
localhost:8080
. Whenever you use addresses like that, io.grpc.internal.DnsNameResolverProvider is your choice. - UDS NameResolver. Unix Domain Sockets (UDS) provide a way for processes on the same machine to communicate with each other using socket addresses that are based on file paths. This can be useful in scenarios where communication needs to occur locally without going through the network stack. it’s not suitable for scenarios where clients and servers reside on different machines. This provider is intended to resolve UDS addresses like this:
/tmp/socketname.socket
. Whenever you want to you UDS, io.grpc.netty.UdsNameResolverProvider / io.grpc.netty.shaded.<…> are your choices.
There is a concept of NameResolverRegistry
. It’s the place when gRPC maintains references to all NameResolvers you have in your classpath. It means that:
- If you specify address like
dns:///localhost:8080
, gRPC will search for a NameResolver in NameResolverRegistry, that returns value ofdns
if callinggetDefaultScheme()
method. - If you specify address like
unix:///tmp/socketname.socket
, then gRPC will search for a NameResolver in NameResolverRegistry which returnsunix
as a default scheme.
Here is a getDefaultScheme()
for DnsNameResolverProvider
:
private static final String SCHEME = "dns";
@Override
public String getDefaultScheme() {
return SCHEME;
}
Keep in mind, that it might be the case when you don’t specify your scheme explicitly. Consider localhost:8080
or /tmp/socketname.socket
. When you don’t specify the scheme, NameResolverRegistry
uses getDefaultScheme
method in itself:
public synchronized String getDefaultScheme() {
return defaultScheme;
}
And defaultScheme
field is being populated from the NameResolver with highest priority. Yeah, we have the concept of NameResolver priority. Each NameResolver has its own priority. Let’s look at io.grpc.internal.DnsNameResolverProvider
, for instance:
@Override
public int priority() {
return 5;
}
And UdsNameResolverProvider
has a priority of 3. NameResolver with a greatest priority wins. It means that when you get DNS and UDS resolvers registered in your registry, DNS will be the choice for addresses without schema explicitly specified.
If you’re interested in sorting, here you go, you can see this place in io.grpc.NameResolverRegistry
:
List<NameResolverProvider> providerList = ServiceProviders.loadAll(
NameResolverProvider.class,
getHardCodedClasses(),
NameResolverProvider.class.getClassLoader(),
new NameResolverPriorityAccessor());
NameResolverPriorityAccessor
is a sort of comparator, that compares priority of NameResolvers and chooses one that has greatest priority value.
Returning back to our initial exception. You see that we have managed channel creation like this: ManagedChannelBuilder.forAddress("localhost", 8081)
. Obviously, NameResolverRegistry
should use DNS NameResolver. However, looking one more time at the exception, you see this: Address types of NameResolver 'unix'
. And you’re right, for some reason (more details on this below), NameResolverRegistry
uses UDS NameResolver.
Give or take, now you can see the sense of initial solution: we want to force NameResolverRegistry
to use the right NameResolver.
NameResolverRegistry
, when first created (under the hood of gRPC library), performs loading of NameResolvers using ServiceLoader JDK feature. If you don’t familiar this feature, here you go:
- Imagine plug-in architecture, when you have some common plug-ins interface defined, and its implementations (discrete plug-ins), that you want to connect to your main application in runtime without code modifications.
- Interface, in terms of ServiceLoader feature, is named Service; and its implementations named Service providers.
- Suppose we have Service named Route and two implementations: HomeProvider and GymProvider. We have to have three initial jars: one jar with Route.java interface, one jar with HomeProvider.java class and one jar with GymProvider.java class.
- Finally, you have fourth jar – your application. Connect dependencies from above to it. Now, all you have to do is to call
ServiceLoader<Route> routeProviders = ServiceLoader.load(Route.class)
. - By making use of this feature, you receive an iterator for all providers you have in your classpath. You can play with it a little – just remove dependency with HomeProvider.java, and you see that
ServiceLoader.load()
will result just in one provider, and no exception thrown.
⚠️ Very important note that you have to create file named as fully qualified class name of your Service under <service-provider-jar-root>/META-INF/services
directory, and insert just one line – your Service prover’s fully qualified class name. Examples:
- For GymProvider Service provider
- Create new file
gym-provider/META-INF/services/org.example.service.Route
- Add this line:
org.example.gym.GymProvider
- Create new file
- For HomeProvider Service provider:
- Create new file
home-provider/META-INF/services/org.example.service.Route
- Add this line:
org.example.home.HomeProvider
- Create new file
See llw33-service-loader-example
repository in my GitHub if you want to see ServiceLoader in action: https://github.com/lawlight33/llw33-service-loader-example.
You can see NameResolverRegistry
works same way:
List<NameResolverProvider> providerList = ServiceProviders.loadAll(...);
Going further in io.grpc.ServiceProviders.loadAll()
-> getCandidatesViaServiceLoader()
:
Iterable<T> i = ServiceLoader.load(klass, cl);
Locations for NameResolvers are following:
io.grpc.internal.DnsNameResolverProvider
is being located undergrpc-core
dependencyio.grpc.netty.UdsNameResolverProvider
is being located undergrpc-natty
dependencyio.grpc.netty.shaded.<...>
is being located undergrpc-natty-shaded
.
Every dependency from above has it’s own service definition in META-INF/service directory I mentioned earlier:
- Consider this for DNS resolver: GitHub link to sources.
- This for UDS resolver: GitHub link to sources.
As we spoke earlier, NameResolverRegistry
should use DNS NameResolver. However, it uses UDS NameResolver. The reason beneath this is because most likely you have fat jar, where Maven/Gradle merges these files into one single file! Therefore, you end up with:
<your-fat-jar-root>/META-INF/services/io.grpc.NameResolverProvider
file that obviously has single line inside: io.grpc.netty.UdsNameResolverProvider
.
All you need to do is to change this line to (or add a new one) io.grpc.internal.DnsNameResolverProvider
. You can do this either manually, or by making use of any Maven plugins suitable for that (ant, assembly, shade), or you can define your own service definition file onto your resource folder, that should override any other ones. Same for Gradle. The process can be named as META-INF service definitions merging.
Useful links:
That’s all. If you want to know more about gRPC transport, what are ChannelProviders and the sense of the word “transport” in initial exception, welcome to the next part of our story: https://mchesnavsky.tech/grpc-transport-and-channel-providers.
Telegram channel
If you still have any questions, feel free to ask me in the comments under this article or write me at promark33@gmail.com.
If I saved your day, you can support me 🤝