Skip to content

Commit 53a46f4

Browse files
committed
Added SchemaProxy mutex #163
Rendering causes bits to be set on structs in the model. Concurrency causes race issues with reading a writing of these bits, as there is no locking in the model. Until now. locking prevents concurrent renders that use a shared model from conflicting with one another. Addresses #163 and pb33f/libopenapi-validator#23 Signed-off-by: quobix <[email protected]>
1 parent dab6346 commit 53a46f4

3 files changed

Lines changed: 45 additions & 36 deletions

File tree

datamodel/high/base/schema.go

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -176,50 +176,50 @@ func NewSchema(schema *base.Schema) *Schema {
176176
s.UniqueItems = &schema.UniqueItems.Value
177177
}
178178
if !schema.Contains.IsEmpty() {
179-
s.Contains = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
179+
s.Contains = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
180180
ValueNode: schema.Contains.ValueNode,
181181
Value: schema.Contains.Value,
182-
}}
182+
})
183183
}
184184
if !schema.If.IsEmpty() {
185-
s.If = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
185+
s.If = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
186186
ValueNode: schema.If.ValueNode,
187187
Value: schema.If.Value,
188-
}}
188+
})
189189
}
190190
if !schema.Else.IsEmpty() {
191-
s.Else = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
191+
s.Else = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
192192
ValueNode: schema.Else.ValueNode,
193193
Value: schema.Else.Value,
194-
}}
194+
})
195195
}
196196
if !schema.Then.IsEmpty() {
197-
s.Then = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
197+
s.Then = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
198198
ValueNode: schema.Then.ValueNode,
199199
Value: schema.Then.Value,
200-
}}
200+
})
201201
}
202202
if !schema.PropertyNames.IsEmpty() {
203-
s.PropertyNames = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
203+
s.PropertyNames = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
204204
ValueNode: schema.PropertyNames.ValueNode,
205205
Value: schema.PropertyNames.Value,
206-
}}
206+
})
207207
}
208208
if !schema.UnevaluatedItems.IsEmpty() {
209-
s.UnevaluatedItems = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
209+
s.UnevaluatedItems = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
210210
ValueNode: schema.UnevaluatedItems.ValueNode,
211211
Value: schema.UnevaluatedItems.Value,
212-
}}
212+
})
213213
}
214214
// check if unevaluated properties is a schema
215215
if !schema.UnevaluatedProperties.IsEmpty() && schema.UnevaluatedProperties.Value.IsA() {
216216
s.UnevaluatedProperties = &DynamicValue[*SchemaProxy, *bool]{
217-
A: &SchemaProxy{
218-
schema: &lowmodel.NodeReference[*base.SchemaProxy]{
217+
A: NewSchemaProxy(
218+
&lowmodel.NodeReference[*base.SchemaProxy]{
219219
ValueNode: schema.UnevaluatedProperties.ValueNode,
220220
Value: schema.UnevaluatedProperties.Value.A,
221221
},
222-
},
222+
),
223223
N: 0,
224224
}
225225
}
@@ -325,11 +325,11 @@ func NewSchema(schema *base.Schema) *Schema {
325325

326326
// for every item, build schema async
327327
buildSchema := func(sch lowmodel.ValueReference[*base.SchemaProxy], idx int, bChan chan buildResult) {
328-
p := &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
328+
p := NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
329329
ValueNode: sch.ValueNode,
330330
Value: sch.Value,
331331
Reference: sch.GetReference(),
332-
}}
332+
})
333333

334334
bChan <- buildResult{idx: idx, s: p}
335335
}
@@ -358,13 +358,12 @@ func NewSchema(schema *base.Schema) *Schema {
358358
buildProps := func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy],
359359
props map[string]*SchemaProxy, sw int,
360360
) {
361-
props[k.Value] = &SchemaProxy{
362-
schema: &lowmodel.NodeReference[*base.SchemaProxy]{
363-
Value: v.Value,
364-
KeyNode: k.KeyNode,
365-
ValueNode: v.ValueNode,
366-
},
367-
}
361+
props[k.Value] = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
362+
Value: v.Value,
363+
KeyNode: k.KeyNode,
364+
ValueNode: v.ValueNode,
365+
},
366+
)
368367

369368
switch sw {
370369
case 0:
@@ -418,11 +417,13 @@ func NewSchema(schema *base.Schema) *Schema {
418417
}
419418
if !schema.Items.IsEmpty() {
420419
if schema.Items.Value.IsA() {
421-
items = &DynamicValue[*SchemaProxy, bool]{A: &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
422-
ValueNode: schema.Items.ValueNode,
423-
Value: schema.Items.Value.A,
424-
KeyNode: schema.Items.KeyNode,
425-
}}}
420+
items = &DynamicValue[*SchemaProxy, bool]{
421+
A: NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{
422+
ValueNode: schema.Items.ValueNode,
423+
Value: schema.Items.Value.A,
424+
KeyNode: schema.Items.KeyNode,
425+
},
426+
)}
426427
} else {
427428
items = &DynamicValue[*SchemaProxy, bool]{N: 1, B: schema.Items.Value.B}
428429
}

datamodel/high/base/schema_proxy.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/pb33f/libopenapi/datamodel/low/base"
1010
"github.com/pb33f/libopenapi/utils"
1111
"gopkg.in/yaml.v3"
12+
"sync"
1213
)
1314

1415
// SchemaProxy exists as a stub that will create a Schema once (and only once) the Schema() method is called. An
@@ -49,40 +50,47 @@ type SchemaProxy struct {
4950
buildError error
5051
rendered *Schema
5152
refStr string
53+
lock *sync.Mutex
5254
}
5355

5456
// NewSchemaProxy creates a new high-level SchemaProxy from a low-level one.
5557
func NewSchemaProxy(schema *low.NodeReference[*base.SchemaProxy]) *SchemaProxy {
56-
return &SchemaProxy{schema: schema}
58+
return &SchemaProxy{schema: schema, lock: &sync.Mutex{}}
5759
}
5860

5961
// CreateSchemaProxy will create a new high-level SchemaProxy from a high-level Schema, this acts the same
6062
// as if the SchemaProxy is pre-rendered.
6163
func CreateSchemaProxy(schema *Schema) *SchemaProxy {
62-
return &SchemaProxy{rendered: schema}
64+
return &SchemaProxy{rendered: schema, lock: &sync.Mutex{}}
6365
}
6466

6567
// CreateSchemaProxyRef will create a new high-level SchemaProxy from a reference string, this is used only when
6668
// building out new models from scratch that require a reference rather than a schema implementation.
6769
func CreateSchemaProxyRef(ref string) *SchemaProxy {
68-
return &SchemaProxy{refStr: ref}
70+
return &SchemaProxy{refStr: ref, lock: &sync.Mutex{}}
6971
}
7072

7173
// Schema will create a new Schema instance using NewSchema from the low-level SchemaProxy backing this high-level one.
7274
// If there is a problem building the Schema, then this method will return nil. Use GetBuildError to gain access
7375
// to that building error.
7476
func (sp *SchemaProxy) Schema() *Schema {
77+
sp.lock.Lock()
7578
if sp.rendered == nil {
79+
7680
s := sp.schema.Value.Schema()
7781
if s == nil {
7882
sp.buildError = sp.schema.Value.GetBuildError()
83+
sp.lock.Unlock()
7984
return nil
8085
}
8186
sch := NewSchema(s)
8287
sch.ParentProxy = sp
88+
8389
sp.rendered = sch
90+
sp.lock.Unlock()
8491
return sch
8592
} else {
93+
sp.lock.Unlock()
8694
return sp.rendered
8795
}
8896
}

datamodel/high/base/schema_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestNewSchemaProxy(t *testing.T) {
5757
ValueNode: idxNode.Content[0],
5858
}
5959

60-
sch1 := SchemaProxy{schema: &lowproxy}
60+
sch1 := NewSchemaProxy(&lowproxy)
6161
assert.Nil(t, sch1.Schema())
6262
assert.Error(t, sch1.GetBuildError())
6363

@@ -98,7 +98,7 @@ func TestNewSchemaProxyRender(t *testing.T) {
9898
ValueNode: idxNode.Content[0],
9999
}
100100

101-
sch1 := SchemaProxy{schema: &lowproxy}
101+
sch1 := NewSchemaProxy(&lowproxy)
102102
assert.NotNil(t, sch1.Schema())
103103
assert.NoError(t, sch1.GetBuildError())
104104

@@ -1125,7 +1125,7 @@ components:
11251125
ValueNode: idxNode.Content[0],
11261126
}
11271127

1128-
sch1 := SchemaProxy{schema: &lowproxy}
1128+
sch1 := NewSchemaProxy(&lowproxy)
11291129
compiled := sch1.Schema()
11301130

11311131
// now render it out, it should be identical.
@@ -1177,7 +1177,7 @@ components:
11771177
ValueNode: idxNode.Content[0],
11781178
}
11791179

1180-
sch1 := SchemaProxy{schema: &lowproxy}
1180+
sch1 := NewSchemaProxy(&lowproxy)
11811181
compiled := sch1.Schema()
11821182

11831183
// now render it out, it should be identical.

0 commit comments

Comments
 (0)