Skip to content

Commit 9948f69

Browse files
authored
config: protect global configOpts with a mutex (#296)
There is a race condition in configOpts when one goroutine calls OverwriteConfigOpts while another reads the variable. This is common, as almost every method in config.C reads this global variable. Fix the data race by using a mutex. Since this variable is mostly read, optimize it with a sync.RWMutex.
1 parent 265b64f commit 9948f69

File tree

1 file changed

+35
-21
lines changed

1 file changed

+35
-21
lines changed

config/config.go

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"errors"
2323
"fmt"
2424
"strings"
25+
"sync"
2526

2627
"github.com/elastic/elastic-agent-libs/str"
2728
ucfg "github.com/elastic/go-ucfg"
@@ -41,7 +42,8 @@ type Namespace struct {
4142
const mask = "xxxxx"
4243

4344
var (
44-
configOpts = []ucfg.Option{
45+
configOptsMu sync.RWMutex
46+
configOpts = []ucfg.Option{
4547
ucfg.PathSep("."),
4648
ucfg.ResolveEnv,
4749
ucfg.VarExp,
@@ -62,6 +64,18 @@ var (
6264
)
6365
)
6466

67+
func getGlobalConfigOpts() []ucfg.Option {
68+
configOptsMu.RLock()
69+
defer configOptsMu.RUnlock()
70+
return configOpts
71+
}
72+
73+
func setGlobalConfigOpts(opts []ucfg.Option) {
74+
configOptsMu.Lock()
75+
defer configOptsMu.Unlock()
76+
configOpts = opts
77+
}
78+
6579
func NewConfig() *C {
6680
return fromConfig(ucfg.New())
6781
}
@@ -74,11 +88,11 @@ func NewConfig() *C {
7488
// result.
7589
func NewConfigFrom(from interface{}) (*C, error) {
7690
if str, ok := from.(string); ok {
77-
c, err := yaml.NewConfig([]byte(str), configOpts...)
91+
c, err := yaml.NewConfig([]byte(str), getGlobalConfigOpts()...)
7892
return fromConfig(c), err
7993
}
8094

81-
c, err := ucfg.NewFrom(from, configOpts...)
95+
c, err := ucfg.NewFrom(from, getGlobalConfigOpts()...)
8296
return fromConfig(c), err
8397
}
8498

@@ -130,33 +144,33 @@ func NewConfigWithYAML(in []byte, source string) (*C, error) {
130144
[]ucfg.Option{
131145
ucfg.MetaData(ucfg.Meta{Source: source}),
132146
},
133-
configOpts...,
147+
getGlobalConfigOpts()...,
134148
)
135149
c, err := yaml.NewConfig(in, opts...)
136150
return fromConfig(c), err
137151
}
138152

139153
// OverwriteConfigOpts allow to change the globally set config option
140154
func OverwriteConfigOpts(options []ucfg.Option) {
141-
configOpts = options
155+
setGlobalConfigOpts(options)
142156
}
143157

144158
// Merge merges the parameter into the C object.
145159
func (c *C) Merge(from interface{}) error {
146-
return c.access().Merge(from, configOpts...)
160+
return c.access().Merge(from, getGlobalConfigOpts()...)
147161
}
148162

149163
// Merge merges the parameter into the C object based on the provided options.
150164
func (c *C) MergeWithOpts(from interface{}, opts ...ucfg.Option) error {
151-
o := configOpts
165+
o := getGlobalConfigOpts()
152166
if opts != nil {
153167
o = append(o, opts...)
154168
}
155169
return c.access().Merge(from, o...)
156170
}
157171

158172
func (c *C) Unpack(to interface{}) error {
159-
return c.access().Unpack(to, configOpts...)
173+
return c.access().Unpack(to, getGlobalConfigOpts()...)
160174
}
161175

162176
func (c *C) Path() string {
@@ -168,11 +182,11 @@ func (c *C) PathOf(field string) string {
168182
}
169183

170184
func (c *C) Remove(name string, idx int) (bool, error) {
171-
return c.access().Remove(name, idx, configOpts...)
185+
return c.access().Remove(name, idx, getGlobalConfigOpts()...)
172186
}
173187

174188
func (c *C) Has(name string, idx int) (bool, error) {
175-
return c.access().Has(name, idx, configOpts...)
189+
return c.access().Has(name, idx, getGlobalConfigOpts()...)
176190
}
177191

178192
func (c *C) HasField(name string) bool {
@@ -184,44 +198,44 @@ func (c *C) CountField(name string) (int, error) {
184198
}
185199

186200
func (c *C) Bool(name string, idx int) (bool, error) {
187-
return c.access().Bool(name, idx, configOpts...)
201+
return c.access().Bool(name, idx, getGlobalConfigOpts()...)
188202
}
189203

190204
func (c *C) String(name string, idx int) (string, error) {
191-
return c.access().String(name, idx, configOpts...)
205+
return c.access().String(name, idx, getGlobalConfigOpts()...)
192206
}
193207

194208
func (c *C) Int(name string, idx int) (int64, error) {
195-
return c.access().Int(name, idx, configOpts...)
209+
return c.access().Int(name, idx, getGlobalConfigOpts()...)
196210
}
197211

198212
func (c *C) Float(name string, idx int) (float64, error) {
199-
return c.access().Float(name, idx, configOpts...)
213+
return c.access().Float(name, idx, getGlobalConfigOpts()...)
200214
}
201215

202216
func (c *C) Child(name string, idx int) (*C, error) {
203-
sub, err := c.access().Child(name, idx, configOpts...)
217+
sub, err := c.access().Child(name, idx, getGlobalConfigOpts()...)
204218
return fromConfig(sub), err
205219
}
206220

207221
func (c *C) SetBool(name string, idx int, value bool) error {
208-
return c.access().SetBool(name, idx, value, configOpts...)
222+
return c.access().SetBool(name, idx, value, getGlobalConfigOpts()...)
209223
}
210224

211225
func (c *C) SetInt(name string, idx int, value int64) error {
212-
return c.access().SetInt(name, idx, value, configOpts...)
226+
return c.access().SetInt(name, idx, value, getGlobalConfigOpts()...)
213227
}
214228

215229
func (c *C) SetFloat(name string, idx int, value float64) error {
216-
return c.access().SetFloat(name, idx, value, configOpts...)
230+
return c.access().SetFloat(name, idx, value, getGlobalConfigOpts()...)
217231
}
218232

219233
func (c *C) SetString(name string, idx int, value string) error {
220-
return c.access().SetString(name, idx, value, configOpts...)
234+
return c.access().SetString(name, idx, value, getGlobalConfigOpts()...)
221235
}
222236

223237
func (c *C) SetChild(name string, idx int, value *C) error {
224-
return c.access().SetChild(name, idx, value.access(), configOpts...)
238+
return c.access().SetChild(name, idx, value.access(), getGlobalConfigOpts()...)
225239
}
226240

227241
func (c *C) IsDict() bool {
@@ -234,7 +248,7 @@ func (c *C) IsArray() bool {
234248

235249
// FlattenedKeys return a sorted flattened views of the set keys in the configuration.
236250
func (c *C) FlattenedKeys() []string {
237-
return c.access().FlattenedKeys(configOpts...)
251+
return c.access().FlattenedKeys(getGlobalConfigOpts()...)
238252
}
239253

240254
// Enabled return the configured enabled value or true by default.

0 commit comments

Comments
 (0)