internal/legacy: guarantee one-to-one generation of wrapper types

When concurrent requests to wrap a type occurs, it is possible that
two different descriptors are created for the same Go type.
Add sufficient synchronization to ensure that one descriptor is ever
returned for one Go type.

Change-Id: Idbbca4c1877a70317b39900ae83bfc3085d4a9c5
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/168398
Reviewed-by: Herbie Ong <herbie@google.com>
diff --git a/internal/legacy/message.go b/internal/legacy/message.go
index c604ec8..cc20053 100644
--- a/internal/legacy/message.go
+++ b/internal/legacy/message.go
@@ -45,10 +45,13 @@
 		p := reflect.New(t.Elem()).Interface()
 		return mt.MessageOf(p)
 	})
-	messageTypeCache.Store(t, mt)
+	if mt, ok := messageTypeCache.LoadOrStore(t, mt); ok {
+		return mt.(*pimpl.MessageType)
+	}
 	return mt
 }
 
+var messageDescLock sync.Mutex
 var messageDescCache sync.Map // map[reflect.Type]protoreflect.MessageDescriptor
 
 // loadMessageDesc returns an MessageDescriptor derived from the Go type,
@@ -70,6 +73,15 @@
 	}
 
 	// Slow-path: initialize MessageDescriptor from the Go type.
+	//
+	// Hold a global lock during message creation to ensure that each Go type
+	// maps to exactly one MessageDescriptor. After obtaining the lock, we must
+	// check again whether the message has already been handled.
+	messageDescLock.Lock()
+	defer messageDescLock.Unlock()
+	if mi, ok := messageDescCache.Load(t); ok {
+		return mi.(pref.MessageDescriptor)
+	}
 
 	// Processing t recursively populates descs and types with all sub-messages.
 	// The descriptor for the first type is guaranteed to be at the front.