Skip to content

Commit a1adc6b

Browse files
committed
cache: clone maps to prevent data race when concurrently creating caches using the same options
1 parent 2aa9459 commit a1adc6b

File tree

2 files changed

+33
-1
lines changed

2 files changed

+33
-1
lines changed

pkg/cache/cache.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,8 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
469469
}
470470
}
471471

472+
opts.ByObject = maps.Clone(opts.ByObject)
473+
opts.DefaultNamespaces = maps.Clone(opts.DefaultNamespaces)
472474
for obj, byObject := range opts.ByObject {
473475
isNamespaced, err := apiutil.IsObjectNamespaced(obj, opts.Scheme, opts.Mapper)
474476
if err != nil {
@@ -480,14 +482,15 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
480482

481483
if isNamespaced && byObject.Namespaces == nil {
482484
byObject.Namespaces = maps.Clone(opts.DefaultNamespaces)
485+
} else {
486+
byObject.Namespaces = maps.Clone(byObject.Namespaces)
483487
}
484488

485489
// Default the namespace-level configs first, because they need to use the undefaulted type-level config
486490
// to be able to potentially fall through to settings from DefaultNamespaces.
487491
for namespace, config := range byObject.Namespaces {
488492
// 1. Default from the undefaulted type-level config
489493
config = defaultConfig(config, byObjectToConfig(byObject))
490-
491494
// 2. Default from the namespace-level config. This was defaulted from the global default config earlier, but
492495
// might not have an entry for the current namespace.
493496
if defaultNamespaceSettings, hasDefaultNamespace := opts.DefaultNamespaces[namespace]; hasDefaultNamespace {

pkg/cache/defaulting_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package cache
1818

1919
import (
2020
"reflect"
21+
"sync"
2122
"testing"
2223
"time"
2324

@@ -432,6 +433,34 @@ func TestDefaultOpts(t *testing.T) {
432433
}
433434
}
434435

436+
func TestDefaultOptsRace(t *testing.T) {
437+
opts := Options{
438+
Mapper: &fakeRESTMapper{},
439+
ByObject: map[client.Object]ByObject{
440+
&corev1.Pod{}: {
441+
Label: labels.SelectorFromSet(map[string]string{"from": "pod"}),
442+
Namespaces: map[string]Config{"default": {
443+
LabelSelector: labels.SelectorFromSet(map[string]string{"from": "pod"}),
444+
}},
445+
},
446+
},
447+
DefaultNamespaces: map[string]Config{"default": {}},
448+
}
449+
450+
// Start go routines which re-use the above options struct.
451+
wg := sync.WaitGroup{}
452+
for range 2 {
453+
wg.Add(1)
454+
go func() {
455+
_, _ = defaultOpts(&rest.Config{}, opts)
456+
wg.Done()
457+
}()
458+
}
459+
460+
// Wait for the go routines to finish.
461+
wg.Wait()
462+
}
463+
435464
type fakeRESTMapper struct {
436465
meta.RESTMapper
437466
}

0 commit comments

Comments
 (0)