Settings: Fix data race when dynamically overriding settings with environment variables (#81667)
Chore: Fix data race when dynamically overriding settings with environment variables
This commit is contained in:
+12
-7
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/util/osutil"
|
||||
)
|
||||
|
||||
type Scheme string
|
||||
@@ -822,6 +823,9 @@ func (cfg *Cfg) loadSpecifiedConfigFile(configFile string, masterFile *ini.File)
|
||||
return fmt.Errorf("failed to parse %q: %w", configFile, err)
|
||||
}
|
||||
|
||||
// micro-optimization since we don't need to share this ini file. In
|
||||
// general, prefer to leave this flag as true as it is by default to prevent
|
||||
// data races
|
||||
userConfig.BlockMode = false
|
||||
|
||||
for _, section := range userConfig.Sections() {
|
||||
@@ -865,8 +869,6 @@ func (cfg *Cfg) loadConfiguration(args CommandLineArgs) (*ini.File, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedFile.BlockMode = false
|
||||
|
||||
// command line props
|
||||
commandLineProps := cfg.getCommandLineProperties(args.Args)
|
||||
// load default overrides
|
||||
@@ -987,8 +989,6 @@ func NewCfgFromBytes(bytes []byte) (*Cfg, error) {
|
||||
return nil, fmt.Errorf("failed to parse bytes as INI file: %w", err)
|
||||
}
|
||||
|
||||
parsedFile.BlockMode = false
|
||||
|
||||
return NewCfgFromINIFile(parsedFile)
|
||||
}
|
||||
|
||||
@@ -1398,13 +1398,14 @@ func (cfg *Cfg) LogConfigSources() {
|
||||
type DynamicSection struct {
|
||||
section *ini.Section
|
||||
Logger log.Logger
|
||||
env osutil.Env
|
||||
}
|
||||
|
||||
// Key dynamically overrides keys with environment variables.
|
||||
// As a side effect, the value of the setting key will be updated if an environment variable is present.
|
||||
func (s *DynamicSection) Key(k string) *ini.Key {
|
||||
envKey := EnvKey(s.section.Name(), k)
|
||||
envValue := os.Getenv(envKey)
|
||||
envValue := s.env.Getenv(envKey)
|
||||
key := s.section.Key(k)
|
||||
|
||||
if len(envValue) == 0 {
|
||||
@@ -1421,7 +1422,7 @@ func (s *DynamicSection) KeysHash() map[string]string {
|
||||
hash := s.section.KeysHash()
|
||||
for k := range hash {
|
||||
envKey := EnvKey(s.section.Name(), k)
|
||||
envValue := os.Getenv(envKey)
|
||||
envValue := s.env.Getenv(envKey)
|
||||
if len(envValue) > 0 {
|
||||
hash[k] = envValue
|
||||
}
|
||||
@@ -1432,7 +1433,11 @@ func (s *DynamicSection) KeysHash() map[string]string {
|
||||
// SectionWithEnvOverrides dynamically overrides keys with environment variables.
|
||||
// As a side effect, the value of the setting key will be updated if an environment variable is present.
|
||||
func (cfg *Cfg) SectionWithEnvOverrides(s string) *DynamicSection {
|
||||
return &DynamicSection{cfg.Raw.Section(s), cfg.Logger}
|
||||
return &DynamicSection{
|
||||
section: cfg.Raw.Section(s),
|
||||
Logger: cfg.Logger,
|
||||
env: osutil.RealEnv{},
|
||||
}
|
||||
}
|
||||
|
||||
func readSecuritySettings(iniFile *ini.File, cfg *Cfg) error {
|
||||
|
||||
@@ -9,9 +9,12 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/util/osutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -952,3 +955,46 @@ func TestNewCfgFromINIFile(t *testing.T) {
|
||||
require.Equal(t, Prod, cfg.Env)
|
||||
require.Equal(t, "test.com", cfg.Domain)
|
||||
}
|
||||
|
||||
func TestDynamicSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("repro #44509 - panic on concurrent map write", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
goroutines = 10
|
||||
attempts = 1000
|
||||
section = "DEFAULT"
|
||||
key = "TestDynamicSection_repro_44509"
|
||||
value = "theval"
|
||||
)
|
||||
|
||||
cfg, err := NewCfgFromBytes([]byte(``))
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := &DynamicSection{
|
||||
section: cfg.Raw.Section(section),
|
||||
Logger: log.NewNopLogger(),
|
||||
env: osutil.MapEnv{},
|
||||
}
|
||||
osVar := EnvKey(section, key)
|
||||
err = ds.env.Setenv(osVar, value)
|
||||
require.NoError(t, err)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go require.NotPanics(t, func() {
|
||||
for i := 0; i < attempts; i++ {
|
||||
ds.section.Key(key).SetValue("")
|
||||
ds.Key(key)
|
||||
}
|
||||
wg.Done()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(t, value, ds.section.Key(key).String())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user