Skip to content

Commit d5422a3

Browse files
authored
Merge pull request #15706 from prezha/fix-addons-map-race-condition
fix addons map race condition
2 parents 6fdb2e1 + 9edc995 commit d5422a3

File tree

3 files changed

+70
-37
lines changed

3 files changed

+70
-37
lines changed

pkg/addons/addons.go

+52-35
Original file line numberDiff line numberDiff line change
@@ -478,43 +478,20 @@ func verifyAddonStatusInternal(cc *config.ClusterConfig, name string, val string
478478
return nil
479479
}
480480

481-
// Start enables the default addons for a profile, plus any additional
482-
func Start(wg *sync.WaitGroup, cc *config.ClusterConfig, toEnable map[string]bool, additional []string) {
481+
// Enable tries to enable the default addons for a profile plus any additional, and returns a single slice of all successfully enabled addons via channel (thread-safe).
482+
// Since Enable is called asynchronously (so is not thread-safe for concurrent addons map updating/reading), to avoid race conditions,
483+
// ToEnable should be called synchronously before Enable to get complete list of addons to enable, and
484+
// UpdateConfig should be called synchronously after Enable to update the config with successfully enabled addons.
485+
func Enable(wg *sync.WaitGroup, cc *config.ClusterConfig, toEnable map[string]bool, enabled chan<- []string) {
483486
defer wg.Done()
484487

485488
start := time.Now()
486-
klog.Infof("enableAddons start: toEnable=%v, additional=%s", toEnable, additional)
489+
klog.Infof("enable addons start: toEnable=%v", toEnable)
490+
var enabledAddons []string
487491
defer func() {
488-
klog.Infof("enableAddons completed in %s", time.Since(start))
492+
klog.Infof("enable addons completed in %s: enabled=%v", time.Since(start), enabledAddons)
489493
}()
490494

491-
// Get the default values of any addons not saved to our config
492-
for name, a := range assets.Addons {
493-
defaultVal := a.IsEnabled(cc)
494-
495-
_, exists := toEnable[name]
496-
if !exists {
497-
toEnable[name] = defaultVal
498-
}
499-
}
500-
501-
// Apply new addons
502-
for _, name := range additional {
503-
isDeprecated, replacement, msg := Deprecations(name)
504-
if isDeprecated && replacement == "" {
505-
out.FailureT(msg)
506-
continue
507-
} else if isDeprecated {
508-
out.Styled(style.Waiting, msg)
509-
name = replacement
510-
}
511-
// if the specified addon doesn't exist, skip enabling
512-
_, e := isAddonValid(name)
513-
if e {
514-
toEnable[name] = true
515-
}
516-
}
517-
518495
toEnableList := []string{}
519496
for k, v := range toEnable {
520497
if v {
@@ -525,8 +502,6 @@ func Start(wg *sync.WaitGroup, cc *config.ClusterConfig, toEnable map[string]boo
525502

526503
var awg sync.WaitGroup
527504

528-
var enabledAddons []string
529-
530505
defer func() { // making it show after verifications (see #7613)
531506
register.Reg.SetStep(register.EnablingAddons)
532507
out.Step(style.AddonEnable, "Enabled addons: {{.addons}}", out.V{"addons": strings.Join(enabledAddons, ", ")})
@@ -544,10 +519,52 @@ func Start(wg *sync.WaitGroup, cc *config.ClusterConfig, toEnable map[string]boo
544519
}(a)
545520
}
546521

547-
// Wait until all of the addons are enabled before updating the config (not thread safe)
522+
// Wait until all of the addons are enabled
548523
awg.Wait()
549524

550-
for _, a := range enabledAddons {
525+
// send the slice of all successfully enabled addons to channel and close
526+
enabled <- enabledAddons
527+
close(enabled)
528+
}
529+
530+
// ToEnable returns the final list of addons to enable (not thread-safe).
531+
func ToEnable(cc *config.ClusterConfig, existing map[string]bool, additional []string) map[string]bool {
532+
// start from existing
533+
enable := map[string]bool{}
534+
for k, v := range existing {
535+
enable[k] = v
536+
}
537+
538+
// Get the default values of any addons not saved to our config
539+
for name, a := range assets.Addons {
540+
if _, exists := existing[name]; !exists {
541+
enable[name] = a.IsEnabled(cc)
542+
}
543+
}
544+
545+
// Apply new addons
546+
for _, name := range additional {
547+
isDeprecated, replacement, msg := Deprecations(name)
548+
if isDeprecated && replacement == "" {
549+
out.FailureT(msg)
550+
continue
551+
} else if isDeprecated {
552+
out.Styled(style.Waiting, msg)
553+
name = replacement
554+
}
555+
// if the specified addon doesn't exist, skip enabling
556+
if _, e := isAddonValid(name); e {
557+
enable[name] = true
558+
}
559+
}
560+
561+
return enable
562+
}
563+
564+
// UpdateConfig tries to update config with all enabled addons (not thread-safe).
565+
// Any error will be logged and it will continue.
566+
func UpdateConfig(cc *config.ClusterConfig, enabled []string) {
567+
for _, a := range enabled {
551568
if err := Set(cc, a, "true"); err != nil {
552569
klog.Errorf("store failed: %v", err)
553570
}

pkg/addons/addons_test.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,15 @@ func TestStart(t *testing.T) {
126126
KubernetesConfig: config.KubernetesConfig{},
127127
}
128128

129+
toEnable := ToEnable(cc, map[string]bool{}, []string{"dashboard"})
130+
enabled := make(chan []string, 1)
129131
var wg sync.WaitGroup
130132
wg.Add(1)
131-
go Start(&wg, cc, map[string]bool{}, []string{"dashboard"})
133+
go Enable(&wg, cc, toEnable, enabled)
132134
wg.Wait()
135+
if ea, ok := <-enabled; ok {
136+
UpdateConfig(cc, ea)
137+
}
133138

134139
if !assets.Addons["dashboard"].IsEnabled(cc) {
135140
t.Errorf("expected dashboard to be enabled")

pkg/minikube/node/start.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,14 @@ func Start(starter Starter, apiServer bool) (*kubeconfig.Settings, error) {
171171

172172
// enable addons, both old and new!
173173
addonList := viper.GetStringSlice(config.AddonListFlag)
174+
enabledAddons := make(chan []string, 1)
174175
if starter.ExistingAddons != nil {
175176
if viper.GetBool("force") {
176177
addons.Force = true
177178
}
179+
list := addons.ToEnable(starter.Cfg, starter.ExistingAddons, addonList)
178180
wg.Add(1)
179-
go addons.Start(&wg, starter.Cfg, starter.ExistingAddons, addonList)
181+
go addons.Enable(&wg, starter.Cfg, list, enabledAddons)
180182
}
181183

182184
// discourage use of the virtualbox driver
@@ -226,7 +228,16 @@ func Start(starter Starter, apiServer bool) (*kubeconfig.Settings, error) {
226228
klog.Infof("waiting for startup goroutines ...")
227229
wg.Wait()
228230

231+
// update config with enabled addons
232+
if starter.ExistingAddons != nil {
233+
klog.Infof("waiting for cluster config update ...")
234+
if ea, ok := <-enabledAddons; ok {
235+
addons.UpdateConfig(starter.Cfg, ea)
236+
}
237+
}
238+
229239
// Write enabled addons to the config before completion
240+
klog.Infof("writing updated cluster config ...")
230241
return kcs, config.Write(viper.GetString(config.ProfileName), starter.Cfg)
231242
}
232243

0 commit comments

Comments
 (0)