Skip to content

Commit 3392408

Browse files
authored
Merge pull request #307 from huww98/fix-race
Fix data race
2 parents 446d839 + 40cac0c commit 3392408

File tree

2 files changed

+93
-20
lines changed

2 files changed

+93
-20
lines changed

schema/elements.go

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package schema
1818

1919
import (
2020
"sync"
21+
"sync/atomic"
2122
)
2223

2324
// Schema is a list of named types.
@@ -28,7 +29,7 @@ type Schema struct {
2829
Types []TypeDef `yaml:"types,omitempty"`
2930

3031
once sync.Once
31-
m map[string]TypeDef
32+
m atomic.Pointer[map[string]TypeDef]
3233

3334
lock sync.Mutex
3435
// Cached results of resolving type references to atoms. Only stores
@@ -144,26 +145,28 @@ type Map struct {
144145
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
145146

146147
once sync.Once
147-
m map[string]StructField
148+
m atomic.Pointer[map[string]StructField]
148149
}
149150

150151
// FindField is a convenience function that returns the referenced StructField,
151152
// if it exists, or (nil, false) if it doesn't.
152153
func (m *Map) FindField(name string) (StructField, bool) {
153154
m.once.Do(func() {
154-
m.m = make(map[string]StructField, len(m.Fields))
155+
mm := make(map[string]StructField, len(m.Fields))
155156
for _, field := range m.Fields {
156-
m.m[field.Name] = field
157+
mm[field.Name] = field
157158
}
159+
m.m.Store(&mm)
158160
})
159-
sf, ok := m.m[name]
161+
sf, ok := (*m.m.Load())[name]
160162
return sf, ok
161163
}
162164

163-
// CopyInto this instance of Map into the other
164-
// If other is nil this method does nothing.
165-
// If other is already initialized, overwrites it with this instance
166-
// Warning: Not thread safe
165+
// CopyInto clones this instance of Map into dst
166+
//
167+
// If dst is nil this method does nothing.
168+
// If dst is already initialized, overwrites it with this instance.
169+
// Warning: Not thread safe. Only use dst after this function returns.
167170
func (m *Map) CopyInto(dst *Map) {
168171
if dst == nil {
169172
return
@@ -175,12 +178,13 @@ func (m *Map) CopyInto(dst *Map) {
175178
dst.Unions = m.Unions
176179
dst.ElementRelationship = m.ElementRelationship
177180

178-
if m.m != nil {
181+
mm := m.m.Load()
182+
if mm != nil {
179183
// If cache is non-nil then the once token had been consumed.
180184
// Must reset token and use it again to ensure same semantics.
181185
dst.once = sync.Once{}
182186
dst.once.Do(func() {
183-
dst.m = m.m
187+
dst.m.Store(mm)
184188
})
185189
}
186190
}
@@ -274,12 +278,13 @@ type List struct {
274278
// if it exists, or (nil, false) if it doesn't.
275279
func (s *Schema) FindNamedType(name string) (TypeDef, bool) {
276280
s.once.Do(func() {
277-
s.m = make(map[string]TypeDef, len(s.Types))
281+
sm := make(map[string]TypeDef, len(s.Types))
278282
for _, t := range s.Types {
279-
s.m[t.Name] = t
283+
sm[t.Name] = t
280284
}
285+
s.m.Store(&sm)
281286
})
282-
t, ok := s.m[name]
287+
t, ok := (*s.m.Load())[name]
283288
return t, ok
284289
}
285290

@@ -352,10 +357,11 @@ func (s *Schema) Resolve(tr TypeRef) (Atom, bool) {
352357
return result, true
353358
}
354359

355-
// Clones this instance of Schema into the other
356-
// If other is nil this method does nothing.
357-
// If other is already initialized, overwrites it with this instance
358-
// Warning: Not thread safe
360+
// CopyInto clones this instance of Schema into dst
361+
//
362+
// If dst is nil this method does nothing.
363+
// If dst is already initialized, overwrites it with this instance.
364+
// Warning: Not thread safe. Only use dst after this function returns.
359365
func (s *Schema) CopyInto(dst *Schema) {
360366
if dst == nil {
361367
return
@@ -364,12 +370,13 @@ func (s *Schema) CopyInto(dst *Schema) {
364370
// Schema type is considered immutable so sharing references
365371
dst.Types = s.Types
366372

367-
if s.m != nil {
373+
sm := s.m.Load()
374+
if sm != nil {
368375
// If cache is non-nil then the once token had been consumed.
369376
// Must reset token and use it again to ensure same semantics.
370377
dst.once = sync.Once{}
371378
dst.once.Do(func() {
372-
dst.m = s.m
379+
dst.m.Store(sm)
373380
})
374381
}
375382
}

schema/elements_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,48 @@ func TestFindField(t *testing.T) {
8787
}
8888
}
8989

90+
func testMap() *Map {
91+
return &Map{
92+
Fields: []StructField{
93+
{
94+
Name: "a",
95+
Type: TypeRef{NamedType: strptr("aaa")},
96+
}, {
97+
Name: "b",
98+
Type: TypeRef{NamedType: strptr("bbb")},
99+
}, {
100+
Name: "c",
101+
Type: TypeRef{NamedType: strptr("ccc")},
102+
},
103+
},
104+
}
105+
}
106+
107+
func BenchmarkFindFieldCached(b *testing.B) {
108+
m := testMap()
109+
m.FindField("a")
110+
for i := 0; i < b.N; i++ {
111+
m.FindField("a")
112+
}
113+
}
114+
115+
func BenchmarkFindFieldNew(b *testing.B) {
116+
for i := 0; i < b.N; i++ {
117+
m := testMap()
118+
m.FindField("a")
119+
}
120+
}
121+
122+
// As a baseline of BenchmarkFindFieldNew
123+
func BenchmarkMakeMap(b *testing.B) {
124+
var m *Map
125+
for i := 0; i < b.N; i++ {
126+
m = testMap()
127+
}
128+
b.StopTimer()
129+
b.Log(m) // prevent dead code elimination
130+
}
131+
90132
func TestResolve(t *testing.T) {
91133
existing := "existing"
92134
notExisting := "not-existing"
@@ -177,3 +219,27 @@ func TestCopyInto(t *testing.T) {
177219
})
178220
}
179221
}
222+
223+
func TestMapCopyInto(t *testing.T) {
224+
s := Map{
225+
Fields: []StructField{
226+
{
227+
Name: "a",
228+
Type: TypeRef{NamedType: strptr("aaa")},
229+
},
230+
},
231+
}
232+
theCopy := Map{}
233+
234+
assert := func(sf StructField, ok bool) {
235+
if !ok || *sf.Type.NamedType != "aaa" {
236+
t.Error("expected NamedType aaa not found")
237+
}
238+
}
239+
240+
go func() {
241+
s.CopyInto(&theCopy)
242+
assert(theCopy.FindField("a"))
243+
}()
244+
assert(s.FindField("a"))
245+
}

0 commit comments

Comments
 (0)