internal/impl: avoid redundant lazy extension inits
After taking the lock on a lazy extension's state, check to see if it
was initialized while we were waiting for the lock.
Change-Id: I1cbd52e9d655eec6c9142c97689ae36f219a28f2
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/216898
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/internal/impl/codec_extension.go b/internal/impl/codec_extension.go
index 141a3cd..1098159 100644
--- a/internal/impl/codec_extension.go
+++ b/internal/impl/codec_extension.go
@@ -110,6 +110,9 @@
func (f *ExtensionField) lazyInit() {
f.lazy.mu.Lock()
defer f.lazy.mu.Unlock()
+ if atomic.LoadUint32(&f.lazy.atomicOnce) == 1 {
+ return
+ }
if f.lazy.xi != nil {
b := f.lazy.b
val := f.typ.New()
@@ -133,7 +136,7 @@
wtyp := wire.Type(tag & 7)
var out unmarshalOutput
var err error
- val, out, err = f.lazy.xi.funcs.unmarshal(b, val, num, wtyp, unmarshalOptions{}) // TODO: options
+ val, out, err = f.lazy.xi.funcs.unmarshal(b, val, num, wtyp, unmarshalOptions{})
if err != nil {
panic(errors.New("decode failure in lazy extension decoding: %v", err))
}
diff --git a/proto/extension_test.go b/proto/extension_test.go
index cf1ef6c..b3a0cff 100644
--- a/proto/extension_test.go
+++ b/proto/extension_test.go
@@ -6,6 +6,7 @@
import (
"fmt"
+ "sync"
"testing"
"github.com/google/go-cmp/cmp"
@@ -66,3 +67,39 @@
}
}
+
+func TestExtensionGetRace(t *testing.T) {
+ // Concurrently fetch an extension value while marshaling the message containing it.
+ // Create the message with proto.Unmarshal to give lazy extension decoding (if present)
+ // a chance to occur.
+ want := int32(42)
+ m1 := &testpb.TestAllExtensions{}
+ proto.SetExtension(m1, testpb.E_OptionalNestedMessageExtension, &testpb.TestAllExtensions_NestedMessage{A: proto.Int32(want)})
+ b, err := proto.Marshal(m1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ m := &testpb.TestAllExtensions{}
+ if err := proto.Unmarshal(b, m); err != nil {
+ t.Fatal(err)
+ }
+ var wg sync.WaitGroup
+ for i := 0; i < 3; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if _, err := proto.Marshal(m); err != nil {
+ t.Error(err)
+ }
+ }()
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ got := proto.GetExtension(m, testpb.E_OptionalNestedMessageExtension).(*testpb.TestAllExtensions_NestedMessage).GetA()
+ if got != want {
+ t.Errorf("GetExtension(optional_nested_message).a = %v, want %v", got, want)
+ }
+ }()
+ }
+ wg.Wait()
+}