Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do the Go-generated protobuf files contain mutex locks?

While working and generating protobuf stubs in go I stumbled upon this interesting issue.

Whenever I try and copy a message's struct by value I get this warning:

call of state.world.script.HandleEvent copies lock value: throne/server/messages.PlayerDialogeStatus contains google.golang.org/protobuf/internal/impl.MessageState contains sync.Mutex copylocks

While I understand why copying a mutex lock by value is wrong, I started wondering why are they even there in the first place.

And thus my question: Why does the go generated protobuf files contain mutex locks placed on the message structs, specifically on the MessageState struct?

Or alternatively: What is the goal of the mutex lock placed in the MessageState struct found on generated protobuf message structs?

like image 592
Nitzan Avatar asked Oct 03 '20 11:10

Nitzan


People also ask

What does a sync mutex lock while it is locked?

A Mutex guarantees only that if something has locked it, it cannot be locked again by something else until the lock is first released. It is up to you to use it correctly, by ensuring that you obtain a lock before you try to access whatever you want protected by the lock, as you've done in your example main .

How do I create a proto Go file?

There are two requirements for the full process. protoc binary installed on your path go get -u github.com/gogo/protobuf/... You can generate the proto files, the marshal/unmarshal and the rest of protobuf stuff for your Go types, the RPC client and server interface and the RPC server implementation for your packages.

How do I create a PB Go file from Proto?

Using protocol buffers with Go To compile the protocol buffer definition, run protoc with the --go_out parameter set to the directory you want to output the Go code to. The generated files will be suffixed . pb.go. See the Test code below for an example using such a file.

What is Go_package in proto file?

This article is all about the go_package option in the Protocol Buffers (Protobuf). The go_package option defines the import path of the package which will contain all the generated code for this file. The Go package name will be the last path component of the import path — Google doc.


Video Answer


2 Answers

The impl.MessageState is embedded in concrete messages only, not in the generated structs that implement a proto message.

It specifically embeds the three pragmas: NoUnkeyedLiterals, DoNotCompare, and DoNotCopy.

The last one, DoNotCopy is a zero-sized array of sync.Mutex. The sole purpose is to have go vet complain loudly about shallow copies, as described in the comment:

DoNotCopy can be embedded in a struct to help prevent shallow copies. This does not rely on a Go language feature, but rather a special case within the vet checker.

The summary of it all: impl.MessageState is not supposed to be copied and the mutex is there only to catch copying. If you do so, it is because you are using something the wrong way.

like image 101
Marc Avatar answered Oct 20 '22 17:10

Marc


As far as I can tell, there are three reasons the Go protobuf API includes the DoNotCopy mutex:

  1. The maintainers may wish to change the internal representation in the future, in a way that will not work with shallow copies.
  2. It is theoretically unsafe to mix atomic and non-atomic accesses to the same memory. The protobuf structs contains an internal field that is usually read and written with an atomic access. Calling msg.Marshal() on a message, then overwriting it with *msg = MyMessage{...} mixes atomic and non-atomic accesses. Even if this works with today's implementation on x86, there is no guarantee this will work on other systems in the future. (See a long Go issue about this).
  3. If you call ProtoReflect() on a message, then later overwrite the message, it will crash, since the ProtoReflect() result relies on the internal reflection pointer (original issue):
d := &durationpb.Duration{Seconds: 1}
protoreflectMessage := d.ProtoReflect()
fmt.Printf("protoreflectMessage.Interface()=%v\n", protoreflectMessage.Interface())
*d = durationpb.Duration{Seconds: 2}
fmt.Printf("protoreflectMessage.Interface()=%v\n", protoreflectMessage.Interface())

This crashes with:

protoreflectMessage.Interface()=seconds:1
panic: invalid nil message info; this suggests memory corruption due to a race or shallow copy on the message struct
like image 31
Evan Jones Avatar answered Oct 20 '22 17:10

Evan Jones