Skip to content

Use lazy copy to patch resources to ensure multiple modifications are applied to the base resource - #4580

Open
nikola-jokic wants to merge 2 commits into
nikola-jokic-revision-aware-outdatedfrom
nikola-jokic/lazy-copy
Open

Use lazy copy to patch resources to ensure multiple modifications are applied to the base resource#4580
nikola-jokic wants to merge 2 commits into
nikola-jokic-revision-aware-outdatedfrom
nikola-jokic/lazy-copy

Conversation

@nikola-jokic

@nikola-jokic nikola-jokic commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Based on #4575

@github-actions

Copy link
Copy Markdown
Contributor

Hello! Thank you for your contribution.

Please review our contribution guidelines to understand the project's testing and code conventions.

@nikola-jokic
nikola-jokic force-pushed the nikola-jokic/lazy-copy branch from f43f84b to ab1c70d Compare July 23, 2026 15:30
@nikola-jokic
nikola-jokic marked this pull request as ready for review July 23, 2026 19:44
Copilot AI review requested due to automatic review settings July 23, 2026 19:44

Copilot AI 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.

Pull request overview

This PR modernizes how the controllers detect/propagate desired-state changes and how they compute patches, replacing integrity-hash annotations with explicit revision/generation tracking and adding a shared in-memory resource cache to avoid rebuilding identical desired objects.

Changes:

  • Introduces a ResourceCache used by ResourceBuilder to reuse desired objects based on a main-object key plus dependency refs.
  • Replaces integrity-hash–based update detection with ActionableRevision/AppliedActionableRevision for EphemeralRunnerSet and ObservedGeneration for AutoscalingRunnerSet (and updates CRDs accordingly).
  • Refactors patch flows to use a lazy DeepCopy helper (once) so multiple in-place mutations can be safely applied before client.MergeFrom(...).

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
main.go Instantiates and wires a shared ResourceCache into ResourceBuilder.
controllers/actions.github.com/utils.go Adds once lazy-copy helper used to build correct MergeFrom patches.
controllers/actions.github.com/utils_test.go Moves test-only random string helper into tests.
controllers/actions.github.com/resourcecache.go Adds typed resource cache with dependency-key tracking and eviction by owner UID.
controllers/actions.github.com/resourcecache_test.go Adds unit tests for cache semantics (dependency ordering, deletion, invalid inputs).
controllers/actions.github.com/resourcebuilder.go Uses cache for desired-object reuse; removes integrity-hash annotations; refactors label/annotation merging.
controllers/actions.github.com/resourcebuilder_test.go Updates tests for removed integrity-hash annotation; adds merge-map tests and cache expectations.
controllers/actions.github.com/helpers.go Adds helper predicates for actionable revision and pod recreation decisions.
controllers/actions.github.com/ephemeralrunnerset_controller.go Switches to actionable revision + status patching with conflict retries; refactors patching with lazy copies.
controllers/actions.github.com/ephemeralrunnerset_controller_test.go Updates/extends integration tests for cache cleanup and actionable revision behavior.
controllers/actions.github.com/ephemeralrunner_controller.go Refactors finalizer patching to use lazy-copy pattern; deletes cached entries on deletion.
controllers/actions.github.com/ephemeralrunner_controller_test.go Extends tests to assert cache cleanup on runner deletion.
controllers/actions.github.com/autoscalingrunnerset_controller.go Uses observed generation for Pending detection; switches spec-change handling to actionable revision; refactors patch logic.
controllers/actions.github.com/autoscalingrunnerset_controller_test.go Updates tests to assert observed generation and actionable revision behavior; adds cache assertions.
controllers/actions.github.com/autoscalinglistener_controller.go Refactors patching of dependent resources using lazy copies; updates pod recreation decision logic.
controllers/actions.github.com/autoscalinglistener_controller_test.go Extends tests to assert resources are cached and evicted appropriately.
config/crd/bases/actions.github.com_ephemeralrunnersets.yaml Adds actionableRevision, appliedActionableRevision, finishedRunnerCleanupPatchID schema fields.
config/crd/bases/actions.github.com_autoscalingrunnersets.yaml Adds observedGeneration to status schema.
charts/gha-runner-scale-set-controller/crds/actions.github.com_ephemeralrunnersets.yaml Mirrors ERS CRD schema additions into chart CRDs.
charts/gha-runner-scale-set-controller/crds/actions.github.com_autoscalingrunnersets.yaml Mirrors ARS CRD schema additions into chart CRDs.
charts/gha-runner-scale-set-controller-experimental/crds/actions.github.com_ephemeralrunnersets.yaml Mirrors ERS CRD schema additions into experimental chart CRDs.
charts/gha-runner-scale-set-controller-experimental/crds/actions.github.com_autoscalingrunnersets.yaml Mirrors ARS CRD schema additions into experimental chart CRDs.
apis/actions.github.com/v1alpha1/version.go Minor condition reordering in version allowance check.
apis/actions.github.com/v1alpha1/ephemeralrunnerset_types.go Adds ActionableRevision spec field and applied/cleanup status fields.
apis/actions.github.com/v1alpha1/autoscalingrunnerset_types.go Adds ObservedGeneration to status.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread controllers/actions.github.com/autoscalingrunnerset_controller.go Outdated
Comment thread controllers/actions.github.com/autoscalinglistener_controller.go Outdated
Comment thread controllers/actions.github.com/utils.go Outdated
@nikola-jokic
nikola-jokic changed the base branch from master to nikola-jokic/remove-annotation-fingerprint September 8, 2026 07:37
@nikola-jokic
nikola-jokic force-pushed the nikola-jokic/lazy-copy branch 2 times, most recently from 7b5e00f to fdb0651 Compare September 9, 2026 20:17
@nikola-jokic
nikola-jokic requested a review from a team as a code owner September 9, 2026 20:17
@nikola-jokic
nikola-jokic changed the base branch from nikola-jokic/remove-annotation-fingerprint to nikola-jokic-revision-aware-outdated September 9, 2026 20:18
@nikola-jokic
nikola-jokic added this pull request to stack #4645 September 9, 2026 21:42
@nikola-jokic
nikola-jokic requested a balanced review from Copilot September 9, 2026 21:44

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Immutable RoleBinding and Secret fields are patched in place, which can leave reconciliation permanently failing.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity · 1 Medium severity

New issues introduced by this change (2)
Severity Finding
High severity controllers/​actions.github.com/​autoscalinglistener_controller.go — A Secret's immutable flag cannot be unset after it becomes true. For an immutable live proxy…
Medium severity controllers/​actions.github.com/​autoscalinglistener_controller.goRoleBinding.roleRef is immutable after creation. If this comparison ever detects drift, the…
Issues resolved since last review (3)
Severity Finding
Low severity controllers/​actions.github.com/​utils.go — The panic message in (*once).Get() is very generic. Using a more specific message will make… View resolved comment
Low severity controllers/​actions.github.com/​autoscalinglistener_controller.gorulesModified is misleading here because this block is checking/modifying RoleRef, not RBAC… View resolved comment
Low severity controllers/​actions.github.com/​autoscalingrunnerset_controller.go — Typo in the variable name listnerLabelsModified makes the code harder to read/search. Rename it… View resolved comment
Suppressed comments (1)

controllers/actions.github.com/autoscalinglistener_controller.go:459

  • A Secret's immutable flag cannot be unset after it becomes true. Setting it to nil guarantees this patch is rejected for an immutable config Secret, leaving reconciliation stuck; preserve it when data is unchanged, or delete and recreate the Secret when the config changes.
		if listenerConfigSecret.Immutable != nil {
			original.Do()
			listenerConfigSecret.Immutable = nil
		}

Comment thread controllers/actions.github.com/autoscalinglistener_controller.go Outdated
Comment thread controllers/actions.github.com/autoscalinglistener_controller.go Outdated
rentziass
rentziass previously approved these changes Sep 10, 2026

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

The implementation is coherent and tested; the identified documentation correction is non-blocking.

Review tier: Balanced
Findings: 1 High severity · 1 Medium severity · 1 Low severity

New issues introduced by this change (1)
Severity Finding
Low severity controllers/​actions.github.com/​lazycopy.go — Do not claim mutation ordering is enforced
Pre-existing issues (2)
Severity Finding
High severity controllers/​actions.github.com/​autoscalinglistener_controller.go — A Secret's immutable flag cannot be unset after it becomes true. For an immutable live proxy… View comment
Medium severity controllers/​actions.github.com/​autoscalinglistener_controller.goRoleBinding.roleRef is immutable after creation. If this comparison ever detects drift, the… View comment

Comment thread controllers/actions.github.com/lazycopy.go

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

The implementation is consistent and tested; only a non-blocking documentation correction remains.

Review tier: Balanced
Findings: None

Issues resolved since last review (3)
Severity Finding
Low severity controllers/​actions.github.com/​lazycopy.go — Do not claim mutation ordering is enforced View resolved comment
Medium severity controllers/​actions.github.com/​autoscalinglistener_controller.goRoleBinding.roleRef is immutable after creation. If this comparison ever detects drift, the… View resolved comment
High severity controllers/​actions.github.com/​autoscalinglistener_controller.go — A Secret's immutable flag cannot be unset after it becomes true. For an immutable live proxy… View resolved comment

rentziass
rentziass previously approved these changes Sep 11, 2026
@nikola-jokic
nikola-jokic dismissed rentziass’s stale review September 12, 2026 21:23

The merge-base changed after approval.

nikola-jokic and others added 2 commits September 12, 2026 23:28
Reconcilers deep copied the object they had just fetched on every single
reconcile, purely so a merge patch could be computed on the rare pass
that actually changes something. The copy is a full recursive walk and
allocation of the object, and the overwhelming majority of reconciles
throw it away untouched.

Introduce lazyCopy, which takes the snapshot on the first call to
Mutate and hands back the live object. Because Mutate is the only way
to reach the object, the snapshot cannot be taken after the mutation it
is supposed to be diffed against, which is the way this optimization is
usually gotten wrong.

Apply it to the four Reconcile entry points, and move the two
EphemeralRunnerSet status copies inside the branch that patches, so they
are only paid for when the status really changed.

While here, drop the short circuit in the EphemeralRunner finalizer
block: `addedFinalizers || AddFinalizer(...)` skipped adding the actions
finalizer whenever the first finalizer was added.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lazycopy.go introduces deepCopyObject, a generic type constraint rather
than a collaborator, and the package's "all: true" mockery config picks
it up and emits a large mock that nothing can use. Switch the package to
an explicit include/exclude regex pair so every other interface is still
discovered automatically while deepCopyObject is skipped.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.

3 participants