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

Add support for anonymous in-process servers. #8589

Merged
merged 11 commits into from Oct 25, 2021

Conversation

markb74
Copy link
Contributor

@markb74 markb74 commented Oct 10, 2021

Anonymous servers aren't registered statically, meaning they can't be referenced by name.
Only the InProcessSocketAddress, fetched via Server.getListenSockets()
can be used to connect to the server.

We also support the creation of in-process channels via forTarget(String), which
is particularly useful for production Android usage of in-process servers,
where process startup latency is crucial. A custom name resolver can be
used to create the server instance on demand without directly impacting
the startup latency of in-process gRPC clients.

This supports a more-standard approach to "OnDeviceServer" referenced in gRFC L73.
https://github.com/grpc/proposal/blob/master/L73-java-binderchannel.md#ondeviceserver

@markb74 markb74 requested a review from ejona86 October 10, 2021 18:49
}
}

@Override
public SocketAddress getListenSocketAddress() {
return new InProcessSocketAddress(name);
return new InProcessSocketAddress(name, anonymous ? this : null);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This choice is notable. The address instance will only reference the server if it's anonymous.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I think that is the correct behavior.

At first I was like, "oh, definitely," then I realized "but that might produce weird behavior/semantics" because depending on how the address was created you'd get different behavior. But I actually think it works well. Essentially it is the same as the anonymous server having a randomly-generated ID that is never reused.

Anonymous servers aren't registered statically, meaning they can't be referenced by name.
Only the InProcessSocketAddress, fetched via Server.getListenSockets()
can be used to connect to the server.

This is particularly useful for production Android usage of in-process servers,
where process startup latency is crucial, since a custom name resolver can be
used to create the server instance on demand without directly impacting
the startup latency of in-process gRPC clients.

This approach supports a more-standard approach to "OnDeviceServer" referenced in gRFC L73.
https://github.com/grpc/proposal/blob/master/L73-java-binderchannel.md#ondeviceserver
@ejona86
Copy link
Member

ejona86 commented Oct 13, 2021

This is particularly useful for production Android usage of in-process servers,
where process startup latency is crucial, since a custom name resolver can be
used to create the server instance on demand without directly impacting
the startup latency of in-process gRPC clients.

That's technically possible without anonymous in-process servers. That just requires InProcessChannelBuilder.forTarget().

Copy link
Member

@ejona86 ejona86 left a comment

Choose a reason for hiding this comment

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

Overall, looks good and is surprisingly minor tweaks. The SocketAddress identity thing though is an important bug to resolve.

}
}

@Override
public SocketAddress getListenSocketAddress() {
return new InProcessSocketAddress(name);
return new InProcessSocketAddress(name, anonymous ? this : null);
Copy link
Member

Choose a reason for hiding this comment

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

Yes, I think that is the correct behavior.

At first I was like, "oh, definitely," then I realized "but that might produce weird behavior/semantics" because depending on how the address was created you'd get different behavior. But I actually think it works well. Essentially it is the same as the anonymous server having a randomly-generated ID that is never reused.

core/src/main/java/io/grpc/inprocess/InProcessServer.java Outdated Show resolved Hide resolved
@markb74
Copy link
Contributor Author

markb74 commented Oct 14, 2021

This is particularly useful for production Android usage of in-process servers,
where process startup latency is crucial, since a custom name resolver can be
used to create the server instance on demand without directly impacting
the startup latency of in-process gRPC clients.

That's technically possible without anonymous in-process servers. That just requires InProcessChannelBuilder.forTarget().

Good point, I'd meant to mention that change explicitly. Updated.

Copy link
Member

@ejona86 ejona86 left a comment

Choose a reason for hiding this comment

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

Looks pretty good, I have two concerns:

  1. Always using "anonymous" in the InProcessSocketAddress. If I use InProcessServer.forName("anonymous") the two socket addresses will look identical, but won't be equal/interchangable. Essentially, there's no reliable way to learn the address is anonymous.
  2. InProcessServer.anonymous() seems slightly weak, in that it isn't obvious it would create a new builder instance. We may want newAnonymous() or the like. I've added this to the API review schedule to discuss.

@ejona86
Copy link
Member

ejona86 commented Oct 14, 2021

API review:

  • No client-side concerns
  • InProcessServer.anonymous()
    • forAnonymous()
    • newAnonymous()
    • forAnonymousServer()
  • InProcessSocketAddress.getName()
    • “Anonymous”
      • Could disallow forName(“anonymous”)
    • “unlikely.to.be.used.anonymous”
      • Could disallow forName(“unlikely.to.be.used.anonymous”)
      • +.5
    • Random string
    • Null
      • +4
      • +.5
  • Some concerns about how the address holds a reference to the server. Discussed how the current implementation is pretty similar to WeakHashMap<InProcessSocketAddress, InProcessServer> (although it can be slow to drop the strong references after the weak ref disappears) and there was mention that such a registry made more sense.

We ran out of time in the meeting. It seems we should maybe schedule a one-off meeting to discuss this, since I sense some resistance to concepts/behavior that make anonymous memory-only; if anonymous servers had to be shut down, it seems that'd simplify the conversation.

Created a new AnonymousInProcessSocketAddress class which is
distinct from the existing InProcessSocketAddress.

This simplifies the server builder API since we can have
forAddress() instead of anonymous(), and removes the need for
InProcessSocketAddress to handle anonymous servers, which had some
tricy implications like getName() returning null.
}
InProcessServer otherServer = ((AnonymousInProcessSocketAddress) obj).getServer();
synchronized (this) {
return otherServer == server;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Notable choice: I chose to let two instances to be equal if they share the same server instance. There's currently no way that can happen (given InProcessServer's impl), but I figured it was a reasonable choice from this classes perspective.

Copy link
Member

Choose a reason for hiding this comment

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

Since this address is the central registration location, this should use instance equality of the address. So we shouldn't have equals/hashCode at all here. We also don't want hashCode() to change over time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh good point re: hashcode, I missed that. Done.

@@ -40,11 +40,16 @@
private static final ConcurrentMap<String, InProcessServer> registry
= new ConcurrentHashMap<>();

static InProcessServer findServer(String name) {
Copy link
Contributor Author

@markb74 markb74 Oct 21, 2021

Choose a reason for hiding this comment

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

The possibility of creating a base AbstractInProcessSocketAddress class with package visible findServer(), registerServer(InProcessServer) and unregisterServer(InProcessServer) methods occurred to me.

That would allow this method, and registerInstance() and unregisterInstance() below to just be methods on the address, removing the need for instance checks.

Copy link
Member

Choose a reason for hiding this comment

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

Optimizing this method seems like a micro-optimization. If this method is the only one that benefits, then avoiding public API seems clearly superior. If we think user's code would improve, then common base class is more interesting. Seems we can always do it in the future, so let's favor less API for now.

Your suggestion would also mean moving the registry to InProcessSocketAddress, which propagates the "strangeness" of anonymous socket address. Seems counter to our preferences.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. I was attracted to the approach making everything more consistent, even if that does mean "embracing the strangeness" to some degree. But I like philosophy that improving user code is the key goal, so agreed this probably isn't worth it.

}
InProcessServer otherServer = ((AnonymousInProcessSocketAddress) obj).getServer();
synchronized (this) {
return otherServer == server;
Copy link
Member

Choose a reason for hiding this comment

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

Since this address is the central registration location, this should use instance equality of the address. So we shouldn't have equals/hashCode at all here. We also don't want hashCode() to change over time.

@@ -40,11 +40,16 @@
private static final ConcurrentMap<String, InProcessServer> registry
= new ConcurrentHashMap<>();

static InProcessServer findServer(String name) {
Copy link
Member

Choose a reason for hiding this comment

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

Optimizing this method seems like a micro-optimization. If this method is the only one that benefits, then avoiding public API seems clearly superior. If we think user's code would improve, then common base class is more interesting. Seems we can always do it in the future, so let's favor less API for now.

Your suggestion would also mean moving the registry to InProcessSocketAddress, which propagates the "strangeness" of anonymous socket address. Seems counter to our preferences.

core/src/main/java/io/grpc/inprocess/InProcessServer.java Outdated Show resolved Hide resolved
@ejona86
Copy link
Member

ejona86 commented Oct 21, 2021

API review notes:

  • To side-step naming and getName() issues, should we make anonymous first-class in the address and have the address power the anonymous feature?
  • Use a different class AnonymousInProcessSocketAddress extends SocketAddress
    • +many (we were already well over time)
  • Given the above, drop explicit anonymous API from server API. Use forAddress(SocketAddress) on server-side instead
    • Mirrors NettyServerBuilder.forAddress()

There was enough agreement for anonymous at this point that everyone felt comfortable with the feature moving forward. We agreed any nits could be handled after this PR went in.

@markb74 markb74 requested a review from ejona86 October 25, 2021 15:58
Copy link
Member

@ejona86 ejona86 left a comment

Choose a reason for hiding this comment

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

Only one comment, and it is probably-good-to-have-but-not-a-blocker.

@markb74 markb74 merged commit 607362a into grpc:master Oct 25, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 24, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants