-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
reflection: improve server implementation #5197
reflection: improve server implementation #5197
Conversation
With improvements like this, I could also incorporate my custom wrapper around the existing proto registries, like to augment the descriptors with source code info: But, as mentioned in the description, another use case would be a dynamic server/proxy, whose service descriptors might be retrieved dynamically. |
af3c5b2
to
4313fb4
Compare
de01755
to
94c0e5d
Compare
@@ -348,7 +356,7 @@ func testFileContainingSymbol(t *testing.T, stream rpb.ServerReflection_ServerRe | |||
{"grpc.testingv3.SearchResponseV3.Result.Value.val", fdTestv3Byte}, | |||
{"grpc.testingv3.SearchResponseV3.Result.Value.str", fdTestv3Byte}, | |||
{"grpc.testingv3.SearchResponseV3.State", fdTestv3Byte}, | |||
{"grpc.testingv3.SearchResponseV3.State.FRESH", fdTestv3Byte}, | |||
{"grpc.testingv3.SearchResponseV3.FRESH", fdTestv3Byte}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a bug in the previous formulation. In protobuf, enum values are defined at the same scope as their enclosing enum.
reflection/serverreflection.go
Outdated
s.processEnum(fd, prefix, en) | ||
// NewServer returns a reflection server implementation using the given options. | ||
// It returns an error if the given options are invalid. | ||
func NewServer(opts ServerOptions) (rpb.ServerReflectionServer, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious what your thoughts on this style of API, vs. Register
where the server object is never exposed.
One advantage here is that it allows the caller to wrap/decorate the service. (Other formulation requires caller to use an interceptor to do so.)
Also, the options include an optional GRPCServer
. But we'd have to have a server to do the actual Register...
call. So it feels like a confusing API to have a required server argument and then also an optional field in the options struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or, we define an interface with func GetServiceInfo()
(and you won't need the other field ServiceNames []string
).
Did we talk about this interface option before? I don't remember if we got any conclusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is already an interface named GRPCServer
. The list of ServiceNames
is useful if the caller wants more control over exactly which services are advertised by the reflection service.
In a dynamic proxy case, even the list of service names could change over time. So providing the separate names and having this factory for a server (vs. the Register
style function, where the server impl is never exposed) allows you to handle this:
- The caller would create a wrapper for the service impl. The wrapper delegates to the service impl. But it can also swap out the underlying impl in response to an event that changes the set of services. (Like watching services in kubernetes or something like that).
- The actual gRPC server handling is all implemented via an
UnknownServiceHandler
, that can dynamically proxy requests instead of returning "Not Implemented" errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right. I forgot we already made the GRPCServer
interface.
What I meant is a smaller interface, with just GetServiceInfo() map[string]grpc.ServiceInfo
.
You can still pass it the grpc server (that's the default behavior), or the dynamic factory.
Or if you want a static []string
, you can pass it an implementation that returns the list (and you won't need the other field in ServerOptions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I think I see what you mean.
Would you object to me adding a utility function, like ServiceInfoFromNames
, that takes a slice of names ([]string
) and returns this new interface, that provides the service info?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. (I went ahead and added the utility function I asked about. Let me know what you think.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, I realized that the whole sync.Once
initialization was not necessary, since this is no longer trying to accumulate an index. So I've removed it, which I think improves things a bit.
94c0e5d
to
7c1ba55
Compare
1. Support alternate source of descriptors, like for RPC servers that get their descriptors dynamically and are dynamic proxies 2. Use the new protobuf API v2 stuff to get the descriptors, which is much more sane than the old APIs
7c1ba55
to
743e65d
Compare
reflection/serverreflection.go
Outdated
// Optional resolver used to load descriptors. | ||
DescriptorResolver protodesc.Resolver | ||
// Optional resolver used to query for known extensions. | ||
ExtensionResolver ExtensionResolver |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's document the default value for these. If unset, protoregistry.GlobalFiles will be used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. These were documented above. So I rearranged a little and moved the relevant comments down to the fields' Go doc.
reflection/serverreflection.go
Outdated
// NewServer returns a reflection server implementation using the given options. | ||
// It returns an error if the given options are invalid. | ||
func NewServer(opts ServerOptions) (rpb.ServerReflectionServer, error) { | ||
if opts.Server != nil && len(opts.ServiceNames) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Related to the other comment, if we merge these two fields)
We can remove this error. Also clean up the code in Register
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
reflection/serverreflection.go
Outdated
// ServiceInfoFromNames returns a ServiceInfoProvider backed by the given | ||
// slice of service names. This allows for creating a reflection server | ||
// with exactly the given advertised services. | ||
func ServiceInfoFromNames(serviceNames []string) ServiceInfoProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know if you object to this helper. Though I think it's useful since it might not otherwise be obvious how to customize the set of advertised services.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally think this isn't necessary.
If your concern is that the interface is confusing (since only the map keys are used), we can change the interface to return []string
, and wrap *grpc.Server
to implement the interface.
@dfawley WDYT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. I went ahead and removed it and added some extra documentation to the Services
field in ServerOptions
.
011b877
to
5999441
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM. Thanks for the change!
reflection/serverreflection.go
Outdated
for _, msg := range fd.MessageType { | ||
s.processMessage(fd, prefix, msg) | ||
// NewServer returns a reflection server implementation using the given options. | ||
// It returns an error if the given options are invalid. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's document that Register()
is still recommended unless the user wants to customize the server behaviors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And to be a bit more cautious, mark the new functions/types as experimental?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
reflection/serverreflection.go
Outdated
// ServiceInfoFromNames returns a ServiceInfoProvider backed by the given | ||
// slice of service names. This allows for creating a reflection server | ||
// with exactly the given advertised services. | ||
func ServiceInfoFromNames(serviceNames []string) ServiceInfoProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally think this isn't necessary.
If your concern is that the interface is confusing (since only the map keys are used), we can change the interface to return []string
, and wrap *grpc.Server
to implement the interface.
@dfawley WDYT
reflection/serverreflection.go
Outdated
func fileDescWithDependencies(fd *dpb.FileDescriptorProto, sentFileDescriptors map[string]bool) ([][]byte, error) { | ||
r := [][]byte{} | ||
queue := []*dpb.FileDescriptorProto{fd} | ||
func (s *serverReflectionServer) fileDescWithDependencies(fd protoreflect.FileDescriptor, sentFileDescriptors map[string]struct{}) ([][]byte, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On map[string]bool
vs map[string]struct{}
.
- I'm not sure which is more memory efficient, but I think the difference is small enough that we wouldn't care
- In gRPC-Go, we've mostly (but not as an official policy) adopted
map[string]bool
as "set"s. Since if the entry doesn't exist, the map read returns the default bool (which is false). And we don't need to writestruct{}{}
when adding entries.
Let me know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I can walk that part back.
My habit is map[string]struct{}
from my early experiences with Go... Admittedly, since it only elides a single byte from the map entry, thanks to pointer word alignment, it may have zero impact on memory usage 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
…ove ServiceInfoFromNames
…ckage; but sourceinfo.GlobalFiles needs to implement add'l methods to serve extension info
ping @dfawley |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Just a couple small things, though.
reflection/serverreflection.go
Outdated
// | ||
// The reflection service is only interested in the service names, but the | ||
// signature is this way so that *grpc.Server implements it. | ||
type ServiceInfoProvider interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add an Experimental comment here, too, and on all of the newly added exported symbols.
I'd like to give this some time for users to starting using this and provide feedback before declaring anything stable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Added here and to the ExtensionResolver
interface, too.
reflection/serverreflection.go
Outdated
// It is okay for a custom implementation to return zero values for the | ||
// grpc.ServiceInfo values in the map. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we move this comment to ServiceInfoProvider
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll remove this and put more clarification over in the other Go docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
…ted symbols as experimental
… (the newly added stuff), then the unexported server impl
I re-ordered some elements in the file in my last commit, hoping to make the file layout a little more intuitive. All of the new experimental stuff now appears after the preferred non-experimental API elements. I think I've addressed all comments. PTAL and let me know. And thanks for the consideration/review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the code reorganization, thank you for the extra care.
…ckage; but sourceinfo.GlobalFiles needs to implement add'l methods to serve extension info
Thanks to grpc/grpc-go#5197, no longer necessary But sourceinfo.GlobalFiles does need to implement add'l methods to serve extension info
This updates the server reflection implementation in two key ways:
*grpc.Server
.RELEASE NOTES:
NewServer(ServerOptions)
function has been added to thegoogle.golang.org/grpc/reflection
package that allows creating the reflection server with customizations to the list of advertised services and to the mechanism used to lookup descriptors and extensions.