CLI Worker Pool Management#
The WorkerPool in pkg/cli/loader/worker.go is the concurrency engine behind the kubectl-kyverno CLI's in-cluster resource loading. It dispatches Kubernetes API list calls across a configurable number of goroutines, used when Concurrency > 1 in ResourceFetcher . The pool is created and torn down within a single call to LoadResourcesConcurrent β it is not a long-lived singleton.
Architecture Overview#
ResourceFetcher.getFromCluster()
βββ loader.LoadResourcesConcurrent()
βββ ClusterLoader
βββ NewWorkerPool(Workers=Concurrency, QueueSize=Concurrency*2)
β βββ N goroutines started via go wp.worker(context.Background(), i)
βββ executeTasks() β submits tasks, collects results
βββ Close(cancel) β signals shutdown, drains pool
Key types :
WorkerPoolβ holds the task channel, result channel,sync.WaitGroup, and worker count.LoadTaskβ one Kubernetes list operation (GVK, GVR, namespace,ListOptions, dynamic client).LoadTaskResultβ the result of that list operation (resources, error, duration).
Pool Sizing and Configuration#
NewClusterLoader configures the pool :
- Workers =
resourceOptions.Concurrency(validated 1β32, ) - QueueSize =
Concurrency * 2
Both channels (taskQueue, resultChan) are buffered to QueueSize. A queue depth of only 2ΓConcurrency is small relative to the number of GVK tasks that can be submitted β this is the root cause of the silent-drop bug described below.
Task Submission (SubmitTask)#
SubmitTask uses a double non-blocking select:
- Check
ctx.Done()β if cancelled, log and return. - Try to send on
taskQueueβ if the channel is full, fall through todefaultand silently drop the task.
This is the source of a known critical bug : when executeTasks submits all tasks synchronously in a tight loop before workers have drained the queue, any tasks beyond the QueueSize are discarded with only a debug-level log message. Since executeTasks still expects exactly len(tasks) results , dropped tasks cause the result-collection loop to block until the 5-minute timeout fires for each missing result.
Worker Lifecycle#
Workers are started in NewWorkerPool β each goroutine runs wp.worker(context.Background(), i). Workers receive context.Background(), not the cancellable context created by LoadResourcesConcurrent. This means:
- Calling
cancel()during normal shutdown does not cause workers to exit viactx.Done(). - Workers only stop when
taskQueueis closed (thecase task, ok := <-wp.taskQueuebranch returns whenok == false, ). - Any in-flight
processTaskcall that blocks on a Kubernetes API will not be interrupted by context cancellation .
processTask handles server-side pagination transparently, looping on list.GetContinue() until all pages are retrieved .
Graceful Shutdown (Close)#
WorkerPool.Close follows this sequence:
cancel()β cancels the context (affectsexecuteTasks'sctx.Done()case, but not the workers themselves, per the issue above).close(wp.taskQueue)β signals workers to drain and exit.wp.wg.Wait()β blocks until all workers have returned.close(wp.resultChan)β safe to close only after all writers (workers) are done.
ClusterLoader.Close is idempotent via a closed boolean guard and mutex . The cancel function propagates from LoadResourcesConcurrent's defer resourceLoader.Close(cancel) .
Known Bugs (as of v1.18.0)#
| Bug | Location | Impact |
|---|---|---|
| Silent task dropping | SubmitTask default branch | Tasks silently discarded when len(tasks) > QueueSize; result loop hangs until timeout |
| Cascading timeout hang | executeTasks for/select | break inside select exits only the select, not the for loop; CLI blocks for N_dropped Γ 5min when ContinueOnError=true |
| Timer memory leak | executeTasks time.After in loop | New Timer allocated each loop iteration; not GC'd until it fires, accumulating across many resources |
| Non-cancellable workers | NewWorkerPool goroutine spawn | Workers use context.Background(); cannot be interrupted by parent cancellation |
Tracking issue: #17053 β WorkerPool silently drops tasks causing deadlocks and massive timeouts, #17049 β Memory leak and loop hang in executeTasks.
Entry Points for Debugging / Modification#
| File | Purpose |
|---|---|
pkg/cli/loader/worker.go | WorkerPool struct, task processing, submit/close logic |
pkg/cli/loader/resource_loader.go | ClusterLoader, executeTasks, pool configuration |
pkg/cli/loader/worker_test.go | Unit tests for pool behavior (submit, pagination, shutdown) |
cmd/cli/kubectl-kyverno/utils/common/fetch.go | CLI entry point that decides concurrent vs. sequential loading |