From c466fa3ace5fe49f1666bdd52011a4e65b5a60c5 Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Tue, 19 May 2026 21:19:02 +0600 Subject: [PATCH] hub: stop sharing the embedded descriptor map across registries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NewRegistryOfKnownResources and NewKVLocal wrapped resourcedescriptors.KnownDescriptors() — the package-global map — directly into a KVMap. Different Registry instances therefore held their own RWMutex but wrote into the same underlying Go map on KVMap.Set. The pool spins up one registry per cluster UID (PoolSize=1024); when several clusters concurrently discovered CRDs that weren't in the embedded set, those Set calls raced on the shared map. In Go that is a "fatal error: concurrent map writes" — not a silent corruption but a process crash. Introduce NewKVMapFromKnown(), which copies the known-descriptors map into a fresh map before wrapping it in a KVMap, and use it from both construction paths. KnownDescriptors() itself is unchanged; out-of-tree pool factories that share the global map should migrate to this helper. Signed-off-by: Tamal Saha --- hub/kv.go | 17 ++++++++++++++--- hub/registry.go | 4 +--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/hub/kv.go b/hub/kv.go index 9fbdcaf001..32c7f9e313 100644 --- a/hub/kv.go +++ b/hub/kv.go @@ -17,6 +17,7 @@ limitations under the License. package hub import ( + "maps" "sync" "kmodules.xyz/resource-metadata/apis/meta/v1alpha1" @@ -41,6 +42,18 @@ func NewKVMap(cache map[string]*v1alpha1.ResourceDescriptor) KV { return &KVMap{cache: cache} } +// NewKVMapFromKnown returns a KVMap seeded with a shallow copy of the +// embedded known-descriptors map. Use this when constructing per-cluster +// registries so that each registry mutates its own map: KnownDescriptors() +// returns the package-global map, and sharing it across registries leads to +// concurrent-map-writes when several clusters discover CRDs simultaneously. +func NewKVMapFromKnown() KV { + known := resourcedescriptors.KnownDescriptors() + cache := make(map[string]*v1alpha1.ResourceDescriptor, len(known)) + maps.Copy(cache, known) + return &KVMap{cache: cache} +} + func (s *KVMap) Set(key string, val *v1alpha1.ResourceDescriptor) { s.m.Lock() s.cache[key] = val @@ -72,9 +85,7 @@ var _ KV = &KVLocal{} func NewKVLocal() KV { return &KVLocal{ - known: &KVMap{ - cache: resourcedescriptors.KnownDescriptors(), - }, + known: NewKVMapFromKnown(), cache: map[string]*v1alpha1.ResourceDescriptor{}, } } diff --git a/hub/registry.go b/hub/registry.go index 251a83bdf7..cd439ceae9 100644 --- a/hub/registry.go +++ b/hub/registry.go @@ -94,9 +94,7 @@ func NewRegistry(uid string, cache KV) *Registry { } func NewRegistryOfKnownResources() *Registry { - return NewRegistry(KnownUID, &KVMap{ - cache: resourcedescriptors.KnownDescriptors(), - }) + return NewRegistry(KnownUID, NewKVMapFromKnown()) } func (r *Registry) DiscoverResources(cfg *rest.Config) error {