Skip to content

refactor: switch InformerManager to context and cache sync lookups#674

Open
Yetkin Timocin (ytimocin) wants to merge 1 commit into
kubefleet-dev:mainfrom
ytimocin:refactor/informermanager-context-and-sync-cache
Open

refactor: switch InformerManager to context and cache sync lookups#674
Yetkin Timocin (ytimocin) wants to merge 1 commit into
kubefleet-dev:mainfrom
ytimocin:refactor/informermanager-context-and-sync-cache

Conversation

@ytimocin

@ytimocin Yetkin Timocin (ytimocin) commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Description of your changes

Three small changes in pkg/utils/informer/informermanager.go:

  1. Channel → context. Replace parentCh <-chan struct{} on NewInformerManager with a plain context.Context. The manager derives a child context via context.WithCancel, so Stop() still works while parent-context cancellation also drives shutdown. The ContextForChannel helper (which spawned a goroutine to bridge channel-close into context-cancel) had no remaining callers and is removed.

  2. Lazy sync cache. Add a sync.Map (syncedInformers) that records GVRs whose informers have already reported HasSynced() == true. HasSynced is monotonic for the life of an informer instance, so a presence-only set with no invalidation is sufficient. The cache lets hot reconcile-path callers — pkg/utils/controller/resource_selector_resolver.go (called per CRP reconcile) and pkg/controllers/resourcechange/resourcechange_controller.go (called per resource-change event) — skip the factory mutex on subsequent lookups. The readiness probe (pkg/utils/informer/readiness) also benefits.

  3. Phantom-informer guard. Add a registeredInformers sync.Map populated by AddStaticResource, CreateInformerForResource, and AddEventHandlerToInformer. IsInformerSynced consults it before calling informerFactory.ForResource(), which otherwise silently registers an unstarted, handler-less informer for any unknown GVR. Observable behaviour is unchanged (callers already treat false as "not ready, requeue"); the factory just no longer accumulates phantom entries.

Five call sites updated: cmd/hubagent/workload/setup.go, pkg/controllers/{rollout,updaterun,placement}/suite_test.go, plus the package's own test file (which now adopts t.Context() since the module is on Go 1.25).

Boy-Scout: type-name-prefix the Manager interface doc, fix includeincluding, fix Stop doc grammar, drop a redundant outer RLock in TestAddEventHandlerToInformer, and modernize one for i := 0; i < n; i++ loop.

Fixes #649

I have:

  • Associated this change with a known KubeFleet Issue (Bug, Feature, etc).
  • Run make reviewable to ensure this PR is ready for review.

How has this code been tested

  • make reviewable clean (fmt, vet, golangci-lint, staticcheck, go mod tidy).
  • go test -race ./pkg/utils/informer/... — all tests pass under the new signature.
  • New tests:
    • TestIsInformerSynced_CachesSyncedResult — first call populates the cache after WaitForCacheSync; subsequent calls hit the cache and behaviour is unchanged.
    • TestIsInformerSynced_DoesNotCacheUnsyncedResult — a registered-but-unstarted informer reports false and is not cached, so a later sync still flips the result.
    • TestIsInformerSynced_UnregisteredGVR_ReturnsFalseWithoutCreatingInformer — unknown GVR short-circuits before factory.ForResource() and never enters either map.
  • Integration test suites that consume NewInformerManager (rollout, updaterun, placement) compile and run.

Special notes for your reviewer

  • Stop() semantics are preserved: it still cancels the manager's context exactly once, and cancel() of an already-cancelled context is a documented no-op.
  • Both caches are per-informerManagerImpl (not global), matching the per-factory scope of the underlying informer instances.
  • LoadOrStore was deliberately not used for syncedInformers — it would force-store even on HasSynced() == false, poisoning the cache. The Load then conditional Store pattern is the correct write-once-on-true idiom.

@codecov

codecov Bot commented Apr 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ytimocin Yetkin Timocin (ytimocin) changed the title refactor(informer): switch InformerManager to context and cache sync lookups refactor: switch InformerManager to context and cache sync lookups Apr 28, 2026
@ytimocin
Yetkin Timocin (ytimocin) force-pushed the refactor/informermanager-context-and-sync-cache branch from 6484d7c to 883a270 Compare April 29, 2026 17:22
@ytimocin
Yetkin Timocin (ytimocin) force-pushed the refactor/informermanager-context-and-sync-cache branch from 883a270 to 14f59bf Compare May 1, 2026 17:43
@ytimocin
Yetkin Timocin (ytimocin) force-pushed the refactor/informermanager-context-and-sync-cache branch 3 times, most recently from e74da99 to 6c38148 Compare May 13, 2026 03:04
…lookups

Replace the custom stop channel parameter on `NewInformerManager` with
a plain `context.Context`. The manager derives a child context via
`context.WithCancel`, so `Stop()` keeps working while parent-context
cancellation also drives shutdown. The `ContextForChannel` helper had
no remaining callers and is removed.

Add a `sync.Map` cache (`syncedInformers`) that records GVRs whose
informers have already reported `HasSynced() == true`. `HasSynced` is
monotonic for the life of an informer instance, so a presence-only
set with no invalidation is sufficient. Hot callers (for example the
webhook scope check on every readiness probe) skip the factory mutex
on subsequent lookups.

Update the five call sites in `cmd/hubagent`, the rollout / updaterun
/ placement integration suites, and the unit tests; tests adopt
`t.Context()` since the module is on Go 1.25.

Boy Scout fixes on touched files: type-name-prefix the `Manager`
interface doc, fix `include` -> `including` and `Stop` doc grammar,
drop a redundant outer `RLock` in `TestAddEventHandlerToInformer`,
and modernize one `for i := 0; i < n; i++` loop. Add new tests
`TestIsInformerSynced_CachesSyncedResult` and
`TestIsInformerSynced_DoesNotCacheUnsyncedResult` covering the cache
write-on-true and never-cache-false paths.

Fixes kubefleet-dev#649

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Yetkin Timocin <ytimocin@microsoft.com>
@ytimocin
Yetkin Timocin (ytimocin) force-pushed the refactor/informermanager-context-and-sync-cache branch from 6c38148 to 0a5b658 Compare May 18, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InformerManager: small refactors

1 participant