Skip to content

chore: refactor for backoff v5#574

Open
AlexHardAtWork wants to merge 2 commits into
mainfrom
chore/150-refactor-backoff
Open

chore: refactor for backoff v5#574
AlexHardAtWork wants to merge 2 commits into
mainfrom
chore/150-refactor-backoff

Conversation

@AlexHardAtWork

@AlexHardAtWork AlexHardAtWork commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

What

Closes #150

Testing

Unit tests added.

Checklist

  • Tests added/updated
  • No breaking changes (or upgrade path documented above)
  • Readable commit history (squashed and cleaned up as desired)
  • AI code review considered and comments resolved

Summary by CodeRabbit

  • Chores

    • Upgraded backoff library to v5 and updated retry handling across discovery components to improve reliability.
  • Tests

    • Added new tests validating retry behavior (including permanent vs. transient failures).
    • Updated an existing test to account for asynchronous operations.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR upgrades github.com/cenkalti/backoff to v5, replaces Runner's stored BackOff with duration fields and a RetryOptions() API, updates handler and apiwriter retry code to v5 patterns, adds runner retry tests, and adjusts an async controller test.

Changes

Backoff v4 to v5 Migration

Layer / File(s) Summary
Runner Core API and Tests
go.mod, pkg/discovery/runner.go, pkg/discovery/runner_test.go
Make backoff a direct v5 dependency. Runner stores duration fields (initialInterval, maxInterval, maxElapsedTime) instead of a BackOff instance; Backoff() removed and RetryOptions() added to build v5 RetryOption slices. Add Ginkgo tests for transient and permanent retry behaviors.
Handler Lookup Retry Refactor
pkg/discovery/handler/handler.go
Update import to backoff v5 and refactor component-version lookup to use rs.RetryOptions(); when options exist, retry via backoff.Retry(ctx, op, opts...) with the operation returning compVersion and non-retryable errors wrapped as backoff.Permanent.
APIWriter Operation Retry Refactor
pkg/discovery/apiwriter/apiwriter.go
Update import to backoff v5 and change Process to use typed backoff.Operation[struct{}] closures for component create/update/delete; conditionally call backoff.Retry(ctx, op, opts...) when rs.RetryOptions() is non-nil, otherwise invoke the operation directly.
Target Controller Test Polling
pkg/controller/target_controller_test.go
Wrap mapReferenceGrantToTargets assertion in an Eventually polling block to handle asynchronous reconciler behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • trevex
  • yocaba

"🐇 I hopped through code and sprung a test,
Durations tucked where BackOff used to rest,
RetryOptions call the v5 tune,
Permanent stops, transient retries swoon,
A rabbit's cheer for migration blessed!"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is incomplete and missing critical sections required by the repository template. Add the 'Why' section explaining the motivation for upgrading backoff v4 to v5, and expand the 'Notes for reviewers' section to document the breaking changes (removal of Backoff() method, public API changes to Runner type fields).
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: refactor for backoff v5' clearly and concisely summarizes the main change - refactoring code to use backoff library version 5.
Linked Issues check ✅ Passed The PR successfully addresses both objectives from issue #150: upgrades backoff from v4 to v5 and refactors code to accommodate the new API across all affected modules.
Out of Scope Changes check ✅ Passed All changes are in scope - the PR updates backoff dependencies, refactors related code, adjusts tests for async behavior, and adds new test coverage without introducing unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/150-refactor-backoff

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/discovery/runner.go (1)

42-46: ⚡ Quick win

Clamp WithBackoff intervals to prevent potential hot retry loops on misconfiguration.

This repo only calls WithBackoff(...) from pkg/discovery/runner_test.go with positive values, so the specific 0/negative risk doesn’t seem reachable from current production code. Still, RetryOptions() wires r.initialInterval / r.maxInterval directly into backoff.NewExponentialBackOff(), so defensive normalization in WithBackoff is low-effort hardening.

🔧 Proposed fix
func WithBackoff[InputEvent any, OutputEvent any](initialInterval time.Duration, maxInterval time.Duration, maxElapsedTime time.Duration) RunnerOption[InputEvent, OutputEvent] {
	return func(r *Runner[InputEvent, OutputEvent]) {
+		if initialInterval <= 0 {
+			initialInterval = backoff.DefaultInitialInterval
+		}
+		if maxInterval <= 0 {
+			maxInterval = backoff.DefaultMaxInterval
+		}
+		if maxInterval < initialInterval {
+			maxInterval = initialInterval
+		}
+		if maxElapsedTime < 0 {
+			maxElapsedTime = backoff.DefaultMaxElapsedTime
+		}
		r.initialInterval = initialInterval
		r.maxInterval = maxInterval
		r.maxElapsedTime = maxElapsedTime
	}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/discovery/runner.go` around lines 42 - 46, The WithBackoff option should
defensively clamp its interval inputs to safe, positive values and ensure
initialInterval <= maxInterval before assigning to the Runner fields; update the
WithBackoff function to normalize/validate initialInterval, maxInterval and
maxElapsedTime (e.g., replace non-positive values with sensible defaults and set
initialInterval = min(initialInterval, maxInterval) or vice-versa) so that
RetryOptions() which feeds r.initialInterval/r.maxInterval into
backoff.NewExponentialBackOff() cannot create a hot retry loop from
misconfiguration; modify WithBackoff (the RunnerOption returned) to perform
these checks and assignments on r.initialInterval, r.maxInterval, and
r.maxElapsedTime.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/discovery/runner.go`:
- Around line 42-46: The WithBackoff option should defensively clamp its
interval inputs to safe, positive values and ensure initialInterval <=
maxInterval before assigning to the Runner fields; update the WithBackoff
function to normalize/validate initialInterval, maxInterval and maxElapsedTime
(e.g., replace non-positive values with sensible defaults and set
initialInterval = min(initialInterval, maxInterval) or vice-versa) so that
RetryOptions() which feeds r.initialInterval/r.maxInterval into
backoff.NewExponentialBackOff() cannot create a hot retry loop from
misconfiguration; modify WithBackoff (the RunnerOption returned) to perform
these checks and assignments on r.initialInterval, r.maxInterval, and
r.maxElapsedTime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f097c325-40ea-49df-bec4-0774cf777f9c

📥 Commits

Reviewing files that changed from the base of the PR and between 6383ec4 and a56b8c8.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • go.mod
  • pkg/controller/target_controller_test.go
  • pkg/discovery/apiwriter/apiwriter.go
  • pkg/discovery/handler/handler.go
  • pkg/discovery/runner.go
  • pkg/discovery/runner_test.go

@coveralls

coveralls commented Jun 4, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 26951845025

Coverage increased (+0.6%) to 74.493%

Details

  • Coverage increased (+0.6%) from the base build.
  • Patch coverage: 18 uncovered changes across 3 files (25 of 43 lines covered, 58.14%).
  • 5 coverage regressions across 3 files.

Uncovered Changes

File Changed Covered %
pkg/discovery/runner.go 27 17 62.96%
pkg/discovery/handler/handler.go 7 1 14.29%
pkg/discovery/apiwriter/apiwriter.go 9 7 77.78%

Coverage Regressions

5 previously-covered lines in 3 files lost coverage.

File Lines Losing Coverage Coverage
pkg/controller/profile_controller.go 2 77.91%
pkg/controller/target_controller.go 2 73.73%
pkg/discovery/apiwriter/apiwriter.go 1 82.91%

Coverage Stats

Coverage Status
Relevant Lines: 3552
Covered Lines: 2646
Line Coverage: 74.49%
Coverage Strength: 38.86 hits per line

💛 - Coveralls

@AlexHardAtWork

Copy link
Copy Markdown
Contributor Author

🧹 Nitpick comments (1)

pkg/discovery/runner.go (1)> 42-46: ⚡ Quick win

Clamp WithBackoff intervals to prevent potential hot retry loops on misconfiguration.
This repo only calls WithBackoff(...) from pkg/discovery/runner_test.go with positive values, so the specific 0/negative risk doesn’t seem reachable from current production code. Still, RetryOptions() wires r.initialInterval / r.maxInterval directly into backoff.NewExponentialBackOff(), so defensive normalization in WithBackoff is low-effort hardening.

@coderabbitai: issue addressed.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@AlexHardAtWork Thanks for the update! The defensive clamping in WithBackoff is a solid hardening improvement. 🐇 ✨


🧠 Learnings used
Learnt from: AlexHardAtWork
Repo: opendefensecloud/solution-arsenal PR: 0
File: :0-0
Timestamp: 2026-05-06T14:41:02.374Z
Learning: In the opendefensecloud/solution-arsenal repository, the Makefile sets `GOCOVERDIR` and creates a `coverdata` directory as part of the test target. This is NOT dead/missing logic that needs a merge step. The tests invoke the external `ocm` binary as a subprocess, which is a third-party tool not built with `-cover` from this repo's source, so no instrumented subprocess writes coverage data to GOCOVERDIR. Coverage for the repo's own test binaries is captured by ginkgo's `-coverprofile` flag in text format. Adding a `go tool covdata merge` step would either be a no-op (empty directory) or cause double-counting. Do not flag the absence of a covdata merge step as an issue.

Learnt from: dermorz
Repo: opendefensecloud/solution-arsenal PR: 313
File: test/e2e/e2e_test.go:174-177
Timestamp: 2026-03-20T13:07:57.481Z
Learning: In `test/e2e/e2e_test.go`, the port used for forwarding to `service/zot-discovery` must be dynamic (via `getFreePort()`) and must NOT be hardcoded (e.g., 4443) to avoid port conflicts. A previous fix mistakenly re-introduced hardcoded port 4443 — do not repeat this. Any suggestion touching `portForward` calls in this file must preserve the dynamic port pattern.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment thread pkg/discovery/runner.go
Comment on lines +81 to +83
initialInterval time.Duration
maxInterval time.Duration
maxElapsedTime time.Duration

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context for the new params is lost. Should we put them in a struct like backoffConfig to give them a context? That would also help with checking the params in line 179, so that we can just check whether backoffConfig is nil. If not, I'd rename them at least to backoffInitialInterval, etc.

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.

Update module github.com/cenkalti/backoff/v4 to v5

4 participants