From 46c335ddf0ef46edb22fbe2a948c2037bc8e6f09 Mon Sep 17 00:00:00 2001 From: gzb1128 <591605936@qq.com> Date: Fri, 30 Jan 2026 21:33:36 +0800 Subject: [PATCH 1/2] Fix IndexField blocking until informer is synced IndexField() was blocking until the informer cache synced, causing unnecessary delays during controller startup. This happens because GetInformer() defaults to blockUntilSync=true. However, adding an index doesn't require the informer to be synced - the underlying client-go threadSafeStore.AddIndexers doesn't depend on sync state. Only List operations with field selectors need the cache to be synced. Relates to #3422 --- pkg/cache/informer_cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cache/informer_cache.go b/pkg/cache/informer_cache.go index 50dd9a8be1..f8a1faa7b9 100644 --- a/pkg/cache/informer_cache.go +++ b/pkg/cache/informer_cache.go @@ -200,7 +200,7 @@ func (ic *informerCache) NeedLeaderElection() bool { // The values may be anything. They will automatically be prefixed with the namespace of the // given object, if present. The objects passed are guaranteed to be objects of the correct type. func (ic *informerCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error { - informer, err := ic.GetInformer(ctx, obj) + informer, err := ic.GetInformer(ctx, obj, BlockUntilSynced(false)) if err != nil { return err } From caf0ae4df019deb178c96b91db9ffad6270db6a3 Mon Sep 17 00:00:00 2001 From: gzb1128 <591605936@qq.com> Date: Sat, 31 Jan 2026 00:55:28 +0800 Subject: [PATCH 2/2] Add test to verify IndexField does not block waiting for cache sync This test verifies that IndexField returns immediately when called before the informer cache is synced, using BlockUntilSynced(false). The test uses FakeInformer with Synced=false and a 100ms timeout context. If IndexField blocks waiting for sync, it will timeout with a clear error message. Relates to #3422 --- pkg/cache/cache_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go index a5ce56de22..ac62688f46 100644 --- a/pkg/cache/cache_test.go +++ b/pkg/cache/cache_test.go @@ -2657,3 +2657,43 @@ func TestReaderWaitsForCacheSync(t *testing.T) { }) } } + +func TestIndexFieldDoesNotBlock(t *testing.T) { + t.Parallel() + synctest.Test(t, func(t *testing.T) { + g := NewWithT(t) + + fakeInformer := &controllertest.FakeInformer{Synced: false} + c, err := cache.New(&rest.Config{}, cache.Options{ + Mapper: &fakeRESTMapper{}, + NewInformer: func(kcache.ListerWatcher, runtime.Object, time.Duration, kcache.Indexers) kcache.SharedIndexInformer { + return fakeInformer + }, + }) + g.Expect(err).NotTo(HaveOccurred()) + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + cacheDone := make(chan struct{}) + go func() { + g.Expect(c.Start(ctx)).To(Succeed()) + close(cacheDone) + }() + synctest.Wait() // Let the cache finish starting + + // Call IndexField before informer is synced with a short timeout + // If IndexField blocks waiting for sync, this will timeout + indexCtx, indexCancel := context.WithTimeout(ctx, 100*time.Millisecond) + defer indexCancel() + fieldName := "testField" + indexFunc := func(obj client.Object) []string { + return []string{"test-value"} + } + pod := &corev1.Pod{} + err = c.IndexField(indexCtx, pod, fieldName, indexFunc) + g.Expect(err).NotTo(HaveOccurred()) + + cancel() + <-cacheDone + }) +}