-
Notifications
You must be signed in to change notification settings - Fork 1.6k
proto: remove or improve registration #268
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
Comments
This provides a more reasonable API for obtaining a FileDescriptorProto and DescriptorProto for a given proto.Message — a process that is currently possible (but undocumented) using the public proto API. The major use case for obtaining a DescriptorProto is to decode custom message options, which are encoded as extensions on the MessageOptions submessage. (See https://developers.google.com/protocol-buffers/docs/proto#customoptions.) Fixes #179. PiperOrigin-RevId: 139356528
I think Registering is breaking people's setups and I am wondering why this is needed in the first place |
@bcmills says
|
Also to be concrete here one place it is breaking people's setups |
Register fixes #179 only because ExtensionsMap was removed, which was another controversial move. |
proto.MessageType and proto.MessageName are nice features, but not really necessary. |
Register has ~nothing to do with #179. #179 requires having a copy of the generated Descriptor proto package; the only impact that registration has on that is to require the generated Descriptor package to be the same one that other uses of Descriptor depend on. (But that's true in general anyway: within a given binary, there should be one and only one copy of each package needed. To do otherwise leads to bloated binaries.) |
This is a Chesterton's Fence. If you want to remove these methods, you should start by understanding what purpose they serve (or served). "Not really necessary" merits further explanation.
Extensions are already error-prone; sacrificing safety is unlikely to help most users. But can you be more specific about the tradeoff you're proposing? What kinds of errors would it fail to detect?
DynamicAny requires the ability to map from a fully-qualified message name to a Go type. That is, it requires |
How does having one copy of the generated Descriptor work with go vendoring? My statement about not really necessary was a little strong, sorry. Thank you for explaining DynamicAny's usage of of proto.MessageType. I have not really thought it through, but don't you think there could be a way to do Extensions without Register, since the user is already passing in the Ok I didn't know about the implementation of DynamicAny using proto.MessageType. Another way might be doing something similar to how extensions work. Ask the user to pass in the correct type to unmarshal Any into. But that will probably be a big change. |
I honestly don't know. The
Yeah, it does seem possible to do extensions without Register. Thinking about it some more it's likely more of an issue for the (Google-internal) "weak" field feature (the one corresponding to That still leaves the problem of |
I'm not sure I get the context here, but using |
Thanks for your really cool response @bcmills I wonder whether:
Then it doesn't seem like Any needs to call "home" to the proto package |
Possible but error-prone. The message name is proto-path-qualified, so it isn't derivable from the Go name for the type; without registration there's no easy way to detect typo'd names. However,
It already supports that usage, and the name checking could be implemented without registration using the same technique as for The major use-case that var ext ptypes.DynamicAny
if err := ptypes.UnmarshalAny(a, &ext); err := nil {
…
}
switch m := ext.Message.(type) {
case *somepb.SomeProtoExtension:
…
case *otherpb.SomeOtherExtension:
…
default:
…
} and the compilation process will ensure that the extensions are valid types and the (implict) names are correct. Without switch ptypes.AnyMessageName(a) {
case "some.proto.path.SomeProtoExtension":
m := new(somepb.SomeProtoExtension)
if err := ptypes.UnmarshalAny(a, m); err != nil {
…
}
…
case "other.proto.path.SomeOtherExtension":
m := new(otherpb.SomeOtherExtension)
if err := ptypes.UnmarshalAny(a, m); err != nil {
…
}
…
default:
…
} and now we've lost the compile-time checking on the paths: we'll have to rely on test coverage to avoid typos in the strings. (We've also introduced a bunch of redundancy in the user code: the message types already know their paths, so why should the user repeat them?) Or, we could avoid var (
someExt somepb.SomeProtoExtension
otherExt otherpb.SomeOtherExtension
)
switch {
case ptypes.UnmarshalAny(a, &someExt) == nil:
…
case ptypes.UnmarshalAny(a, &otherExt) == nil:
…
default:
…
} but that's O(N) for what ought to be an O(1) switch and also requires fairly sophisticated compiler optimizations to avoid wasting memory. (In my experience, one should not expect such optimizations from the There is an equivalent (and somewhat more subtle) use-case for programs which use reflection to process protocol messages using a sort of plugin architecture: currently, a package can use |
I assume the name is the same name that is generated as a parameter to
This is certainly not as simple as the type switch |
I don't really understand how the plugin usecase would work exactly, but I can understand the need for it, since I am pretty sure I have needed this in the past. I planned to do this with |
Trying to unmarshal each message in succession is also not an option, its slow and error prone, since protos are so backwards and forwards compatible. |
We now have a resolver for Any #410 , which means there is one less reason to Register in the first place :) |
I'm in the process of thinking over the Go API for protobufs. I'm noting this here for future reference. A mechanism for registration makes the assumption that there is a one-to-one mapping between Go types and protobuf message types. However, my observation is that this not always true. The following
This seems to be evidence for a n-to-m relationship between Go types and protobuf message types, further indicating that registration needs to be rethought. The discussion that has occured thus far in this issue is also valuable. I have absolutely not fully thought through what would happen if we removed registration. |
If I were to redo protobufs for go, which I would love to do, if I was given the time to do it. |
Proto.Actor https://github.com/AsynkronIT/protoactor-go is heavily dependent on registered types. e.g. if there are 50 messages of the same type being sent, the type name will only occur once in the batch lookup. Redesigning the lookup / type information too much would break our project. |
I think that we have run into this, but not 100% sure it's relevant. In our case, we are wrapping concrete events within a generic event via package util
import (
"reflect"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
)
// UnmarshalPayload uses the internal proto.protoTypes registry to instance a concrete type
func UnmarshalPayload(payload *any.Any) (interface{}, error) {
// Get the message name which is compatible with the golang proto.protoType registry
payloadMessageName, err := ptypes.AnyMessageName(payload)
if err != nil {
return nil, err
}
// Get the type from the registry
payloadMessageType := proto.MessageType(payloadMessageName)
// .Elem() gives us the value of the struct type instead of a pointer
// In other words: thing := MyStruct{}, without Elem() it would be: var thing *MyStruct
// .Interface() is just so we can type assert when passing into UnmarshalAny
payloadInstance := reflect.New(payloadMessageType.Elem()).Interface()
err = ptypes.UnmarshalAny(payload, payloadInstance.(proto.Message))
if err != nil {
return nil, err
}
return payloadInstance, nil
}
// This should be what we use, but seems broken with errors like:
// mismatched message type: got "monolith.Event" want ""
func unmarshalPayload(payload *any.Any) (*ptypes.DynamicAny, error) {
var result ptypes.DynamicAny
err := ptypes.UnmarshalAny(payload, result)
if err != nil {
return nil, err
}
return &result, nil
} |
#567 was closed as a duplicate of this issue. But the conversation up to this point seems to focus more on message registration, like for use with de-serializing In particular, #567 described the problems/fragility inherent with registering files solely by name ( (I'm adding this comment to make sure that addressing the brittle file registration mechanism is included in whatever improvements come out of this issue.) |
We discussed the .proto file name piece @jhump brought up in grpc/grpc#19832. The long and short of it is that protos should have a canonical name, otherwise things break in general. protocolbuffers/protobuf#6492 is tracking the issue in protobuf to make the situation more clear to users and to potentially detect when things are awry. It also references an issue where C++ breaks when imports are non-canonical. |
@ejona86 I agree. The v2 implementation will be moving towards a world where the set of protobuf declarations (i.e., enums, messages, extensions, etc) linked into a binary must be unique. The current situation where half the register functions fail loudly, and the other half silently fail is not okay. |
I guess this is not a problem in Java because the descriptors are loaded (e.g. cross-linked with their dependencies) in static initializers, so if there is a problem linking descriptors, the message classes cannot even be loaded. Maven and gradle plugins for protos also make this much less error-prone than how other repos (such as Go repos) tend to handle managing their proto dependencies. So I guess this will largely be "fixed" by v2 of the go proto APIs since it looks like they also construct and link descriptors during an However, be prepared for the blast radius once v2 is released/GA: there's a good chance it will be a source of new issues filed and an inhibitor to projects upgrading to v2 because they may have dependencies that have unlinkable descriptors. They've basically been able to "get away with it" up to now because there was nothing in package initialization that was checking the validity of the compiled descriptors. |
I agree. For that reason, we'll be easing users into it, but the end goal is to enforce strict unique-ness in the protobuf namespace. Today (in v1), the behavior is that two of the register functions panic on conflicts, two of them log warnings to stderr on conflicts, and two of them silently allow the conflict. This situation is inconsistent and confusing. For the initial release of v2, all registration functions will log warnings to stderr on any conflicts. The warning will 1) warn users that a future release will panic on conflicts, and 2) point users to documentation on how to resolve it. For a later minor release of v2, all registration functions will panic on any conflicts. We may provide an opt-in to go back to the behavior of making them warnings, but that opt-out will not be around forever. |
While developing the new protobuf implementation, we considered removing global registration entirely (the Java approach), but decided that it wasn't feasible if we want to a straightforward way for existing code to migrate. As such, we are double-downing on the concept of a global registry. However, almost every top-level function that consults the global registry can have a custom resolver passed in. The v1.20.0 release implements these details. |
We started a discussion here
cf10ca0#commitcomment-20160444
The text was updated successfully, but these errors were encountered: