Skip to content

Commit b48c057

Browse files
authored
fix: panic with concurrent schema parsing (#502)
1 parent 323f149 commit b48c057

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed

schema.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,17 @@ func (c *SchemaCache) Get(name string) Schema {
131131
return nil
132132
}
133133

134+
// AddAll adds all schemas from the given cache to the current cache.
135+
func (c *SchemaCache) AddAll(cache *SchemaCache) {
136+
if cache == nil {
137+
return
138+
}
139+
cache.cache.Range(func(key, value interface{}) bool {
140+
c.cache.Store(key, value)
141+
return true
142+
})
143+
}
144+
134145
// Schemas is a slice of Schemas.
135146
type Schemas []Schema
136147

schema_parse.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,17 @@ func ParseBytesWithCache(schema []byte, namespace string, cache *SchemaCache) (S
7474
json = string(schema)
7575
}
7676

77+
internalCache := &SchemaCache{}
78+
internalCache.AddAll(cache)
79+
7780
seen := seenCache{}
78-
s, err := parseType(namespace, json, seen, cache)
81+
s, err := parseType(namespace, json, seen, internalCache)
7982
if err != nil {
8083
return nil, err
8184
}
85+
86+
cache.AddAll(internalCache)
87+
8288
return derefSchema(s), nil
8389
}
8490

schema_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package avro_test
33
import (
44
"encoding/json"
55
"strings"
6+
"sync"
67
"testing"
78

89
"github.com/hamba/avro/v2"
@@ -2244,3 +2245,18 @@ func TestNewSchema_IgnoresInvalidProperties(t *testing.T) {
22442245
}, rec.Props())
22452246
})
22462247
}
2248+
2249+
func TestConcurrentParse(t *testing.T) {
2250+
var wg sync.WaitGroup
2251+
2252+
for i := 0; i < 10000; i++ {
2253+
wg.Add(1)
2254+
go func() {
2255+
defer wg.Done()
2256+
_, err := avro.ParseFiles("testdata/concurrent-schema.avsc")
2257+
require.NoError(t, err)
2258+
}()
2259+
}
2260+
2261+
wg.Wait()
2262+
}

testdata/concurrent-schema.avsc

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
{
2+
"type": "record",
3+
"name": "FootballUpdateEvent",
4+
"namespace": "com.example.avro",
5+
"fields": [
6+
{
7+
"name": "event_metadata",
8+
"type": {
9+
"type": "record",
10+
"name": "EventMetadata",
11+
"fields": [
12+
{ "name": "name", "type": "string", "default": "" },
13+
{ "name": "trace", "type": "string", "default": "" },
14+
{ "name": "stamp", "type": "long", "default": 0 },
15+
{ "name": "destination", "type": "string", "default": "" }
16+
]
17+
},
18+
"default": {
19+
"name": "",
20+
"trace": "",
21+
"stamp": 0,
22+
"destination": ""
23+
}
24+
},
25+
{
26+
"name": "player",
27+
"type": {
28+
"type": "record",
29+
"name": "Player",
30+
"fields": [
31+
{ "name": "team_id", "type": "string", "default": "" },
32+
{ "name": "name", "type": "string", "default": "" },
33+
{ "name": "team", "type": "string", "default": "" },
34+
{ "name": "contract", "type": "long", "default": 0 },
35+
{ "name": "xg", "type": "double", "default": 0.0 },
36+
{ "name": "xgp", "type": "double", "default": 0.0 },
37+
{ "name": "xgp99", "type": "double", "default": 0.0 },
38+
{ "name": "xg90", "type": "double", "default": 0.0 },
39+
{
40+
"name": "xg90p",
41+
"type": {
42+
"type": "record",
43+
"name": "MatchXG",
44+
"fields": [
45+
{ "name": "matchXG", "type": "double", "default": 0.0 },
46+
{ "name": "matchXGP", "type": "string", "default": "" }
47+
]
48+
},
49+
"default": {
50+
"matchXG": 0.0,
51+
"matchXGP": ""
52+
}
53+
},
54+
{
55+
"name": "ttd",
56+
"type": "MatchXG",
57+
"default": {
58+
"matchXG": 0.0,
59+
"matchXGP": ""
60+
}
61+
},
62+
{
63+
"name": "leagueXG",
64+
"type": {
65+
"type": "record",
66+
"name": "LeagueXG",
67+
"fields": [
68+
{ "name": "top_assist", "type": "string", "default": "" },
69+
{ "name": "top_score", "type": "string", "default": "" },
70+
{ "name": "top_xg", "type": "string", "default": "" },
71+
{ "name": "top_creation", "type": "string", "default": "" }
72+
]
73+
},
74+
"default": {
75+
"top_assist": "",
76+
"top_score": "",
77+
"top_xg": "",
78+
"top_creation": ""
79+
}
80+
},
81+
{
82+
"name": "player_numbers",
83+
"type": {
84+
"type": "array",
85+
"items": {
86+
"type": "record",
87+
"name": "PlayerNumber",
88+
"fields": [
89+
{ "name": "player_number", "type": "long", "default": 0 }
90+
]
91+
}
92+
},
93+
"default": []
94+
},
95+
{
96+
"name": "contact_renewal",
97+
"type": [
98+
"null",
99+
{
100+
"type": "record",
101+
"name": "ContactRenewal",
102+
"fields": [
103+
{ "name": "stamp", "type": "long", "default": 0 },
104+
{ "name": "type", "type": "string", "default": "" },
105+
{ "name": "xg", "type": "MatchXG", "default": {
106+
"matchXG": 0.0,
107+
"matchXGP": ""
108+
} }
109+
]
110+
}
111+
],
112+
"default": null
113+
},
114+
{ "name": "defence", "type": "string", "default": "" },
115+
{ "name": "offence", "type": "string", "default": "" }
116+
]
117+
},
118+
"default": {}
119+
}
120+
]
121+
}

0 commit comments

Comments
 (0)