{"record":{"id":"82e96eedb1a16c32","repo":"derailed/k9s","slug":"refresh-in-progress-dropping","errorCode":null,"errorMessage":"refresh in progress, dropping","messagePattern":"refresh in progress, dropping","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"internal/model/rev_values.go","lineNumber":164,"sourceCode":"\t\tcase <-time.After(delay):\n\t\t\tif err := v.refresh(ctx); err != nil {\n\t\t\t\tv.fireResourceFailed(err)\n\t\t\t\tif delay = backOff.NextBackOff(); delay == backoff.Stop {\n\t\t\t\t\tslog.Error(\"Giving up retrieving chart values\", slogs.Error, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbackOff.Reset()\n\t\t\t\tdelay = defaultReaderRefreshRate\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (v *RevValues) refresh(context.Context) error {\n\tif !atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1) {\n\t\tslog.Debug(\"Dropping update...\")\n\t\treturn errors.New(\"refresh in progress, dropping\")\n\t}\n\tdefer atomic.StoreInt32(&v.inUpdate, 0)\n\n\tv.reconcile()\n\n\treturn nil\n}\n\nfunc (v *RevValues) reconcile() {\n\tv.fireResourceChanged(v.lines, v.filter(v.query, v.lines))\n}\n\n// AddListener adds a new model listener.\nfunc (v *RevValues) AddListener(l ResourceViewerListener) {\n\tv.listeners = append(v.listeners, l)\n}\n\n// RemoveListener delete a listener from the list.","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/derailed/k9s/blob/2d3ccc6ba2ce98c3781bfc441bb3e884f072774f/internal/model/rev_values.go#L146-L182","documentation":"RevValues.refresh implements a single-flight guard with atomic.CompareAndSwapInt32(&v.inUpdate, 0, 1): if a refresh/reconcile is already running, the second caller logs 'Dropping update...' and returns this error. It is a deliberate, benign drop of a redundant tick — the watcher loop that triggers refresh (rev_values.go:71/124/129/147) simply skips that cycle and the next tick re-syncs. The same pattern guards model1 Table/Yaml/Tree/Describe values.","triggerScenarios":"A watcher event or refresh timer firing while a previous refresh is still executing — large CRD/value payloads, slow terminals, or bursts of cluster events make refreshes overlap; every overlapping call gets this error.","commonSituations":"Viewing helm values / large ConfigMaps on slow connections where reconciliation outlasts the refresh interval; high churn clusters; it surfaces in logs as noise rather than as a user-visible failure.","solutions":["Treat it as a no-op signal: log at debug level and skip (the shipped callers already log 'Dropping update...').","If data latency matters, raise the reader refresh rate so refreshes rarely overlap, or coalesce events before calling refresh.","Do not retry immediately in the same goroutine — the in-flight refresh will publish fresher data anyway."],"exampleFix":"// before\nif err := v.refresh(ctx); err != nil { slog.Error(\"refresh failed\", \"err\", err) }\n// after\nif err := v.refresh(ctx); err != nil {\n    if !errors.Is(err, errRefreshInFlight) { slog.Error(\"refresh failed\", \"err\", err) }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := v.refresh(ctx); err != nil {\n    if strings.Contains(err.Error(), \"refresh in progress, dropping\") {\n        return // benign single-flight drop; next tick reconciles\n    }\n    return err\n}","preventionTips":["Treat this error as a skip signal, never as a failure to report.","Tune refresh rate if drops are frequent enough to cause stale views.","Reuse the same CompareAndSwap guard pattern in custom value models."],"tags":["go","concurrency","single-flight","model","benign"],"backgroundTag":null,"analyzedSha":"2d3ccc6ba2ce98c3781bfc441bb3e884f072774f","analyzedAt":"2026-08-15T16:09:14.432Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}