Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pkg/kubestatemetrics/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ type Builder struct {
eventMutex sync.RWMutex
}

// ksmBuilderMu serializes access to the kube-state-metrics library's
// package-level availableStores map, which is shared across all Builder
// instances and mutated by WithCustomResourceStoreFactories / read by
// BuildStores without internal synchronization. Without this lock, two
// concurrent KSM check workers calling buildStores() race on that map.
var ksmBuilderMu sync.Mutex

// New returns new Builder instance
func New() *Builder {
return &Builder{
Expand Down Expand Up @@ -174,6 +181,8 @@ func (b *Builder) WithGenerateCustomResourceStoresFunc(f ksmtypes.BuildCustomRes

// WithCustomResourceStoreFactories configures a constom store factory
func (b *Builder) WithCustomResourceStoreFactories(fs ...customresource.RegistryFactory) {
ksmBuilderMu.Lock()
defer ksmBuilderMu.Unlock()
b.ksmBuilder.WithCustomResourceStoreFactories(fs...)
}

Expand Down Expand Up @@ -211,7 +220,9 @@ func (b *Builder) Build() metricsstore.MetricsWriterList {
// BuildStores initializes and registers all enabled stores.
// It returns metric cache stores.
func (b *Builder) BuildStores() [][]cache.Store {
ksmBuilderMu.Lock()
stores := b.ksmBuilder.BuildStores()
ksmBuilderMu.Unlock()

if b.WorkloadmetaReflector != nil {
// Starting the workloadmeta reflector here allows us to start just one for all stores.
Expand Down
57 changes: 57 additions & 0 deletions pkg/kubestatemetrics/builder/builder_race_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build kubeapiserver

package builder

import (
"sync"
"testing"

"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/kube-state-metrics/v2/pkg/customresource"
generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator"
)

// fakeFactory is a minimal customresource.RegistryFactory implementation for
// testing. It lets us call WithCustomResourceStoreFactories without pulling in
// real Kubernetes clients.
type fakeFactory struct {
name string
}

func (f *fakeFactory) Name() string { return f.name }
func (f *fakeFactory) CreateClient(cfg *rest.Config) (interface{}, error) {
return nil, nil
}
func (f *fakeFactory) MetricFamilyGenerators() []generator.FamilyGenerator { return nil }
func (f *fakeFactory) ExpectedType() interface{} { return nil }
func (f *fakeFactory) ListWatch(c interface{}, ns string, fs string) cache.ListerWatcher {
return nil
}

var _ customresource.RegistryFactory = &fakeFactory{}

// TestWithCustomResourceStoreFactoriesConcurrent verifies that concurrent
// calls to WithCustomResourceStoreFactories on different Builder instances
// don't race on the kube-state-metrics library's package-level availableStores
// map. Without the ksmBuilderMu lock, this test fails under -race.
func TestWithCustomResourceStoreFactoriesConcurrent(t *testing.T) {
Comment thread
pgimalac marked this conversation as resolved.
t.Parallel()

const goroutines = 100
var wg sync.WaitGroup

for range goroutines {
wg.Go(func() {
b := New()
b.WithCustomResourceStoreFactories(&fakeFactory{name: "fake-resource"})
})
}

wg.Wait()
}
Loading